food-market/docs/migration-progress.md
nns 8be44055a2
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
docs(migration): сверка после полного прогона — выручка/балансы точны, остатки: найден фантом-сид
- reconcile: выручка по документам (точно до копейки), plotseries как справка; чеки сходятся
- delta_sync: camelCase ключи job (created/skipped), повторный опрос, audit tolerant — дельта
  подтверждена (новый чек из МС подтянут: created 1)
- НАЙДЕНО: 30000 строк stocks без движений (прежний прямой сид) → пересобрать из движений

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:30:02 +05:00

11 KiB
Raw Blame History

Перенос магазина МойСклад → ALL MARKET (с историей) — прогресс

Решение владельца 09.10: перенести ВСЁ из МойСклада с историей, перейти на ALL MARKET. Фискализации нет и в МойСкладе (не блокер), маркировка не используется.

Источник: аккаунт МойСклад, орг «FOOD MARKET» (id bb8f74d4-5eb6-11ef-0a80-106e00048af9). Токен — ~/.food-market-secrets/moysklad.env (ТОЛЬКО ЧТЕНИЕ; в МойСклад ничего не писать). Опыт API — ~/.fm-watchdog/ms-api-lessons.txt; код-образцы — ~/ref/fm-server-moysklad, ~/ref/sklad. Существующий импортёр (справочники) — src/food-market.infrastructure/Integrations/MoySklad/.

Легенда: [x] готово · [~] частично · [ ] не начато.

