Автор фото: Yanalya/Freepic

Интеграция сайта на Laravel с 1С — туториал-исповедь от оптимиста.

Арсен Гоян

--

Решил написать статью о том, как скрестить пингвина с дельфином на русском, т.к. целевая аудитория для 1С в основном русскоязычная. Статья предназначена для PHP-разработчиков, которым предстоит реализовать интеграцию сайта на Laravel с продуктом компании 1С.

Из скиллов нужно — composer, Laravel, будет полезно уметь дебажить с помощью xdebug и PHPStorm.

Когда я услышал, что предстоит синхронизировать заказы в интернет-магазине с приложением “1С-Предприятие”, эмоции были смешанные. С одной стороны — это точно понадобится! С другой стороны, один только бренд “1С” вызывает у разработчика ноющую боль где-то в верхней части желудка.

Стоило взглянуть на документацию, и стало понятно, что “Протокол обмена”, как они его назвали, это просто какой-то франкенштейн в мире программных интерфейсов. Такое впечатление, что они внимательно почитали все существующие гайдлайны и конвенции, а потом сделали наоборот. Но, по крайней мере, создалось впечатление, что в этот раз обойдётся без кода кириллицей (я ошибался).

Но делать надо было. Так что не долго думая, я избрал путь самурая, то есть — делать. Лишь позже я прочитал, что по статистике от трети до половины попыток интеграции сайта с 1С заканчивается провалом.

TL;DR

  1. composer require mavsan/laravel-1c-protocol и composer require arsengoian/commerce-ml
  2. Выполнить всё, написанное тут.
  3. Прописать в конфиге ‘saleShareModel’ => OneCExportOrders::class, реализовать эту модель. Она должна реализовать интерфейс Mavsan\LaProtocol\Interfaces\ExportOrders.
  4. Создать адаптеры для классов заказа и товара, наследующие от CommerceML\Constructors\Document, CommerceML\Constructors\Product
  5. На выходе должен выйти файл, подобный этому.
  6. Настраиваем “Обмен данными с WEB-сайтом” в 1С и тестируем

Что это вообще и зачем?

Если вы, мой милый читатель, не из мира бухгатерии, финансов или управления, вам сказали делать интеграцию, а вы ещё не знаете, зачем это, экскурс в двух словах:

  1. Любой компании нужен учёт (зарплат, складов, заказов итп), будь то для внутренних потребностей (руководство хочет оценить успешность бизнеса) либо для внешних (надо заплатить налоги и сдать отчётность).
  2. Отчётность надо сдавать согласно местному законодательству (Например, в Украине — НПСБО), а значит 99.9% известных западных ERP-систем не годится, т.к. нет соответствующих модулей.
  3. Решение для большого бизнеса — допилить модуль самостоятельно. Решения для малого бизнеса — купить родную СНГ-шную систему, например (в большинстве случаев) — 1С.
  4. Для бизнесов, которые решились на п̶р̶о̶т̶и̶в̶о̶е̶с̶т̶е̶с̶т̶в̶е̶н̶н̶о̶е̶ сайт на Битриксе, интеграция идет “из коробки”.

Путь самурая

Сайт у нас не на Битриксе, а на Laravel. Так что всю интеграцию пришлось делать ручками!

Итак, расстановка сил была следующая:

  • Laravel 5.7 + PHP 7.2
  • 1C-Предприятие 8.2
  • Shared хостинг на одном из популярных украинских сервисов
  • Лаконичная документация
  • 1 печальный бэкенд-инженер

Как?

Конечно, ни о каких вебхуках или REST API речи даже не идёт. Идея такова: приложение 1С на Windows само отправляет запрос типа http://<сайт>/<путь> /1c_exchange.php?type=TYPE&mode=MODE.

