Службы доставки

Коннекторы служб доставки добавляют логистические сервисы с возможностью отправки посылок, отслеживания доставки, распечатки этикеток и расширения формы обработки заказа в CRM.

Создание службы доставки

Работа с коннкеторами для служб доставки ведётся в разделе "Службы доставки" по кнопке "Файлы". Сами файлы коннекторов располагаются в папке delivery вашего хака. Название файла может состоять из маленьких латинских букв и цифр. Оно обязательно должно начинаться с буквы, а не цифры. Нельзя использовать заглавные буквы и спецсимволы, в том числе дефис или знак подчёркивания.

В момент создания коннектора, в файле автоматически создаётся соответствующий класс. Название класса состоит из слова delivery, названия хака и названия коннектора, разделённых знаком подчёркивания, например: delivery_dhl. Коннектор должен наследоваться от класса deliverybase.

class delivery_dhl extends deliverybase { … }

Класс службы может реализовывать следующие поля и функции:

  • track() - трекинг посылки, основа работы служб доставки.
  • calc() - подсчёт стоимости и сроков доставки.
  • send() - отправка посылки в службу доставки.
  • $cansend - поддерживается отправка посылок.
  • $canauto - поддерживается автоматическая отправка посылок.
  • bulkprint() - массовая распечатка этикеток.
  • $canbulk - поддерживается массовая распечатка этикеток.
  • options() - доступные кнопки службы доставки.
  • actions() - реализация действий для кнопок службы доставки.
  • block() - контент-блок и дополнительная форма для службы доставки.
  • ajax() - реализация действий и загрузки данных для контент-блока.
  • _courier( $id ) - возвращает внутреннее имя курьера по ID из поля courier для аналитики.
  • _tariff( $id ) - возвращает внутреннее имя тарифа по ID из поля tariff для аналитики.

Обязательной реализации не существует, но рекомендуется как минимум реализация трекинга.

Подключение к системе

Коннекторы не появятся в списке служб доставки автоматически, вам нужно указать их через файл запуска. Для этого в массиве инициализации укажите ключ delivery, а в качестве значения - список доступных в вашем хаке коннекторов. Например:

return [
  /* другие команды инициализации */
  'delivery' => [ 'cdek', 'dhl', 'ponyexpress' ],
];

Трекинг

Основной функционал любой службы доставки - это отслеживание посылок. Функция track должна принимать на вход два параметра: трек-код и массив с заказом. В качестве результата должна возвращать ассоциативный массив с историей статусов, текущим статусом посылки и временем следующей проверки.

return [
  'next'   => time() + 43000, // Время следующей проверки, UNIX timestamp
  'status' => $status, // Текущий статус посылки
  'time'   => $tm, // Время изменения статуса, UNIX timesramp
  'stage'  => $stage // Массив статусов
];

Поле status может принимать одно из следующих числовых значений:

  1. wait — посылка ожидает отправки.
  2. transfer — посылка находится в пути, переводится в статус "Доставка".
  3. problem — при доставке посылки возникли проблемы, требуется реакция поставщика.
  4. delivered — посылка прибыла в пункт назначения, переводится в статус "Доставлено".
  5. paid — посылка успешно выкуплена, переводится в статус "Оплачено".
  6. return — посылка возвращена или утилизирована, переводится в статус "Возврат".
  7. comment — произвольный комментарий, не меняющий состояние посылки.

Массив stage должен состоять из записей движения заказа. Записи рекомендуется отсортировать от самой старой к самой свежей. Каждая запись может содержать следующие поля:

  • status - числовой идентификатор статуса.
  • time - время возникновения статуса.
  • country - двух-символьный ISO-код страны, в которой возник статус.
  • zip - индекс или ZIP-код отделения связи, на котором возник статус.
  • city - город, в котором возник статус.
  • comment - произвольный текстовый комментарий к статусу.
  • md5 - уникальный хеш статуса, по которому проводится проверка дубликатов.

Подготовка массива статусов и определения конечного статуса и времени может выглядеть вот так:

