Intégration 1C : import catalogue et tunnel Cloudflare

Guide d'intégration 1C : réception du catalogue via POST /api/catalog/1c-import, tunnel Cloudflare sortant depuis le terminal, tests Postman et données simulées.

Spar Skills Guide Bot
DeveloppementAvancé
0018/09/2026
Claude Code
#1c#api-integration#cloudflare-tunnel#fastapi#catalog-sync

Recommandé pour


name: 1c-exchange description: Обмен с 1С — приём каталога POST /api/catalog/1c-import под неизменную обработку заказчика, туннель Cloudflare с прибора, проверка через Postman и из 1С, мок рабочих данных на время теста. Использовать при правке import_1c.py, схем Import1C*, туннеля и при вопросах «1С не видит весы».

Обмен с 1С

Контракт диктует обработка

integrations/1c/original-Libra.bsl — внешняя обработка 1С 8.2, уже работает у заказчика и не правится без явной просьбы владельца. Наш приёмник backend/app/api/import_1c.py подстроен под неё, а не наоборот:

| Обработка шлёт | Приёмник | | --- | --- | | заголовок X-API-Key | основной способ; Authorization: Bearer оставлен для curl. Пробелы по краям токена срезаются — его копируют руками | | article (числовой артикул) | это и есть plu: в схеме validation_alias=AliasChoices("plu","article"), поле необязательное | | позиции без артикула / длиннее 5 цифр | не 422 на весь пакет, а пропуск с записью в errors[] (plu: 0, name) — 1С печатает их в своём логе | | GET /health («Проверить связь») | явный маршрут в main.py вместе с /healthz; без него SPA-заглушка отвечала 200 при любом состоянии | | in_stock всегда true | отбор «только в наличии» делает 1С; чего нет в пакете — гасится (active=0) | | replace_images | снимок существующему товару кладётся только с ним; новым — всегда |

На время записи киоск закрыт экраном «Оновлення»: бэкенд шлёт live.notify «catalog_busy» до записи и «catalog» после — в finally, поэтому сорвавшаяся выгрузка не оставит прибор за экраном ожидания (плюс минутная страховка в киоске). Без этого покупатель успевал нажать карточку посреди выгрузки и получить этикетку со старой ценой. После снятия экрана киоск возвращается к началу каталога, но не тогда, когда взвешенное ещё не забрали.

Полная синхронизация опасна при сбое выгрузки: пустой пакет и пакет без единого пригодного артикула отклоняются (400) до того, как что-то погаснет. После приёма — live.notify("catalog"). Поля настройки SMK_HTTP_Настройки и формат тела — integrations/1c/README.md; менять контракт можно только с обеих сторон разом.

Как 1С достаёт до прибора

Сервер 1С доступен только по RDP, приборы — за NAT провайдера без белого IP (проверено: WAN роутера — приватный 192.168.12.x, проброс снаружи не отвечает). Поэтому прибор сам держит исходящий туннель Cloudflare (сервис tunnel в deploy/docker/compose.yml, скилл deployment), а 1С ходит на имя vesy-N.<домен>, SSL = Истина. Tailscale отпал: на сервер 1С клиента ставить нельзя. Если 8.2 не договорится по TLS — SSL = Ложь через порт 80 работает (Cloudflare принимает HTTP), но у домена должно быть выключено «Always Use HTTPS».

Постоянный адрес

Домен smk-retail.com в аккаунте Cloudflare владельца. Туннель vesy-dev (ID ef35cc5d-…) с маршрутом vesy-dev.smk-retail.comhttp://127.0.0.1:8000 — это машина разработчика: cloudflared tunnel run --token <из backend/.env, CLOUDFLARE_TUNNEL_TOKEN>. Токен надстройка Claude в Chrome не отдаёт (и правильно) — его копирует владелец руками. Снаружи проверено: /health 200 из Кипра и Германии, /admin 401 без пароля и 200 с ним, приём 1С отвечает своим 401/400. HTTP без TLS через туннель тоже проходит — запасной путь для 1С 8.2 без OpenSSL.

Проверка на машине разработчика

  1. Бэкенд слушает 0.0.0.0 (tools/dev.py так и запускает); в backend/.env задан S2L_IMPORT_TOKEN.
  2. Быстрый туннель без домена: cloudflared tunnel --no-autoupdate --url http://127.0.0.1:8000 печатает имя *.trycloudflare.com; живёт, пока идёт команда, меняется при каждом старте. Убедиться снаружи: curl https://<имя>/health и, если есть сомнения в сети, check-host.net (TCP/DNS с чужих узлов).
  3. Postman: вкладка Authorization → No Auth, заголовок X-API-Key во вкладке Headers. Первая ловушка теста: в Authorization стоял Bearer с другим значением — 401, хотя curl с тем же токеном проходил.
  4. Пакет — ровно как у обработки (article, не plu), плюс позиция без артикула и с шестизначным — проверить errors[]. Отрицательные: без ключа 401, пустой пакет 400.
  5. Из 1С: сначала «Проверить связь» (то же поле Сервер, без тела), потом «Заполнить» → «ВыгрузитьВВесы». Couldn't resolve host name — в поле Сервер лишний https://, путь или пробел, либо DNS на RDP-сервере не отдаёт trycloudflare.com (nslookup там же). Именованный туннель со своим доменом такие фильтры не трогают.

Результат первого прогона 2026-09-15: 415 позиций, 290 со снимками, 4 группы — киоск перерисовался сам.

Мок рабочих данных

Выгрузка гасит всё, чего нет в пакете, поэтому демо-каталог на время теста уезжает в backend/data/mock/ (гитигнор):

cd backend && .venv/Scripts/python tools/mock_data.py stash     # база в мок
cd backend && .venv/Scripts/python tools/mock_data.py restore   # вернуть

Снимки мок не трогает: присланные из 1С лежат в backend/data/photos/ и перекрывают демо-набор из сборки по коду товара (скилл product-photos).

Оба — при остановленном бэкенде: SQLite занят, на Windows файл не переименовать. S2L_SEED_DEMO=0 в backend/.env, иначе пустая база на старте снова засеется демо. Ловушка Windows: после убийства родителя uvicorn --reload дочерние воркеры (--multiprocessing-fork в командной строке) живут дальше и держат базу — добивать их отдельно.

Проверять приём одной позицией через curl нельзя: это полная синхронизация, и такой запрос погасил 415 боевых товаров — восстанавливать пришлось повторной отправкой всего каталога из базы. Тестовую позицию слать только внутри полного пакета или на пустой базе.

Skills similaires