food-market/docs/migration-progress.md
nns 18578e2f0c
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
fix(migration): сверка остатков — скоуп по орг (Демо тоже засеян из МС) + классификация 42 расхождений
- reconcile: ORG-фильтр во всех SQL (остатки/выручка/балансы/долги) — без него Демо-магазин
  (те же msId из МС) суммировался и давал ложные расхождения остатков
- орг «FOOD MARKET» чистая: 30944 товара = 30944 msId, stocks == Σдвижений
- остатки: 42 расхождения классифицированы — 35 дубль-ШК в МС (слияние, нет аналога, объяснено
  + решение), 7 живой аккаунт (±1-4, дельта). Все объяснены построчно
- delta подтверждён: подтянул 4 живые продажи

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

103 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Перенос магазина МойСклад → ALL MARKET (с историей) — прогресс
PROBE BUILD: ~/pos-release/allmarket-pos-win-x64-20261009-1845-probe.zip — касса `foodmarket.Pos.exe --probe-hw [--out <файл>] [--port COMx]` (COM/весы Масса-К·CAS/принтеры/HID, без печати) + `--test-print "<принтер>"` (ESC/POS тест-чек, отдельно); проверено на Win11 VM, выход < 10 с, ничего не трогает.
Решение владельца 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. [x] **Инвентаризация источника** → `docs/migration-source-inventory.md` (скрипт
`scripts/migration/ms_inventory.py`). Орг «FOOD MARKET», ~31k товаров, 627 контрагентов,
~110k документов, 7558 строк остатков; gap-анализ + цели сверки.
2. [~] **Мигратор — работает на реальных данных.**
- [x] Справочники: товары **30 944**, контрагенты **628** (+externalId), группы **53**;
идемпотентность по `MoySkladExternalId`.
- [x] **Документный мигратор (сервер, фон, идемпотентный, резюмируемый) — РАБОТАЕТ и
ПРОВЕРЕН на ПРИЁМКАХ (полный перенос 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,
дубль номеров (МойСклад повторяет по годам → дизамбигуация «номер-год»).
- [x] **Оприходования (enter) — 72/72** перенесены и проверены (даты/суммы/статус).
- [x] **Розничные продажи (retaildemand) — мигратор работает и ПРОВЕРЕН**: оплата
нал/безнал из cashSum/noCashSum верна (сверено с МойСкладом: первые чеки noCashSum→Card),
даты МСК→UTC, суммы из тиынов, движения остатков (−). Запущен полный прогон 97 707
(фон, ~2.5ч); идёт корректно.
- [x] Списания (loss, −остаток), возвраты поставщику (purchasereturn, −остаток) —
мигратор написан по шаблону (запуск ПОСЛЕ retaildemand — параллельно нельзя, гонка
остатков по одной орг).
- [x] **retailsalesreturn** (возврат розницы, связь с чеком по externalId demand, +остаток),
**inventory** (Completed без движений, BookQty/ActualQty из calculated/quantity) — в сборке.
- [x] **Деньги** (`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).
- [x] **Дельта** (`?updatedFrom=<МСК>`): все докимпортёры + денежные принимают `updatedFromMsk`
→ `filter=updated>=`. Драйвер `scripts/migration/delta_sync.py` (cron раз в N мин, poll
job'ов, удаления через `/audit`, состояние в `~/.fm-watchdog/migration-delta-state.json`).
- [ ] uom/склад/точка/сотрудники как отдельные справочники с externalId (пока товары на
дефолтных ед.; склад — единственный склад орг).
3. [x] **Новая орг на стенде** «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. [x] **Дельта-досинхронизация** (`updated>=` МСК + удалённые через `/audit`) —
`scripts/migration/delta_sync.py`; проверка «изменения появляются» — после деплоя.
6. [x] **Производительность/надёжность** — идемпотентность по `MoySkladExternalId`, резюм по
`?offset=N`, per-doc SaveChanges+ChangeTracker.Clear (в т.ч. в catch) → сбой не каскадит.
Задокументировано в рунбуке (Part 6).
7. [x] **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 (нет права токена) → ручная сверка на дне перехода.
- **ОСТАТКИ — РАЗОБРАНО:** орг «FOOD MARKET» чистая (30 944 товара = 30 944 msId, БЕЗ дублей;
`stocks == Σ(движений)`). «Фантом 30 000» оказался БАГОМ СВЕРКИ: SQL не фильтровал по орг, а
Демо-магазин тоже засеян из МойСклада теми же msId → суммировался. После скоупа по орг
(`ORG=eb5db1ae-…`) расхождений остатков **42**: **35** — товары МС с ДУБЛЕМ ШК (в МС несколько
карточек на один штрихкод, часто весовые/хлеб «0…»); у нас ШК уникален → слиты (`MergeByBarcode`),
msId «лишней» карточки нет в каталоге (нет аналога в модели) — остаток мал; **7** — живой аккаунт
(±1–4, догоняются дельтой). Все 42 объяснены построчно в `migration-reconciliation.md`.
## Ключевые факты 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. В МойСклад — только чтение.