$stage = [];
$tm = $status = 0;
foreach ( $info as $i ) {

  // Make the stage
  $s = [
    'status'  => $this->s2i[$i['status']],
    'time'    => $core->text->number( $i['time'] ),
    'country' => $core->text->link( $i['country'] ),
    'zip'     => $core->text->anum( $i['zip'] ),
    'city'    => $core->text->line( $i['city'] ),
    'comment' => $core->text->line( $i['comment'] ),
  ];

  // Add stage to the list
  $tm = $s['time'];
  $status = $s['status'];
  $s['md5'] = md5( $s['status'].$s['time'].$s['country'].$s['zip'].$s['city'].$s['comment'] );
  $stage[] = $s;

}

Рекомендуется ознакомиться с примерами реализации механизмов проверки статусов посылок, которые доступны в системе. Код модулей интеграции со службами доставки открыт. Файлы располагаются в папке core/delivery вашей платформы.

Калькулятор стоимости и сроков

Калькулятор стоимости и сроков позволяет подставить цену и себестоимость доставки в посылку. Функция calc получает на вход массив с данными заказа. В качестве результата должна вызывать функцию calcs с четырьмя параметрами: цена доставки, данные заказа, минимальный срок, максимальный срок. Обязательным параметром является только цена доставки.

Пример простой реализации функции:

public function calc( $o ) {
    $price = $this->somemagic( $o );
    return $this->calcs( $price, $o );
}

Пример реализации функции со сроками доставки:

public function calc( $o ) {
    $price = $this->_price( $o );
    $term = $this->_term( $o );
    return $this->calcs( $price, $o, $term['min'], $term['max'] );
}

Отправка посылки

Автоматическая отправка посылки выполняется через функцию send, которая принимает на вход массив данных заказа и должна возвращать true или false в зависимости от успеха.

В процессе работы, функция сама должна отредактировать заказ и внести в него трек-код и иные необходимые данные. Это реализуется функцией $core->lead->edit() с указанием трек-кода в параметре track и активацией трекинга в параметре trackon.

$core->lead->edit( $o['order_id'], [ 'track' => $code, 'trackon' => 1 ] );

Пример реализации функции отправки:

public function send( $o ) {
    $code = $this->somemagic( $o );
    if ( $code ) {
        $this->core->lead->edit( $o['order_id'], [ 'track' => $code, 'trackon' => 1 ] );
        return true;
    } else return false;
}

Чтобы активировать автоматическую отправку посылок, укажите в начале объявления класса:

public $canauto = true;
public $cansend = true;

Печать бланков

Массовая распечатка бланков реализуется через функцию bulkprint, которая принимает на вход массив трек-кодов посылок. При этом ключ массива - ID заказа в системе. Функция должна возвращать массив ссылок или путей к файлам, в которых хранятся готовые бланки. Автоматика сама скачает нужные бланки, сформирует из них архив и выдаст пользователю.

Пример реализации функции массовой распечатки:

public function bulkprint( $ids ) {
    $files = [];
    foreach ( $ids as $i ) $files[] = $this->_print( $i );
    return $files;
}

Чтобы активировать массовую распечатку бланков, укажите в начале объявления класса:

public $canprint = true;

Также, хорошим тоном будет завести функцию print, которая принимает на вход массив данных заказа и возвращает ссылку на файл с бланком. Пример реализации такой функции:

public function print( $o ) {
  if ( $o['order_status'] > 9 ) return false;
  if ( $o['order_status'] < 6 ) return false;
  if ( ! $o['track_code'] ) return false;
  return $this->_print( $o['track_code'] );
}

Кнопки действий

Кнопки действий добавляются в правую часть формы заказа под блоком с техническими данными (метки, гео, источники). С помощью этих кнопок вы можете вызывать внутренние действия вашей службы доставки, такие как отправка посылки, распечатка документов нужного типа или отмена отправки посылки.

