1С Общепит (миграция каталога + выгрузка чеков)
Источник требований
Интеграция нашей ERP с конфигурацией 1С:Предприятие 8 — Общепит на стороне партнёра/бухгалтерии. Мы заменяем у партнёра кассовое ПО (Аверс) и каталог; бухгалтер продолжает вести в 1С техкарты, складской учёт, производство и бухгалтерию. Между нашей ERP и 1С — двусторонняя связь через коды номенклатуры:
- Импорт каталога: из 1С → в нашу ERP (разовый при онбординге, повторяемый при изменениях).
- Выгрузка чеков: из нашей ERP → в 1С (Excel или прямой API — отдельной BR).
BR 1.17 — фундамент. Закладывает связь опций structural-мода с 1С-кодами. Выгрузка чеков — следующей BR.
Отличие от других интеграций
| Кейс | Сущность | Сервис-владелец |
|---|---|---|
| PayKeeper (платежи + фискализация) | Invoice flow, фискальные документы | Paykeeper Adapter |
| Агрегаторы доставки (Яндекс.Еда) | OAuth2 binding, обмен меню и статусов | Aggregator Service |
| 1С Общепит (учёт + бухгалтерия) | Маппинг номенклатуры, выгрузка продаж | Catalog Service (поля sku/sku_1c) + будущий Reports Service |
В отличие от PayKeeper/Aggregator, у 1С нет отдельного микросервиса-адаптера. Маппинг живёт прямо в каталоге (поля products.sku и modifier_options.sku_1c), потому что у нас нет realtime push/pull с 1С — обмен через Excel/файлы офлайн.
Архитектура связи каталога
В 1С каждая вариация = отдельная номенклатура с уникальным кодом. Нам нужно отразить эту связь в нашем каталоге.
Простые товары (без вариаций)
Пример: «Сосиска в тесте», «Булки», «Бамбл 400 мл».
products.sku = '1c:00000003541'— префикс1c:+ код номенклатуры 1С.- Поле
skuуже существует в схеме, не требует миграции. - При выгрузке чека:
sku.replace('1c:', '')→ код для 1С.
Виртуальные товары + structural-модификатор
Пример: «Кофе Капучино» как виртуальный продукт с опциями 250 / 300 / 400 мл.
products.sku = NULL(виртуальный, нет своего 1С-кода).- Привязан structural-мод с опциями. У каждой опции — своё поле
modifier_options.sku_1c(новое поле, BR 1.17). - Каждая опция = отдельная номенклатурная позиция в 1С с уникальным кодом.
Капучино (виртуальный, sku=NULL)
└─ structural «Объём»
├─ 250 мл (sku_1c=00000005351, цена 220 ₽)
├─ макси 300 мл (sku_1c=00000005477, цена 250 ₽)
└─ макси 400 мл (sku_1c=00000006202, цена 280 ₽)
Pattern «N по M» для многомерных кейсов
Если в 1С продукт имеет несколько измерений (Капучино × тип молока × объём), и каждая комбинация — отдельная номенклатура:
| 1С-код | Название |
|---|---|
| 001 | Кофе Капучино обычный 250 |
| 002 | Кофе Капучино обычный 300 |
| … | … |
| 009 | Кофе Капучино овсяный 400 |
Отражаем как N виртуальных продуктов с M structural-опциями каждый:
| Виртуальный продукт | Опции (sku_1c) |
|---|---|
| Капучино обычный | 250 → 001, 300 → 002, 400 → 003 |
| Капучино соевый | 250 → 004, 300 → 005, 400 → 006 |
| Капучино овсяный | 250 → 007, 300 → 008, 400 → 009 |
Ограничение «один structural на товар» (BUG-007) сохраняется. Настоящая матрица (product_combinations) — overkill, в BR 1.17 не реализуется. Может быть добавлена отдельной BR при конкретном запросе.
Импорт каталога из 1С
Скрипт-генератор: scripts/1c-import/import-1c-orp-grouped.py (в репо vault, не сервис).
Источники данных
| Файл | Откуда в 1С | Что даёт |
|---|---|---|
nomenklatura.xlsx | Справочник «Номенклатура» → правый клик → «Вывести список» → Excel | Имена позиций ↔ 1С-коды |
Пачка *.xlsx ОРП | Каждый «Отчёт о розничных продажах» по нужному складу/ТТ → табличная часть → Excel | Реальные позиции в продаже + фактические цены продаж |
Алгоритм
- Парсим номенклатуру — словарь
имя → код. - Парсим все ОРП — агрегируем позиции с модальной (самой частой) ценой. Разные цены под одним кодом игнорируются как акции/скидки.
- Группировка по «корню имени без размера»:
- Regex захватывает trailing размер:
\s+(макси\s+)?(\d+)\s*(мл|г|гр)\.?\s*$. - Корень = имя минус trailing размер.
- Regex захватывает trailing размер:
- ≥2 позиций с одним корнем → виртуальный продукт + structural-мод «Объём» с опциями.
- Одиночка → плоский продукт с
sku = '1c:КОД'. - Нормализация имён: CAPS → Title Case, удаление мусора (
порц.,покупной, двойные пробелы). - Автокатегоризация по regex-правилам (Кофе / Выпечка / Сэндвичи / Десерты / …).
- Генерация SQL для catalog_db + привязка price_list к store.
Идемпотентность
Скрипт чистит данные импорта по uuid-префиксу перед накатом. Повторный прогон даёт одинаковое состояние БД.
Что важно
- Имена позиций в нашей ERP = имена из 1С номенклатуры (после нормализации). Это снимает боль бухгалтера с расхождением имён между Аверс и 1С.
- 1С-коды в опциях — пока хранятся в
descriptionкак костыль1c:КОД | оригинальное имя 1с. После реализации BR 1.17 — миграция в явное полеsku_1c.
Текущие пилоты на test VPS
| Тенант | Подход импорта | Состояние | |
|---|---|---|---|
| Кофейня Цифра | cifra1@nirbi.ru | Плоский (из Кафе-цены) | 1452 позиции с историческими ценами 2020-2022. Для сравнения. |
| Кофейня Цифра v2 | cifra2@nirbi.ru | Сгруппированный (из ОРП) | 52 продукта (5 виртуальных с structural-модом + 47 плоских) с актуальными ценами 2026. Эталон. |
Оба изолированы UUID-префиксами (c1f0a000-... / c2f0a000-...). Не пересекаются.
Выгрузка чеков в 1С
Не предмет BR 1.17 — отдельная BR
Алгоритм описан ниже как готовый контракт. Реализация — будущей BR (предположительно «3.6 Excel-выгрузка продаж в 1С Общепит» или аналог).
Алгоритм разрешения 1С-позиции
def resolve_1c_position(order_item):
# Шаг 1: проверяем выбранную structural-опцию
structural_option = order_item.get_structural_option()
if structural_option and structural_option.sku_1c:
return structural_option.sku_1c
# Шаг 2: проверяем product.sku
if order_item.product.sku and order_item.product.sku.startswith('1c:'):
return order_item.product.sku[3:]
raise CannotMapTo1C(order_item.product.id)Формат выгрузки
Целевой формат — Excel под существующий процесс бухгалтера (загружает в 1С через типовую обработку «Загрузка из табличного документа»). Колонки соответствуют документу «Отчёт о розничных продажах» в 1С:Общепит:
| Колонка | Источник у нас |
|---|---|
| Номенклатура (имя) | По коду 1С → лукап в кэш номенклатуры |
| Код 1С | resolve_1c_position(order_item) |
| Единица | products.unit_of_measure |
| Количество | order_items.quantity |
| Цена | order_items.unit_price (фактическая, со скидкой) |
| Сумма | quantity × unit_price |
| % НДС | products.vat_rate |
Группировка — по дню + ТТ (на каждый день/ТТ один Excel = один ОРП в 1С).
Известные ограничения
-
Lookup vs Snapshot. При выгрузке исторических чеков используется текущее значение
sku_1c. Если бухгалтер перепривязал — выгрузка пойдёт по новому коду, могут быть несостыковки. Snapshot-подход (фиксация вorder_modifier_entries.sku_1c_snapshot) — отдельной BR. -
Двойные цены под одним 1С-кодом = скидки/акции в Аверсе (Шаурма 198 ↔ 242, Капучино 250 ↔ 310). В 1С нет понятия «акция» — там только финальная цена в чеке. Управление акциями в нашей ERP — отдельная BR. При импорте берём модальную (стандартную) цену.
-
Bidirectional sync. Не предмет интеграции. Бухгалтер изменил позицию в 1С → у нас в ERP не обновится автоматически. Решение — повторный прогон импорт-скрипта.
-
Конфликт с PayKeeper Catalog Sync (BR 3.4). Поле
products.skuмы используем с префиксом1c:. Если PaykeeperAdapter используетskuдля своего маппинга — будет конфликт. Проверить при реализации BR 1.17 (см.erp-paykeeper-adapter/.../CatalogEventConsumer.java). Если конфликт — мигрировать на отдельное полеproducts.sku_1cтогда. -
Множественные внешние системы (СБИС / 1С Розница / другие). Добавление новых
sku_*полей = анти-паттерн. При появлении 3-й системы — рефакторинг вexternal_idsтаблицу (отдельная архитектурная BR). -
Soft-delete опций. При удалении опции из админки нужно сохранять
deleted_at, чтобы исторические заказы могли резолвитьsku_1c. Часть BR 1.17.
Аверс — что мы заменяем
У партнёра сейчас работает связка:
- Аверс — кассовое ПО (российский аналог iiko)
- 1С:Общепит ред. 3.0 — учёт и бухгалтерия
Боль бухгалтера: имена в Аверс и 1С расходятся (одно и то же блюдо называется по-разному), приходится руками сверять.
После перехода на нашу ERP + PayKeeper:
- Кассовое ПО = наш POS Desktop.
- Имена в нашем POS = имена из 1С (через импорт-скрипт).
- При выгрузке чеков обратно в 1С — имена совпадают 1-в-1.
- Боль с расхождением имён исчезает.
Это главный value proposition перехода — наряду с заменой Аверса как ПО.
Связь с другими модулями
- Каталог — поле
skuдля простых товаров (с префиксом1c:). - Модификаторы — поле
sku_1cу опций structural-мода (BR 1.17). - Заказы — источник данных для выгрузки чеков (будущая BR).
- PayKeeper — параллельная интеграция, обе читают каталог.