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Реальные позиции в продаже + фактические цены продаж

Алгоритм

  1. Парсим номенклатуру — словарь имя → код.
  2. Парсим все ОРП — агрегируем позиции с модальной (самой частой) ценой. Разные цены под одним кодом игнорируются как акции/скидки.
  3. Группировка по «корню имени без размера»:
    • Regex захватывает trailing размер: \s+(макси\s+)?(\d+)\s*(мл|г|гр)\.?\s*$.
    • Корень = имя минус trailing размер.
  4. ≥2 позиций с одним корнем → виртуальный продукт + structural-мод «Объём» с опциями.
  5. Одиночка → плоский продукт с sku = '1c:КОД'.
  6. Нормализация имён: CAPS → Title Case, удаление мусора (порц., покупной, двойные пробелы).
  7. Автокатегоризация по regex-правилам (Кофе / Выпечка / Сэндвичи / Десерты / …).
  8. Генерация SQL для catalog_db + привязка price_list к store.

Идемпотентность

Скрипт чистит данные импорта по uuid-префиксу перед накатом. Повторный прогон даёт одинаковое состояние БД.

Что важно

  • Имена позиций в нашей ERP = имена из 1С номенклатуры (после нормализации). Это снимает боль бухгалтера с расхождением имён между Аверс и 1С.
  • 1С-коды в опциях — пока хранятся в description как костыль 1c:КОД | оригинальное имя 1с. После реализации BR 1.17 — миграция в явное поле sku_1c.

Текущие пилоты на test VPS

ТенантEmailПодход импортаСостояние
Кофейня Цифраcifra1@nirbi.ruПлоский (из Кафе-цены)1452 позиции с историческими ценами 2020-2022. Для сравнения.
Кофейня Цифра v2cifra2@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С).


Известные ограничения

  1. Lookup vs Snapshot. При выгрузке исторических чеков используется текущее значение sku_1c. Если бухгалтер перепривязал — выгрузка пойдёт по новому коду, могут быть несостыковки. Snapshot-подход (фиксация в order_modifier_entries.sku_1c_snapshot) — отдельной BR.

  2. Двойные цены под одним 1С-кодом = скидки/акции в Аверсе (Шаурма 198 ↔ 242, Капучино 250 ↔ 310). В 1С нет понятия «акция» — там только финальная цена в чеке. Управление акциями в нашей ERP — отдельная BR. При импорте берём модальную (стандартную) цену.

  3. Bidirectional sync. Не предмет интеграции. Бухгалтер изменил позицию в 1С → у нас в ERP не обновится автоматически. Решение — повторный прогон импорт-скрипта.

  4. Конфликт с PayKeeper Catalog Sync (BR 3.4). Поле products.sku мы используем с префиксом 1c:. Если PaykeeperAdapter использует sku для своего маппинга — будет конфликт. Проверить при реализации BR 1.17 (см. erp-paykeeper-adapter/.../CatalogEventConsumer.java). Если конфликт — мигрировать на отдельное поле products.sku_1c тогда.

  5. Множественные внешние системы (СБИС / 1С Розница / другие). Добавление новых sku_* полей = анти-паттерн. При появлении 3-й системы — рефакторинг в external_ids таблицу (отдельная архитектурная BR).

  6. 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 — параллельная интеграция, обе читают каталог.

Ссылки

  • BR 1.17
  • BR 1.8 — базовая модель модификаторов
  • BR 1.9.2 — structural vs free
  • BR 1.10 — прейскуранты per-ТТ
  • scripts/1c-import/ — скрипты импорта в репозитории vault