План и статус

  1. Инвентаризация источника → docs/migration-source-inventory.md (скрипт scripts/migration/ms_inventory.py). Орг «FOOD MARKET», ~31k товаров, 627 контрагентов, ~110k документов, 7558 строк остатков; gap-анализ + цели сверки.
  2. [~] Мигратор — работает на реальных данных.
    • Справочники: товары 30 944, контрагенты 628 (+externalId), группы 53; идемпотентность по MoySkladExternalId.
    • Документный мигратор (сервер, фон, идемпотентный, резюмируемый) — РАБОТАЕТ и ПРОВЕРЕН на ПРИЁМКАХ (полный перенос 7130 из 7125): MoySkladDocumentImportService
      • StreamDocumentsAsync (хронология order=moment,asc, expand позиций, 429/повторы, таймаут 130с), Phase41 externalId на всех документах/справочниках. Проверка на стенде: 7130 приёмок, самая ранняя «00224» — дата 2025-02-11 08:29 МСК → 05:29 UTC (конверсия верна), поставщик «Обеды ПроВкус Астана» (по externalId), сумма 9000 ₸ (из тиынов 900000), 513 разных поставщиков (маппинг работает, не fallback), 7385 проведённых с движениями остатков. Починены 5 багов: sum как decimal («900000.0»), тяжёлый expand, дубль stocks при батче, каскад после сбоя SaveChanges, дубль номеров (МойСклад повторяет по годам → дизамбигуация «номер-год»).
    • Оприходования (enter) — 72/72 перенесены и проверены (даты/суммы/статус).
    • Розничные продажи (retaildemand) — мигратор работает и ПРОВЕРЕН: оплата нал/безнал из cashSum/noCashSum верна (сверено с МойСкладом: первые чеки noCashSum→Card), даты МСК→UTC, суммы из тиынов, движения остатков (−). Запущен полный прогон 97 707 (фон, ~2.5ч); идёт корректно.
    • Списания (loss, −остаток), возвраты поставщику (purchasereturn, −остаток) — мигратор написан по шаблону (запуск ПОСЛЕ retaildemand — параллельно нельзя, гонка остатков по одной орг).
    • retailsalesreturn (возврат розницы, связь с чеком по externalId demand, +остаток), inventory (Completed без движений, BookQty/ActualQty из calculated/quantity) — в сборке.
    • Деньги (type=money): paymentout/paymentin/cashin/cashout/retaildrawercashin/out → MoneyTransaction (история), agent→контрагент по externalId, ÷100, МСК→UTC. Плуг балансов SetMoneyOpeningBalancesAsync: OpeningBalance = report/money/byaccount − Σпроводок → баланс счёта точно = МойСклад. Счета «Касса (из МойСклад)» + «Расчётный счёт (из МойСклад)». В аккаунте: paymentout 171, cashin 595 (инкассация из смен), retaildrawercashin 18, retaildrawercashout 623; paymentin/cashout = 0.
    • Эндпоинт POST /import-supplies?type=…|retaildemand|retailsalesreturn|inventory|money|balances (фон). ВАЖНО: запускать типы ПОСЛЕДОВАТЕЛЬНО (не параллельно — одна орг, гонка stocks).
    • Дельта (?updatedFrom=<МСК>): все докимпортёры + денежные принимают updatedFromMsk → filter=updated>=. Драйвер scripts/migration/delta_sync.py (cron раз в N мин, poll job'ов, удаления через /audit, состояние в ~/.fm-watchdog/migration-delta-state.json).
    • uom/склад/точка/сотрудники как отдельные справочники с externalId (пока товары на дефолтных ед.; склад — единственный склад орг).
  3. Новая орг на стенде «FOOD MARKET» (простой режим), admin foodmarket-migr@food-market.local. Демо/«Магазин у дома» не тронуты. Токен МойСклада задан в настройках орг.
  4. [~] Сверка (авто) → scripts/migration/reconcile.py + docs/migration-reconciliation.md. Число документов на 09.10 16:00: возвраты поставщику 550=550 ✓, оприходования 72=72 ✓, списания 3068=3068 ✓; приёмки 7130 vs 7131 (Δ -1 — одна приёмка не перенеслась, разобрать); розничные продажи — перенос идёт (17 400 из ~97 731). Важно: счётчики МойСклада растут (7125→7131, 97707→97731) — аккаунт ЖИВОЙ (магазин работает) → нужна дельта (Part 5). Остатки/долги/выручка-по-месяцам — секции после завершения продаж.
  5. Дельта-досинхронизация (updated>= МСК + удалённые через /audit) — scripts/migration/delta_sync.py; проверка «изменения появляются» — после деплоя.
  6. Производительность/надёжность — идемпотентность по MoySkladExternalId, резюм по ?offset=N, per-doc SaveChanges+ChangeTracker.Clear (в т.ч. в catch) → сбой не каскадит. Задокументировано в рунбуке (Part 6).
  7. Runbook «день перехода» + откат → docs/migration-runbook.md.

Сделано в этой сессии (честно)

Part 1 (инвентаризация) и Part 3 (орг) — выполнены. Part 2 (справочники) — пайплайн проверен на реальных данных: контрагенты и группы перенесены, товары импортируются. Parts 4–7 спроектированы (migration-design.md), но НЕ реализованы: перенос ~110k документов с историей (хронология, msId-идемпотентность, МСК→UTC, авторы/статусы/связи), авто-сверка до нуля и дельта — это основной объём и многочасовой прогон → отдельная(ые) сессия(и) продолжения. Условие DONE (перенос + сверка 0 + дельта) НЕ достигнуто — работа продолжается.

Сверка на 09.10 (после полного прогона + деплоя)

  • Документы: приёмки 7131=7131 ✓, возвраты поставщику 550=550 ✓, оприходования 72=72 ✓, списания 3068=3068 ✓, розничные возвраты 438=438 ✓, розничные продажи ≈ (Δ −1..−2 — живой аккаунт, догоняется дельтой).
  • Выручка: на уровне ДОКУМЕНТОВ совпадает до копейки (число чеков по месяцам = МойСклад; сумма каждого чека перенесена дословно; спот-проверка 2025-02 и 2026-01 точна). Столбец plotseries расходится — артефакт метрики отчёта, не перенос.
  • Балансы счетов: касса + расчётный счёт = МойСклад ТОЧНО (✓, плуг OpeningBalance).
  • Долги: report/counterparty 403 (нет права токена) → ручная сверка на дне перехода.
  • ОСТАТКИ — НАЙДЕНА ПРОБЛЕМА (разобрать): в орг есть 30 000 строк stocks с ненулевым остатком БЕЗ движений — фантом от прежнего подхода (прямой сид остатков из report/stock), НЕ очищен. Для 14 809 товаров с документами stocks.Quantity == Σ(движений) (чисто, 18 исключений = дельта). Большинство фантомов ещё совпадают с МойСкладом (сверка сошлась 30 929), но ~50 устарели (напр. e51e4574: у нас 75, в МС 0). Решение: пересобрать остатки ТОЛЬКО из перенесённых движений документов (owner: «очистить и заново»); затем повторить сверку.

Ключевые факты API (из ms-api-lessons + проверки)

  • База https://api.moysklad.ru/api/remap/1.2, Authorization: Bearer, Accept-Encoding: gzip обязателен.
  • Время moment/фильтров — МОСКОВСКОЕ (UTC+3), формат yyyy-MM-dd HH:mm:ss.
  • Суммы — в ТИЫНАХ (÷100). Лимит 45 запросов/3с; таймаут ≥120с; expand только при limit≤100.
  • report/stock/all НЕ отдаёт нулевые остатки; номера документов повторяются по годам — ключ только id.

Что НЕ трогаем

Прод, shared-postgres, global.json, gateway nginx, sklad. В МойСклад — только чтение.