За список действий отвечает функция options, которая принимает на вход один параметр - массив заказа. Она должна возвращать массив, в котором содержится описание пунктов меню. Каждый пункт меню - это массив, содержащий поля:

  • name - заголовок пункта меню, например "Отправить посылку".
  • short - краткий заголовок, который отображается в списке заказов, например "Отправить".
  • icon - название иконки Font Awesome, которая прикрепляется к пункту меню, например fa-paper-plane.
  • confirm - текст всплывающего окна с подтверждением действия, например "Вы уверены, что хотите отправить эту посылку?"
  • url - ссылка на само действие.

Ссылку на действие нужно формировать следующей функцией:

$core->u([ 'order', $o['order_id'] ], 'action=dlaction&oa=send' )

Где вместо send будет указываться ваше название действия. Пример функции может выглядеть так:

public function options( $o ) {
  if ( $o['order_status'] > 9 ) return false;
  if ( $o['order_status'] < 6 ) return false;
  $core = $this->core;
  if ( $o['track_code'] ) {
    return [[
      'icon'    => 'fa-address-card',
      'url'     => $core->u([ 'order', $o['order_id'] ], 'action=dlaction&oa=print' ),
      'name'    => $core->lang['delivery_print'],
      'short'   => $core->lang['delivery_print_s'],
    ]];
  } elseif ( $this->config['api'] ) {
    return [[
      'icon'    => 'fa-paper-plane',
      'url'     => $core->u([ 'order', $o['order_id'] ], 'action=dlaction&oa=send' ),
      'name'    => $core->lang['delivery_send'],
      'short'   => $core->lang['delivery_send_s'],
      'confirm' => $core->lang['delivery_send_c'],
    ]];
  }
}

За обработку действий отвечает функция action, которая принимает два параметра - название действия и массив заказа. Функция должна обрабатывать действие и перенаправлять пользователя на страницу заказа с выполненным действием или ошибкой.

Пример функции, которая реализует предыдущий массив действий:

public function action( $action, $o ) {
  switch ( $action ) {

    case 'send':
    if ( $this->send( $o ) ) {
      $this->core->msgo( 'ok', $this->core->u([ 'order', $o['order_id'] ]) );
    } else $this->core->msgo( 'error', $this->core->u([ 'order', $o['order_id'] ]) );

    case 'print':
    if ( $url = $this->print( $o ) ) {
      $this->core->go( $url );
    } else $this->core->msgo( 'error', $this->core->u([ 'order', $o['order_id'] ]) );

    default: $this->core->msgo( 'error', $this->core->u([ 'order', $o['order_id'] ]) );

  }
}

Для обработки ошибки или успеха, используйте умное перенаправление вот такого вида:

$this->core->msgo( 'error', $this->core->u([ 'order', $o['order_id'] ]) );

Вместо 'error' может быть указано 'ok' в случае успешного выполнения.

Формы

Форма добавляется на страницу редактирования заказа в CRM. Она располагается под списком товаров, над основными данными заказа. В этой форме можно реализовать выбор курьеров, методов доставки, пунктов выдачи, даты и времени получения и любых других внутренних параметров, характерных для доставки.

За вывод формы отвечает функция block, которая принимает на вход один параметр - массив данных заказа. Функция должна возвращать сгенерированный код для вставки в страницу.

Код формы рекомендуется генерировать с помощью встроенного шаблонизатора. Для загрузки шаблона используйте функцию $core->hack->dhl->tpl( 'innerform', 'shpt-dhl' ), где вместо dhl указывается название вашего хака, а shpt-dhl - это название вашего шаблона. Для генерации шаблона используйте $core->tpl->make( 'innerform' ).

Пример реализации функции:

public function block( $o ) {
  $core = $this->core;
  $tm = xxdec( $o['track_meta'] );
  $courier = isset( $tm['courier'] ) ? $tm['courier'] : -1;
  $core->hack->dhl->tpl( 'innerform', 'shpt-dhl' );
  $core->tpl->vars( 'innerform', [
    'u_check'  => $core->u( [ 'order', $o['order_id'] ], 'action=dlajax&oa=check' ),
    'couriers' => json_encode( $this->courier, JSON_PRETTY_PRINT ),
  ]);
  foreach ( $this->courier as $i => $n ) $core->tpl->block( 'innerform', 'courier', [ 'i' => $i, 'n' => $n, 's' => $i == $courier ]);
  return $core->tpl->make( 'innerform' );
}