Получается 4 таких шага:

  1. mode=checkauth Начало сеанса — сайт получает в HTTP заголовке юзернейм и пароль и в ответ отправляет куки в теле HTTP ответа. Куки с одному Богу известной целью отправляются на каждом следующем шаге вместе с юзернеймом.
  2. mode=init Уточнение параметров сеанса — сайт должен отдать инфу о поддержке zip-сжатия и максимальном размере выгружаемого с 1С файла (человеческим языком, максимальный размер тела HTTP-запроса для импорта на сайт), так словно нельзя было это отдать в предыдущем запросе.
  3. mode=query “Полезный” запрос — мы возвращаем список заказов в формате CommerceML! Чтоо? А это ещё что? XML-схема, придуманная 1C для обмена с сайтом. С тегами на русском. И кодировкой Windows-1251. О ней поговорим позже.
  4. mode=success Да, серьёзно. Ну вы поняли.

Ещё есть mode=file для того, чтобы подтягивать заказы обратно с 1С на сайт (зачем?), но давайте это опустим. Вдруг вы захотите проверить успешность экспорта. Скажу только, что готовое решение, о котором поговорим позже будет паковать заказы в файлы в папке из переменной конфига inputPath. Если вам ценно место на диске, есть смысл поставить на хрон очистку этой папки.

Поняли, о чём я говорил, когда жаловался на протокол обмена?

К счастью, вот тут нашлась готовая либа для интеграции. Даже доступная на packagist.org! Дело в шляпе? А вот и нет. Человек сделал интеграцию для подтягивания каталога (возможно, позже поговорим об этом в другой статье). А 50% кода для подтягивания заказов… Нет!

Окей. Доделаем. Наверное, есть готовое решение для того, чтобы произвольную структуру данных переделать в CommerceML? Нет! Есть только для чтения. А для записи — 0!

Пы.сы. Состоянием на конец 2018 года мой пулл-реквест с экспортом заказов так и висит вот тут. Так что если понадобится, либу берите оттуда…

Боремся с CommerceML

Хорошо. Либы нет — надо делать. Полный мастер-класс тут расписывать не буду, скажу только, что сделал на основании SimpleXML небольшую библиотеку-враппер, которая покрывает только нужные для экспорта заказов теги (да, я лентяй.)

Типизировал все теги, имеющие дочерние элементы. В итоге, создание списка заказов выглядит вот так (если строить не по кусочкам, а все сразу):

new CommercialInformation([
new Document(
'142',
'42',
date('Y-m-d'),
'Заказ товара',
'Продавец',
'UAH',
'30',
'4000',
new Counterparties([
new Counterparty(
'19',
'Jake',
NULL,
'User',
'Jake Sully',
'Sully',
'Jake',
new Address([
new AddressField('Улица', 'Ул. Тестера'),
new AddressField('Дом', '7а'),
new AddressField('Квартира', '104'),
], '87698'),
new Representatives([
new Representative(
new Counterparty('20', 'Jenny', 'Admin')
)
])
)
]),
date('H:i:s'),
NULL,
new Products([
new Product(
'192',
'Тетріс',
'4',
new BaseUnit('796', 'Штука', 'PCE', 'шт'),
'400',
'5',
'2000',
new RequisiteValues([
new RequisiteValue('ВидНоменклатуры', 'Товар')
])
)
]),
new RequisiteValues([
new RequisiteValue('Заказ оплачен', TRUE),
new RequisiteValue('Метод оплаты', 'Наличный расчет'),
new RequisiteValue('Дата оплаты', '2007-10-16 15:44:44'),
])
)
])

Тут сразу у тебя, читатель, рождается закономерный вопрос. Но можно же было просто сделать blade-шаблон с CommerceML и подставить туда нужные значения? Можно. Но мы ступили на путь самурая! И поэтому делать всё будем со вкусом. Расширяемо. Модульно.

Значения в CommerceML

Тут надо отметить: в некоторые поля надо будет прописать типичные значения. В том числе,

  • В документе: экономическая операция — Заказ товара, роль — Продавец, обменный курс1, валюту указывать кирилличным сокращением малыми буквами, например, грн или руб.
  • В контрагентах (пользователях): роль — Покупатель
  • В адресе представление я писал 87698, без понятия, что это значит. Поля — Город и Адрес доставки. То есть:
new Address([
new AddressField('Город', $this -> order -> city() -> name),
new AddressField('Адрес доставки', $this -> order -> address)
], '87698'),
  • Представителей везде делал пустыми: new Representatives([]).
  • В продуктах:

Реквизиты:

new RequisiteValues([
new RequisiteValue('ВидНоменклатуры', 'Товар')
]);

Базовая единица (штука, взято с примера):

new BaseUnit('796', 'Штука', 'PCE', 'шт');

Что делать с сайтом?

Получается, нам осталось зареквайрить composer’ом mavsan/laravel-1c-protocol и arsengoian/commerce-ml, при чем вместо первой — вставить переделанную версию библиотеки.

Настраиваем библиотеку для синхронизации (скопировал из документации):

  • Зарегистрировать сервис-провайдер в config/app.php
/*
* Протокол обмена информацией с 1С
*/
Mavsan\LaProtocol\Providers\ProtocolProvider::class,
// сервис-провайдер:
/*
* работа с zip архивами
*/
Chumper\Zipper\ZipperServiceProvider::class,
// фасад
/*
* Работа с zip архивами
*/
'Zipper' => Chumper\Zipper\Zipper::class,
  • Опубликовать конфигурацию и изменить при необходимости: php artisan vendor:publish --tag=la1CProtocolConfig.

Открываем конфигу, прописываем модель для экспорта.

'saleShareModel' => OneCExportOrders::class,

Эту модель теперь надо реализовать. Она должна реализовать интерфейс Mavsan\LaProtocol\Interfaces\ExportOrders.

Теперь думаем по логике.

Очевидно, что на нашем (hopefully) готовом сайте уже есть готовые классы для продукта и заказа. Просто делаем для них адаптеры. Получается, если у нас обычный сайт, надо создать свою имплементацию для тегов “Документ” и “Товар”. Для этого мы создаём классы DocumentAdapter и ProductAdapter соответсвенно. Они наследуют от имплементаций из библиотеки: CommerceML\Constructors\Document, CommerceML\Constructors\Productсоответственно.

Для продукта это будет выглядеть как-то так:

public static function defaultBaseUnit(): BaseUnit {
return new BaseUnit('796', 'Штука', 'PCE', 'шт');
}

private static function getRequisiteValues(): RequisiteValues {
return new RequisiteValues([
new RequisiteValue('ВидНоменклатуры', 'Товар')
]);
}

public function __construct(Product $element, int $count) {
return parent::__construct(
$element -> id,
$element -> title,
$element -> category_id ?? 0,
self::defaultBaseUnit(),
$element -> price,
$count,
$element -> price * $count,
self::getRequisiteValues()
);
}

Вообще, ориентируемся на то, чтобы в итоге вышел заказ с сайта, как в документации 1С. Но реализуем его с помощью библиотеки и подставляем нужные значения для параметров конструкторов имплементаций.

Сделали реализацию протокола. Теперь что? Как дебажить?

Пришли к самому замечательному — настройке самой 1С-ки. И заодно рождается вопрос. Как это всё тестить вообще?

Ну, для того, чтобы порядочно это протестировать, должен быть установлен и настроен дебаггер у нас на локальном компе или на удалённом сервере. Если внезапно его всё ещё нет: вот туториал для XDebug и PHPStorm’а.

По сути, выборов тут миллион, я решил работать так: имея уже настроенный комп и ngrok, просто перенастроил свой локальный хост на локалхост и раздал с помощью комманды ngrok http 80. Отличная утилита, к слову. Как вариант, если настроен локально виртуальный хост, отличный от локалхоста (dev.localonlinestore.com в примере), можно сделать и так: ngrok http -host-header=rewrite dev.localonlinestore.com:80. К слову, купив тут платную версию, можно получить статичный домен вроде johndoe.ngrok.io. Очень удобно, так как не надо перенастраивать 1С каждый раз когда включаешь , как я делал.

Немного сложнее будет настроить xdebug или zend debugger на удалённом сервере. Тогда можно к ним подсоединиться через ssh-туннель коммандой ssh -R 9000:localhost:9000 user@yourvps.com, где 9000 — порт, на котором настроен дебаггер. Плюс опять в том, что не надо настраивать заново 1С каждый раз. Больше об удалённом дебаггинге, если не слышали, в этом туториале, опять-таки для PhpStorm’а. К сожалению, со своим shared hosting не мог себе это позволить.

