Metadata-Version: 2.5
Name: onecdc
Version: 1.1.0
Summary: Change data capture (CDC) from 1C:Enterprise to your data warehouse
Project-URL: Homepage, https://github.com/pavel-v-sobolev/onecdc
Project-URL: Repository, https://github.com/pavel-v-sobolev/onecdc
Project-URL: Issues, https://github.com/pavel-v-sobolev/onecdc/issues
Author-email: Pavel Sobolev <pavel-v-sobolev@yandex.ru>
License-Expression: MIT
License-File: LICENSE
Keywords: 1c,1c-enterprise,cdc,data-engineering,dwh,etl,postgres,python
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: croniter<7,>=6.0.0
Requires-Dist: dbmerge<2,>=1.0.22
Requires-Dist: requests<3,>=2.33.0
Requires-Dist: sqlalchemy<3,>=2.0.49
Requires-Dist: xmltodict<2,>=1.0.4
Provides-Extra: dev
Requires-Dist: psycopg2-binary<3,>=2.9.12; extra == 'dev'
Requires-Dist: pytest-randomly>=3.15; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Provides-Extra: postgres
Requires-Dist: psycopg2-binary<3,>=2.9.12; extra == 'postgres'
Description-Content-Type: text/markdown

[![PyPI version](https://img.shields.io/pypi/v/onecdc.svg)](https://pypi.org/project/onecdc/)
![Status](https://img.shields.io/badge/status-stable-brightgreen)
[![Python versions](https://img.shields.io/pypi/pyversions/onecdc.svg)](https://pypi.org/project/onecdc/)


<p align="left">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/pavel-v-sobolev/onecdc/main/assets/logo_dark.png">
    <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/pavel-v-sobolev/onecdc/main/assets/logo_light.png">
    <img alt="OneCDC logo" src="https://raw.githubusercontent.com/pavel-v-sobolev/onecdc/main/assets/logo_light.png" width="500">
  </picture>
</p>

**OneCDC** is a Python library and Docker image for loading **1C:Enterprise** data into PostgreSQL using Change Data Capture (CDC). \
It uses the standard OData interface and exchange plans to read changes and update the target database.

**OneCDC** — Python-библиотека и Docker-образ для загрузки данных из **1С** в PostgreSQL по принципу CDC (Change Data Capture — получение изменений данных). \
Для чтения изменений используются стандартный интерфейс **OData** и механизм **планов обмена** 1С.

# Общий принцип действия
<p align="center">
  <img alt="Схема работы OneCDC" src="https://raw.githubusercontent.com/pavel-v-sobolev/onecdc/main/assets/scheme.png" width="600">
</p>

1. Основной класс библиотеки — `Replicator`. Он циклически читает изменения из 1С через OData и план обмена, записывает их в PostgreSQL и подтверждает приём пакета после сохранения поддерживаемых объектов.
2. Для каждого регистра, справочника, документа и каждой табличной части создаётся отдельная таблица. Структура следует метаданным OData, а имена таблиц и колонок транслитерируются.
3. К загруженным таблицам можно обращаться напрямую или строить над ними витрины. Обработчики (handlers) запускают ваш Python-код при изменении данных. Например, обработчик может обновлять витрину по представлению (view), пересчитывая только затронутые записи. Готовые примеры приведены ниже.


# Что нужно для работы

1. Опубликовать базу 1С на веб-сервере и открыть стандартный интерфейс OData.
2. Настроить план обмена в конфигураторе и включить в него нужные поддерживаемые объекты.
3. Создать пользователя OData с правами чтения загружаемых объектов, чтения и изменения плана обмена.
4. Дать пользователям, записывающим объекты, право чтения плана обмена, чтобы регистрация изменений не приводила к ошибкам.
5. Создать узел обмена с помощью внешней обработки [onecdc.epf](onecdc.epf) и включить план и его объекты в состав OData.
6. Запустить загрузку Docker-образом `sobolevp/onecdc` или Python-библиотекой `onecdc` — оба варианта описаны ниже.

# Использование библиотеки Python

## Установка
```bash
pip install "onecdc[postgres]"
```

Целевая СУБД — **PostgreSQL**. Для записи используется библиотека
[dbmerge](https://github.com/pavel-v-sobolev/dbmerge). OneCDC создаёт схемы, таблицы и колонки
автоматически; пользователю БД нужны соответствующие права. Требуется Python 3.10 или новее.

### Требования к ресурсам

**Ориентир для начала — 4 ГБ оперативной памяти на процесс.**

Потребление памяти зависит от размера пакета изменений. 1С формирует ответ `SelectChanges`
целиком в памяти рабочего процесса, игнорируя параметры `$top`/`$filter`. При обработке ответа
OneCDC одновременно хранит его тело, текст, дерево разбора и данные для записи. В одном из замеров 28 МБ ответа дали 102 МБ пиковых выделений памяти.
Учитывайте также параллельные полные выгрузки и обработчики: 4 ГБ не гарантируют достаточный запас.

Размер принимаемого ответа ограничен: по умолчанию **512 МиБ**, параметр
`Replicator(max_response_bytes=...)`. Ответ крупнее не принимается — соединение обрывается, пакет
не подтверждается. В журнал записывается `ERROR` с размером из заголовка или числом уже принятых байт.
Пакет остаётся в очереди 1С, поэтому повторный запрос даст тот же результат. Перед повтором цикл
увеличивает паузу; фоновые выгрузки и обработчики могут продолжать работу.
Чтобы уменьшить будущие пакеты, **настройте дроссель на стороне 1С**: он ограничивает число объектов, регистрируемых в очереди за раз (см. [Большие объёмы изменений и
распределение нагрузки](#большие-объёмы-изменений-и-распределение-нагрузки)).

`max_response_bytes=None` отключает ограничение размера ответа. Выбирайте это значение с учётом
доступной памяти и размера пакетов.


```python
from sqlalchemy import create_engine
from onecdc import Replicator

# Максимум соединений основного пула — pool_size + max_overflow. Задавайте оба параметра:
# неявный max_overflow=10 увеличит выбранный предел на десять соединений.
# См. README_DB.md, «Сколько нужно соединений к БД».
engine = create_engine("postgresql+psycopg2://user:pass@localhost:5432/onecdc",
                       pool_size=3,
                       max_overflow=2,          # запас для двух потоков полной выгрузки
                       pool_pre_ping=True)      # проверять соединение перед использованием

rep = Replicator(
    odata_url="http://host/base/odata/standard.odata",
    odata_auth=("odata", "secret"),        # (user, password); None — 1С без авторизации
    engine=engine,
    exchange_name="ВашПланОбмена",         # имя плана обмена в 1С
    queue_guid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",  # Ref_Key узла обмена
    db_schema="onecdc",                    # None → схема БД по умолчанию (public у Postgres)
    request_timeout=60,                    # таймаут HTTP-запросов к 1С, сек (по умолчанию 60 на коннект, 900 на ответ)
    full_load_workers=2,                   # число фоновых потоков полной выгрузки
    automatic_full_load=True,              # ставить новые объекты на полную выгрузку самому
    log_retention_days=30,                 # срок хранения журнала загрузок; 0 — хранить всё
)

rep.run_forever(interval=60)               # пауза 60 секунд между успешными циклами
```

План обмена и узел нужны только для чтения изменений. Если репликатор используется только для **полной
выгрузки** (разовой или по расписанию), эти параметры можно не задавать — `full_load` читает объект
прямо из OData; `run_forever`/`run_once` у такого репликатора откажутся стартовать.

### Примеры целиком

| файл | о чём |
|---|---|
| [examples/replicate_forever.py](examples/replicate_forever.py) | непрерывная репликация: один процесс, один цикл, без Docker и собственного пула потоков — то же, что выше, но готовое к запуску |
| [examples/full_load_once.py](examples/full_load_once.py) | разовая полная выгрузка одного объекта: первая загрузка, добор истории, пересборка после нового реквизита |
| [config/runner.py](config/runner.py) | многопоточный запуск: несколько обработчиков витрин, полные выгрузки по расписанию, несколько планов обмена в одном процессе |



## Запуск в Docker

```bash
docker run --rm --network host \
  -e ONECDC_ODATA_URL="http://server/base/odata/standard.odata" \
  -e ONECDC_ODATA_USER=odata -e ONECDC_ODATA_PASSWORD=secret \
  -e ONECDC_EXCHANGE_NAME="ВашПланОбмена" \
  -e ONECDC_QUEUE_GUID="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" \
  -e ONECDC_DB_URL="postgresql+psycopg2://user:pass@database_host:5432/cdc" \
  -e ONECDC_DB_SCHEMA=onecdc -e ONECDC_DB_TEMP_SCHEMA=onecdc_tmp \
  -v "$PWD/config:/config:ro" \
  sobolevp/onecdc:latest
```

`-v "$PWD/config:/config:ro"` — монтирует папку `config` из текущего каталога. В ней лежит файл [runner.py](https://github.com/pavel-v-sobolev/onecdc/blob/main/config/runner.py), а также подпапка с примерами обработчиков (config/handlers).

**docker compose.** Пример — [docker-compose.yml](https://github.com/pavel-v-sobolev/onecdc/blob/main/docker-compose.yml) в репозитории.


## Запуск из окружения

Для запуска из окружения Python используйте `python -m onecdc` или команду
`onecdc`. Параметры читаются из переменных окружения, описанных в [README_ENV.md](https://github.com/pavel-v-sobolev/onecdc/blob/main/README_ENV.md)



### Как узнать GUID узла обмена

`queue_guid` — это `Ref_Key` узла плана обмена, того самого, на который 1С регистрирует изменения
(`ЭтотУзел` не подходит: он описывает саму базу-источник).

Создать узел обмена и узнать его `Ref_Key` можно во внешней обработке
[onecdc.epf](https://github.com/pavel-v-sobolev/onecdc/blob/main/onecdc.epf).

Ещё один вариант: оставьте параметр пустым (`queue_guid=""`, или просто не задавайте
`ONECDC_QUEUE_GUID`) и запустите — чтение изменений выведет в лог список узлов плана обмена и
завершит цикл с ошибкой. В режиме `run_forever` попытки повторяются; после получения GUID остановите процесс и задайте узел.


```
ERROR onecdc.change_reader: queue_guid is not set. Available nodes of exchange plan ДляODATA:
    a9bc23c5-3689-11f1-926c-0800270bc6cb  CDC  Витрина
```

GUID из первой колонки и есть искомый `queue_guid`.

## Режимы: `run_once` и `run_forever`

```python
rep.run_once()                 # один цикл: read → save → notify (подтверждение только после save)
rep.run_forever(interval=60)   # основной режим: непрерывный цикл run_once с паузой; фоном — полные выгрузки
```

- `run_once(notify_changes=False)` — не подтверждать приём (пакет останется в очереди 1С; сделано для отладки).
- `run_forever(interval, max_iterations=0)` — `max_iterations>0` ограничивает число итераций.


## Полная (первоначальная) выгрузка

При работе `run_forever` объекты, впервые встреченные в пакете изменений, автоматически ставятся в
очередь на полную выгрузку и грузятся фоновыми потоками. Управляет этим параметр
`automatic_full_load` (по умолчанию `True`).
- Чтобы назначить выгрузку вручную, установите `full_load_is_required=true` в таблице `onecdc_metadata_objects`. Фоновые потоки подхватят объект на следующем цикле — в том числе при `automatic_full_load=False`.
- Полная выгрузка спроектирована так, чтобы работать параллельно с получением изменений объекта и не затирать свежие изменения объекта. При наличии подходящего поля даты большой объект разбивается на периоды и выгружается порциями, размер которых подбирается автоматически (batch_size).
- Есть возможность инициировать полную выгрузку и на стороне 1С — обработкой `onecdc.epf` зарегистрировать все изменения объекта в узле обмена. Но тогда объект поступит одним пакетом `SelectChanges`, а его платформа целиком держит в памяти рабочего процесса, поэтому так рекомендуется делать только вместе с «дросселем» (см. [Большие объёмы изменений и распределение нагрузки](#большие-объёмы-изменений-и-распределение-нагрузки)). В этом случае поставьте `automatic_full_load=False`, иначе репликатор запросит ту же выгрузку второй раз.


## Какие объекты 1С поддерживаются

В план обмена можно включать:

| Класс объекта 1С | |
|---|---|
| справочники | вместе с табличными частями |
| документы | вместе с табличными частями |
| регистры сведений | подчинённые регистратору |
| регистры накопления | |
| регистры бухгалтерии | без субконто, см. ниже |

**Всё остальное не поддерживается** — константы, регистры расчёта, планы видов характеристик,
планы счетов, планы видов расчёта, бизнес-процессы, задачи. Не включайте их в план обмена: их
изменения будут потеряны безвозвратно, потому что пакет подтверждается целиком и 1С их больше не
пришлёт. В логе это видно как `ERROR` со словами `CHANGES LOST`.

**Независимые регистры сведений** (без регистратора) `SelectChanges` не возвращает. Для них
доступна только полная выгрузка, в том числе по расписанию.

**Субконто регистра бухгалтерии не выгружаются.** Движения регистра приезжают полностью, но без
аналитики: в описании движения субконто нет, а единственный их источник — виртуальная таблица
`RecordsWithExtDimensions`, которую нельзя листать. Её нельзя адресовать ни по регистратору
(проверено на живой 1С: guid'ом — ошибка 500, строкой — пустой ответ при существующем
регистраторе), ни страницами (`$skip` отвергается, `Top` отдаёт произвольное подмножество).
Остаётся перечисление периодов, а это не масштабируется: один плотный период — один неразрывный
запрос на все его строки во всей базе.

Те же аналитические разрезы обычно проще взять из документа-регистратора. Если субконто нужны
именно из регистра — их придётся добирать своими силами на стороне 1С.

Если вы грузили субконто прежними версиями, колонки `ExtDimensionsDr` и `ExtDimensionsCr`
останутся в таблице со старыми значениями: библиотека колонки не удаляет и больше их не обновляет.
Удалять или нет — решать вам.


## Что появляется в целевой БД

На каждый объект 1С заводится своя таблица: имя транслитерируется, к полям добавляются служебные
(`merged_on`, `inserted_on`, `is_deleted_or_empty`, `exchange_message_no`). Схемы, таблицы и новые
колонки библиотека создаёт сама. Рядом появляются служебные таблицы, по которым видно состояние
загрузки: журнал `onecdc_replicator_log`, реестр объектов `onecdc_metadata_objects`, состояние обработчиков
`onecdc_handlers`, реестр незавершённых записей `onecdc_writes_in_process`, реестр имён
таблиц и колонок `onecdc_name_claims` и реестр читателей узлов обмена `onecdc_exchange_nodes`.

Журнал загрузок растёт быстро — строка на объект на пакет, то есть миллионы строк в год, — поэтому
у него есть срок хранения: `log_retention_days` (по умолчанию 30 суток, `0` — хранить всё). Уборка
идёт сама, раз в сутки на процесс.

Подробно — [README_DB.md](https://github.com/pavel-v-sobolev/onecdc/blob/main/README_DB.md): состав полей, что означает `is_deleted_or_empty` и что лежит в каждой служебной таблице.

## Если в 1С поменяли тип поля

При смене типа поля в 1С библиотека сохраняет прежние данные под другим именем и создаёт
новую колонку или таблицу. Это позволяет избежать неоднозначного преобразования, например
из произвольной строки в ссылку.

| что поменялось | что произойдёт |
|---|---|
| обычное поле | колонка переименуется в `Поле_Old_<хэш>`, рядом появится новая, нужного типа |
| поле, входящее в ключ | переименуется **вся таблица** — в `Таблица_Old_<хэш>`, рядом появится новая |

Затем объекту **назначается полная выгрузка**, чтобы загрузить исторические данные в новую колонку или таблицу. Если автоматические
выгрузки выключены (`automatic_full_load=False`), выгрузка не назначается — новая колонка или таблица
будет наполняться только текущими изменениями, без истории; запустите `full_load` сами, когда
удобно.

Если тип колонки задали вы сами (например сузили `varchar` до `varchar(50)` или указали точность
`numeric`), это сменой типа не считается и колонку библиотека не тронет.

## Классы библиотеки

`Replicator` читает изменения и записывает их в БД. `Handler` и `HandlerLoop` запускают ваш код
при изменении данных, а `FullLoadCron` выполняет полную выгрузку по расписанию.
Параметры и методы этих классов описаны в [README_API.md](https://github.com/pavel-v-sobolev/onecdc/blob/main/README_API.md).

## Логирование

Библиотека использует стандартный модуль `logging`. Если приложение ещё не настроило вывод,
OneCDC добавляет обработчик к логгеру `onecdc` с уровнем `INFO`.

## Шум со стороны 1С

1С регистрирует изменения при каждой записи объекта, даже если его реквизиты остались прежними. Изменение только `DataVersion` и номера пакета обычно не вызывает обновления строки: `merged_on` остаётся прежним, и обработчики не пересчитывают те же данные. Исключение — объект под полной выгрузкой: в это время отметка обновляется при каждом пакете, чтобы защитить свежие данные от записи устаревшей страницы.


## Большие объёмы изменений и распределение нагрузки

Если изменений много и один репликатор не справляется, нагрузку можно распределить тремя способами:

1. Разные объекты 1С — в разные планы обмена. Каждый план читает свой репликатор; репликаторы можно
   развести по машинам или запустить в одном многопоточном скрипте.
2. Один объект — через «дроссель»: он дозирует изменения, чтобы пакет в SelectChanges не разрастался (см. ниже).
3. Тот же дроссель, но с несколькими планами-отдачами: один объект читают несколько репликаторов
   параллельно.

<p align="center">
  <img alt="Способы распределения нагрузки OneCDC" src="https://raw.githubusercontent.com/pavel-v-sobolev/onecdc/main/assets/modes.png" width="600">
</p>

Метод `SelectChanges` в OData, к сожалению, не умеет отдавать изменения постранично: платформа
формирует весь пакет в памяти рабочего процесса, а `$top` / `$select` / `$filter` игнорирует.
Ограничить размер пакета со стороны клиента нечем — это делается на стороне 1С, регистрацией
изменений. Готовый общий модуль («дроссель») лежит в репозитории:
[onecdc-throttle-module.bsl](https://github.com/pavel-v-sobolev/onecdc/blob/main/onecdc-throttle-module.bsl)

Репликатор читает `SelectChanges` из отдачи, поэтому число регистраций в пакете ограничивается настройками дросселя. Размер в байтах зависит от самих объектов.
Планов-отдач может быть несколько: они наполняются по порядку. Следующий план получает изменения,
когда предыдущий достиг своего лимита. На каждую отдачу поднимается свой `Replicator` — так получение
изменений идёт в несколько потоков.

Настройка модуля «Дроссель»:

- Настроить план обмена **«Накопитель»** — с авторегистрацией изменений ваших объектов.
- Настроить один или несколько планов обмена **«Отдача»** — с отключённой авторегистрацией:
  единственный, кто в них регистрирует, — дроссель.
- Создать в конфигураторе общий модуль (раздел «Общие модули») и вставить в него код
  [onecdc-throttle-module.bsl](https://github.com/pavel-v-sobolev/onecdc/blob/main/onecdc-throttle-module.bsl).
- В процедуре `ЗапуститьПеренос1()` указать имя плана-накопителя и имена одного или нескольких
  планов-отдач. Саму процедуру можно назвать по своему усмотрению.
- Создать регламентное задание, которое её вызывает: оно и будет периодически переносить изменения
  из накопителя в отдачи.

Разбор устройства дросселя — в
[DESIGN.md](https://github.com/pavel-v-sobolev/onecdc/blob/main/DESIGN.md#размер-пакета-со-стороны-клиента-его-ограничить-нечем).

## Сколько процессов запускать

**Один узел обмена читает один репликатор, один обработчик исполняет один процесс.** Это и есть
схема запуска: параллельное чтение одного узла удваивает нагрузку на 1С и может привести к потере
изменений, а параллельный расчёт одной витрины — к замене актуального результата устаревшим.

Это правило поддерживается арендой в БД. Перед работой процесс **захватывает** узел
обмена и имя обработчика, а пока жив, подтверждает захват отметкой живости. Поэтому:

- **второй контейнер с той же целевой схемой** ожидает освобождения узла и имён обработчиков;
- **после аварийной остановки первого процесса второй сможет продолжить работу** после истечения 15 минут с последней отметки (отметка живости перестала
  обновляться). При штатной остановке (`SIGTERM`) захват освобождается при завершении работы;
- **полные выгрузки при этом делятся**: объекты захватываются поштучно, так что «лишний» процесс
  может обрабатывать очередь полных выгрузок вместе с первым. Ограничения аренды описаны в [DESIGN.md](DESIGN.md#аренда-истечение-отметки-доказывает-молчание-а-не-смерть).

## Дальнейшая обработка и материализация

1С хранит данные нормализованно: чтобы получить код товара по записи регистра, нужен `JOIN` со
справочником по GUID. Для корпоративного хранилища данных (КХД) обычно удобнее менее строгая нормализация — витрины, собранные
заранее.

Витрины можно обновлять по мере изменения данных: репликатор передаёт сигнал, по которому
цикл обработчика вызывает ваш код. Такой код называется **обработчиком** — это класс с подпиской на таблицы и методом `handle`:

```python
from onecdc import Handler, HandlerContext

class ZakazyKlientov(Handler):
    ON = ["AccumulationRegister_ZakazyKlientov"]   # имена ТАБЛИЦ в БД, не объектов 1С

    def handle(self, context: HandlerContext) -> None:
        self.execute(context, "insert into ... where merged_on > :last_run_at",
                     last_run_at=context.last_run_at)
```

Тем же способом изменения отправляют во внешнюю систему — очередь, вебхук, другую БД.
Обработчик-отправщик сделайте устойчивым к повторной отправке: передавайте ключ и
пропускайте на приёмнике то, что уже приходило — после сбоя одно и то же окно изменений может быть обработано повторно.

Оптимально начать с **представления (view)**: в нём собран `SELECT` со всеми `JOIN` по GUID-ключам.
DDL можно прописать прямо в обработчике (`setup`) или завести отдельно. Дальше обработчик работает так:

- библиотека вызывает его, передавая отметку `last_run_at` — нижнюю границу окна изменений;
- обработчик пишет `SELECT`, который находит по представлению изменившиеся ключи условием
  `merged_on > last_run_at` (`merged_on` есть и у таблиц, подключённых через `JOIN`, — см. пример);
- этот `SELECT` уходит в условия обновления
  [dbmerge](https://github.com/pavel-v-sobolev/dbmerge): `source_condition` говорит, какие ключи
  взять из источника, `delete_condition` — в каком множестве ключей чистить строки, если что-то
  удалилось. `delete_condition` нужен не всегда: строки, выпавшие из набора движений или
  табличной части, репликатор не удаляет, а помечает флагом `is_deleted_or_empty` (см.
  [README_DB.md](https://github.com/pavel-v-sobolev/onecdc/blob/main/README_DB.md)), поэтому
  витрине с тем же ключом, что у источника, достаточно обычного обновления. Удаление
  нужно там, где ключ витрины свой, — например, у агрегата по `GROUP BY`.

В примерах разобраны два варианта — с кодом и подробными пояснениями:

1) ключ витрины совпадает с первичным ключом объекта 1С (GUID или составной ключ набора записей). Годится для таблицы
   фактов: [config/handlers/zakazy_klientov.py](https://github.com/pavel-v-sobolev/onecdc/blob/main/config/handlers/zakazy_klientov.py);
2) ключ витрины заменён на бизнес-ключ — например, номер документа вместо guid. Связь с «сырыми»
   данными хранится в колонках-массивах (`ARRAY`) с индексами *GIN*: по ним обработчик быстро
   находит, какие группы затронуло изменение в источнике.
   [config/handlers/zakazy_klientov_grouped.py](https://github.com/pavel-v-sobolev/onecdc/blob/main/config/handlers/zakazy_klientov_grouped.py).


---

## Ссылки

| файл | описание |
|---|---|
| [README.md](https://github.com/pavel-v-sobolev/onecdc/blob/main/README.md) | этот файл: обзор, установка, запуск |
| [README_ENV.md](https://github.com/pavel-v-sobolev/onecdc/blob/main/README_ENV.md) | запуск из окружения: все переменные `ONECDC_*`, значения по умолчанию, ошибки конфигурации |
| [README_API.md](https://github.com/pavel-v-sobolev/onecdc/blob/main/README_API.md) | классы, которые собирает пользователь: `Replicator`, `Handler`, `HandlerLoop`, `FullLoadCron`; обработчики — окно изменений, когда их вызывают, пересборка витрины |
| [README_DB.md](https://github.com/pavel-v-sobolev/onecdc/blob/main/README_DB.md) | что появляется в целевой БД: таблицы, служебные поля, служебные таблицы |
| [DESIGN.md](https://github.com/pavel-v-sobolev/onecdc/blob/main/DESIGN.md) | внутреннее устройство для тех, кто правит код: интерфейс OData, цикл изменений, пагинация полной выгрузки, гонки со снимком, механика обработчиков |
| [CHANGELOG.md](https://github.com/pavel-v-sobolev/onecdc/blob/main/CHANGELOG.md) | что менялось от версии к версии |
| [config/runner.py](https://github.com/pavel-v-sobolev/onecdc/blob/main/config/runner.py) | шаблон точки входа: репликатор, обработчики и расписания в одном файле |
| [config/handlers](https://github.com/pavel-v-sobolev/onecdc/tree/main/config/handlers) | примеры обработчиков |


---

<sub>Неофициальный проект, не связан с фирмой «1С». «1С» и «1С:Предприятие» — товарные знаки ООО «1С», используются для указания совместимости.</sub>