AJAX-действия

Действия через AJAX могут использоваться для получения данных и выполнения задач встроенными формами. За обработку действий отвечает функция ajax, которая принимает на вход код действия и массив заказа. Функция должна возвращать массив данных, который будет передан в ответе как JSON-объект.

Пример реализации функции:

public function ajax( $action, $o ) {
  switch ( $action ) {
    case 'cities':
    $area = $this->core->post['area'];
    return $this->cities( $area );
  }
}

Ссылка на саму функцию должна формироваться с помощью кода:

$core->u( [ 'order', $o['order_id'] ], 'action=dlajax&oa=cities' )

Где вместо cities будет указываться код действия, которое необходимо выполнить.

Вы можете сохранять внутри заказа дополнительные данные, которые используете в формах и передаёте через AJAX-действия. В этих данных можно хранить идентификаторы тарифов, курьеров, пунктов выдачи и прочих внутренних сущностей доставки.

Данные доставки принято хранить в поле track_meta массива данных заказа. Данные извлекаются вот так:

$tm = xxdec( $o['track_meta'] );

Для сохранения мета-данных доставки, используйте следующий код:

$core->lead->edit( $o['order_id'], [ 'tmeta' => $tm ] );

Обратите внимание, что в $tm должны присутствовать все поля сразу. Если вы отправите только часть полей, другие ранее существовавшие поля будут удалены.

Статистика по тарифам и курьерам

В массиве track_meta используется два "волшебных" параметра, которые позволяют строить аналитику доставки в разрезе внутренних тарифов и курьеров. Чтобы реализовать такую статистику, добавьте два поля:

  • tariff - идентификатор тарифа службы доставки.
  • courier - идентификатор курьера службы доставки.

Рекомендуется использовать числовые идентификаторы. Для красоты показа статистики, вы можете добавить функции _tariff и _courier, которые принимают на вход идентификатор тарифа или курьера и возвращают его красивое название.

Функция настройки

Чтобы добавить дополнительные поля к настройкам, необходимо реализовать две функции:

  • form - возвращает массив с дополнительными полями формы.
  • save - принимает сырые данные и возвращает массив с полями конфигурации.

Функция form не имеет параметров. Доступ к полям конфигурации должен выполняться через массив $this->config. Результатом работы функции должен быть массив полей формы. Каждое поле - это массив, который может содержать следующие поля:

  • type - тип поля: text, email, textarea, number, select, mselect, radio, checkbox.
  • name - название поля, рекомендуется добавлять префикс с названием модуля.
  • head - заголовок поля, используйте языковой файл для хранения.
  • descr - описание поля, используйте языковой файл для хранения.
  • value - текущее значение поля, используйте $this->config.
  • options - набор опций для полей типа select, mselect, radio.
  • checked - пометка для поля типа checkbox.

Пример реализации формы:

public function form() {
  return [
    [ 'type' => 'head', 'value' => $this->core->lang['settings'] ],
    [ 'type' => 'text', 'name' => 'generic_url', 'head' => $this->core->lang['delivery_url_track'], 'value' => $this->config['url'] ],
    [ 'type' => 'text', 'name' => 'generic_doc', 'head' => $this->core->lang['delivery_url_doc'], 'value' => $this->config['doc'] ],
  ];
}

Функция save получает на вход параметр $data и должна вернуть обработанный массив параметров в виде ключ-значение. Пример реализации:

public function save( $data ) {
  return [
    'url' => str_replace( '&', '&', $this->esc( $data['generic_url'] ) ),
    'doc' => str_replace( '&', '&', $this->esc( $data['generic_doc'] ) ),
  ];
}