Интеграция сайта на Laravel с 1С — туториал-исповедь от оптимиста.
Решил написать статью о том, как скрестить пингвина с дельфином на русском, т.к. целевая аудитория для 1С в основном русскоязычная. Статья предназначена для PHP-разработчиков, которым предстоит реализовать интеграцию сайта на Laravel с продуктом компании 1С.
Из скиллов нужно — composer, Laravel, будет полезно уметь дебажить с помощью xdebug и PHPStorm.
Когда я услышал, что предстоит синхронизировать заказы в интернет-магазине с приложением “1С-Предприятие”, эмоции были смешанные. С одной стороны — это точно понадобится! С другой стороны, один только бренд “1С” вызывает у разработчика ноющую боль где-то в верхней части желудка.
Стоило взглянуть на документацию, и стало понятно, что “Протокол обмена”, как они его назвали, это просто какой-то франкенштейн в мире программных интерфейсов. Такое впечатление, что они внимательно почитали все существующие гайдлайны и конвенции, а потом сделали наоборот. Но, по крайней мере, создалось впечатление, что в этот раз обойдётся без кода кириллицей (я ошибался).
Но делать надо было. Так что не долго думая, я избрал путь самурая, то есть — делать. Лишь позже я прочитал, что по статистике от трети до половины попыток интеграции сайта с 1С заканчивается провалом.
TL;DR
composer require mavsan/laravel-1c-protocol
иcomposer require arsengoian/commerce-ml
- Выполнить всё, написанное тут.
- Прописать в конфиге
‘saleShareModel’ => OneCExportOrders::class
, реализовать эту модель. Она должна реализовать интерфейсMavsan\LaProtocol\Interfaces\ExportOrders
. - Создать адаптеры для классов заказа и товара, наследующие от
CommerceML\Constructors\Document
,CommerceML\Constructors\Product
- На выходе должен выйти файл, подобный этому.
- Настраиваем “Обмен данными с WEB-сайтом” в 1С и тестируем
Что это вообще и зачем?
Если вы, мой милый читатель, не из мира бухгатерии, финансов или управления, вам сказали делать интеграцию, а вы ещё не знаете, зачем это, экскурс в двух словах:
- Любой компании нужен учёт (зарплат, складов, заказов итп), будь то для внутренних потребностей (руководство хочет оценить успешность бизнеса) либо для внешних (надо заплатить налоги и сдать отчётность).
- Отчётность надо сдавать согласно местному законодательству (Например, в Украине — НПСБО), а значит 99.9% известных западных ERP-систем не годится, т.к. нет соответствующих модулей.
- Решение для большого бизнеса — допилить модуль самостоятельно. Решения для малого бизнеса — купить родную СНГ-шную систему, например (в большинстве случаев) — 1С.
- Для бизнесов, которые решились на п̶р̶о̶т̶и̶в̶о̶е̶с̶т̶е̶с̶т̶в̶е̶н̶н̶о̶е̶ сайт на Битриксе, интеграция идет “из коробки”.
Путь самурая
Сайт у нас не на Битриксе, а на 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 таких шага:
- mode=checkauth Начало сеанса — сайт получает в HTTP заголовке юзернейм и пароль и в ответ отправляет куки в теле HTTP ответа. Куки с одному Богу известной целью отправляются на каждом следующем шаге вместе с юзернеймом.
- mode=init Уточнение параметров сеанса — сайт должен отдать инфу о поддержке zip-сжатия и максимальном размере выгружаемого с 1С файла (человеческим языком, максимальный размер тела HTTP-запроса для импорта на сайт), так словно нельзя было это отдать в предыдущем запросе.
- mode=query “Полезный” запрос — мы возвращаем список заказов в формате CommerceML! Чтоо? А это ещё что? XML-схема, придуманная 1C для обмена с сайтом. С тегами на русском. И кодировкой Windows-1251. О ней поговорим позже.
- 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,
- Зарегистрировать пакет
Chumper/Zipper
, как указано в документации:
// сервис-провайдер:
/*
* работа с 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С, заходим в приложение. Радуемся тёплому и интуитивному интерфейсу.
Если настроек ещё не создавали, сразу откроется окно создания.
Обратите внимание, добавляем к пути параметр 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С или бухгалтером.
На следующем шагу настраиваем регулярность (не чаще 30 минут…) и… готово!
Можем сразу открыть нашу новую настройку для редактирования.
Если всё вышло, получим что-то такое:
Ура!
В случае, если в процессе будет выброшено исключение, если повезёт, весь мусор будет напечатан в том же логе, но я не стал бы на это рассчитывать =)
Подводные камни
Напоследок хотелось бы отметить следующее:
- Не забывайте, что 1С пишется с кирилличной “С”. Мне создали пользователя с названием 1СAdmin и я долго не мог залогиниться… То же касается названий некоторых папок. В общем, избегайте использования “1С” в названиях. Где-либо.
- Не забудьте, что нет поддержки HTTPS. В случае HTTPS — никакого сообщения об ошибке.
- По протоколу единственная верная кодировка — Windows-1251. Никакого юникода. В случае, если не та кодировка — никакого сообщения об ошибке.
- Обратите внимание на значение некоторых тегов. Для них есть фиксированные значения, описанные в документации CommerceML. Рекоммендую то, что выше в статье (“Значения в ComerceML”).
- Библиотеки тестировались в среде Windows 10 и CentOS.
- В случае, если вы настроили субдомен в папке рядом с public и IDE спрашивает, через какую папку настраивать, не перепутайте. Можно долго думать, почему дебаггер не включается.
- Проследите, чтобы либо всё, что касается экспорта удалялось с Soft Deletes, либо чтобы в случае отсутствия какого-то значения возвращались пустые значения. Не хотелось бы вертаться к интеграции через полгода, потому что оказалось, что надо сформировать отчётность, а в админке удалили какой-то товар и из-за этого все заказы ломаются. Вообще, словите все возможные исключения, возникающие при экспорте.