Третий вариант — ваш 1С уже стоит локально на компе рядом с сайтом. Ура! Можно дебажить локально.

Настроив дебаггер, ставим брейкпойнт где-то в коде нашей модели.

В крайнем случае, если всё это не получилось, в Laravel к вашим услугам удобнейший логгер, хотя на дебаг уйдет в 3 раза больше времени, так что…)

Как настроить 1С?

Логинимся в наш компьютер с 1С, заходим в приложение. Радуемся тёплому и интуитивному интерфейсу.

Выбираем “Настроить обмен данными с WEB-сайтом”

Если настроек ещё не создавали, сразу откроется окно создания.

Вводим все нужные данные.

Обратите внимание, добавляем к пути параметр XDEBUG_SESSION_START=ide_key, где ide_key — ваш настроенный в php.ini ключ. Это если мы собираемся дебажить!

На счёт параметра пути 1c-exchange — это значение ключа 1cRouteNameCatalog в конфиге config/protocolExchange1C.php.

Ещё одно — для меня оказалось это полнейшим сюрпризом, но 1С вплоть до новейших подверсий 8.3. не поддерживает HTTPS. Так что копируем адрес ngrok.io с HTTP. (Или настраиваем свой сервер соответствующим образом).

А вот при настройке продакшновой версии интеграции если у вас на сайте авторедирект с HTTP на HTTPS (а он должен быть, 2019 год на улице), вам не повезло. Придётся настроить субдомен. По сути, можно скопировать файл public/index.php в какую-то папку рядом с public и настроить там корень субдомена. У меня вышло 1c.website.com. Не забудьте отключить редирект для субдомена!

На следующем этапе настраиваем все параметры тут. Выбираем организацию, ответственного и т.д. Если не существует — создаём. Тут всё просто. Рекомендую посоветоваться тут с админом 1С или бухгалтером.

Тут мне показалось логичным проставить галочки именно так. Тоже советуйтесь с бухгалтером.
Важно вот тут переключить, чтобы не засорять 1С-ку

На следующем шагу настраиваем регулярность (не чаще 30 минут…) и… готово!

Можем сразу открыть нашу новую настройку для редактирования.

… и протестировать.

Если всё вышло, получим что-то такое:

Сколько счастья от одной картинки!

Ура!

В случае, если в процессе будет выброшено исключение, если повезёт, весь мусор будет напечатан в том же логе, но я не стал бы на это рассчитывать =)

Подводные камни

Напоследок хотелось бы отметить следующее:

  • Не забывайте, что 1С пишется с кирилличной “С”. Мне создали пользователя с названием 1СAdmin и я долго не мог залогиниться… То же касается названий некоторых папок. В общем, избегайте использования “1С” в названиях. Где-либо.
  • Не забудьте, что нет поддержки HTTPS. В случае HTTPS — никакого сообщения об ошибке.
  • По протоколу единственная верная кодировка — Windows-1251. Никакого юникода. В случае, если не та кодировка — никакого сообщения об ошибке.
  • Обратите внимание на значение некоторых тегов. Для них есть фиксированные значения, описанные в документации CommerceML. Рекоммендую то, что выше в статье (“Значения в ComerceML”).
  • Библиотеки тестировались в среде Windows 10 и CentOS.
  • В случае, если вы настроили субдомен в папке рядом с public и IDE спрашивает, через какую папку настраивать, не перепутайте. Можно долго думать, почему дебаггер не включается.
  • Проследите, чтобы либо всё, что касается экспорта удалялось с Soft Deletes, либо чтобы в случае отсутствия какого-то значения возвращались пустые значения. Не хотелось бы вертаться к интеграции через полгода, потому что оказалось, что надо сформировать отчётность, а в админке удалили какой-то товар и из-за этого все заказы ломаются. Вообще, словите все возможные исключения, возникающие при экспорте.

--

--