Решение владельца 09.10: полный перенос магазина из МойСклада в ALL MARKET с историей. - scripts/migration/ms_inventory.py — read-only инвентаризация источника. - docs/migration-source-inventory.md: орг «FOOD MARKET», ~31k товаров, 627 контрагентов, ~110k документов (97 707 чеков, 7 125 приёмок, 3 068 списаний…), 7 558 строк остатков, 1 склад/точка/орг, касса наличных, 1 тип цены; gap-анализ модели + цели сверки. - docs/migration-design.md: идемпотентность по msId, хронология документов, МСК→UTC, тиыны, резюмируемость, порядок этапов, сверка, дельта, runbook. - docs/migration-progress.md: план Part 1–7. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6.1 KiB
6.1 KiB
Мигратор МойСклад → ALL MARKET — дизайн
Основано на инвентаризации (migration-source-inventory.md): ~31k товаров, 627 контрагентов,
~110k документов (97 707 чеков). Существующий импортёр (MoySkladImportService) уже делает
справочники product/productfolder/counterparty с идемпотентностью по MoySkladExternalId.
Принципы
- Идемпотентность по msId: у КАЖДОЙ перенесённой сущности хранить
MoySkladExternalId(уже есть у Product/ProductGroup — расширить на Counterparty/Store/RetailPoint/Employee/ UnitOfMeasure и ВСЕ документы + денежные операции). Повторный запуск — upsert по msId, без дублей (ключ — только id МойСклада; номера документов повторяются по годам). - Хронологический порядок документов (по
moment), чтобы остатки/себестоимость/деньги складывались корректно: справочники → смены → приёмки/оприходования/возвраты поставщику → продажи/розн.возвраты → списания → инвентаризации → внесения/выплаты/ордера/платежи. - Время:
momentМойСклада — МСК (UTC+3). Хранить в UTC = moment − 3ч. Фильтр дельтыupdated>=— тоже МСК. - Деньги: суммы МойСклада в ТИЫНАХ → ÷100. cashSum/noCashSum чека → нал/безнал оплата.
- Авторы/статусы/связи: owner(кассир)→Employee; applicable→проведён; retailsalesreturn.demand→ исходный чек (связь «на основании»); комментарии (description)→Notes.
- Резюмируемость: курсор по сущности (последний перенесённый msId/offset/updated) в
migration_state(или ImportJob.progress). Падение → продолжение с места. - Прогресс/ошибки: на каждую сущность — счётчики ok/skip/err + журнал ошибок (ImportJob).
Порядок этапов (Hangfire, по шагам, каждый резюмируемый)
- uom → UnitOfMeasure (59; маппинг кода на наши ОКЕИ, иначе по имени, иначе создать).
- pricetype → PriceType (1 «Цена продажи» = системный).
- productfolder → ProductGroup (есть); product (+barcodes) → Product (есть); 31k постранично.
- counterparty → Counterparty (есть; флаги поставщик/покупатель).
- store → Store; retailstore → RetailPoint (+ денежный счёт кассы, наличные).
- employee → Employee/User (3; для owner чеков).
- Документы в хронологии (батчами по msId-страницам, expand≤100): supply, purchasereturn, enter, loss, inventory, retailshift, retaildemand, retailsalesreturn, retaildrawercashin/out, cashin, paymentout. Каждая позиция: assortment→Product по msId, qty, price(тиыны), discount.
Производительность
- Чтение: лимит 45 req/3с, таймаут ≥120с, повтор страниц ≤4. 110k докумов страницами по 100 (+expand) ≈ тысячи запросов → десятки минут чтения; запись батчами в БД. Полный прогон — несколько часов, поэтому фоновый Hangfire с резюмируемым курсором, не блокирующий.
- Повторный запуск: upsert по msId, 0 дублей; измерять время в ImportJob.
Сверка (Part 4, авто → migration-reconciliation.md)
Цели (из источника, read-only):
- Остатки: по товар×склад
report/stock/all(7558 строк; нулевые НЕ отдаёт) == наши остатки. - Долги:
report/counterparty(взаиморасчёты) == наши долги поставщиков/покупателей. - Деньги: остаток кассы точки (чеки+внесения−выплаты−ордера) == МойСклад.
- Выручка/чеки по месяцам: Σ retaildemand.sum и count по месяцам == наши.
- Число документов каждого типа == таблица инвентаризации. Расхождения — разобрать построчно/объяснить.
Дельта (Part 5)
После полного переноса — Hangfire раз в N минут: filter=updated>=<последний_курсор МСК> по
каждому типу + удалённые (через /audit или сверку id-множеств) → upsert/удаление. В день
перехода — финальный прогон дельты, сверка, переключение кассы на ALL MARKET.
Runbook дня перехода (Part 7) — кратко
- Накануне: полный перенос + сверка = 0. 2. Утром: закрыть смену в МойСкладе, финальная дельта, сверка кассы/остатков. 3. Переключить POS на ALL MARKET-орг. 4. Проверить продажу/возврат/смену.
- Откат: вернуть POS на МойСклад (данные там не трогались). Детали — отдельно при реализации Part 5–6.
Статус
- Part 1 (инвентаризация + цели сверки + gap-анализ).
- Part 2–7: спроектированы; реализация + прогон (часы) — продолжение после перехода-инфраструктуры.