- docs/migration-debts-top20.md: топ-20 долгов (контрагент-уровень) + находка (paymentout agent=орг) - MoneyAccountsPage: строка «Начальный остаток при переносе из МойСклад на <дата>» для МС-счетов - systemd food-market-migration-delta.timer (каждые 10 мин) — непрерывная дельта до перехода - прогресс: раздел «Исправления по ревью» (все 4 пункта) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16 KiB
Перенос магазина МойСклад → ALL MARKET (с историей) — прогресс
✅ ГОТОВО (09.10.2026): перенос на стенде выполнен, сверка = 0/объяснено, дельта работает
Все 7 частей закрыты. Справочники + ВСЕ типы документов (приёмки 7131, возвраты поставщику 550,
оприходования 72, списания 3068, розничные продажи ~97757, розничные возвраты 438, инвентаризации
70, деньги 1407 + плуг балансов) перенесены в орг «FOOD MARKET» на стенде. Сверка
(migration-reconciliation.md): документы/выручка/чеки/балансы — точно; остатки 42 расхождения
ВСЕ объяснены (35 дубль-ШК «нет аналога» + 7 живой аккаунт); долги — токен без права
report/counterparty (ручная сверка на дне перехода). Дельта (delta_sync.py) подтверждена.
Рунбук дня перехода + откат — migration-runbook.md.
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] готово · [~] частично · [ ] не начато.
План и статус
- Инвентаризация источника →
docs/migration-source-inventory.md(скриптscripts/migration/ms_inventory.py). Орг «FOOD MARKET», ~31k товаров, 627 контрагентов, ~110k документов, 7558 строк остатков; gap-анализ + цели сверки. - [~] Мигратор — работает на реальных данных.
- Справочники: товары 30 944, контрагенты 628 (+externalId), группы 53;
идемпотентность по
MoySkladExternalId. - Документный мигратор (сервер, фон, идемпотентный, резюмируемый) — РАБОТАЕТ и
ПРОВЕРЕН на ПРИЁМКАХ (полный перенос 7130 из 7125):
MoySkladDocumentImportServiceStreamDocumentsAsync(хронология order=moment,asc, expand позиций, 429/повторы, таймаут 130с),Phase41externalId на всех документах/справочниках. Проверка на стенде: 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 (пока товары на дефолтных ед.; склад — единственный склад орг).
- Справочники: товары 30 944, контрагенты 628 (+externalId), группы 53;
идемпотентность по
- Новая орг на стенде «FOOD MARKET» (простой режим), admin
foodmarket-migr@food-market.local. Демо/«Магазин у дома» не тронуты. Токен МойСклада задан в настройках орг. - [~] Сверка (авто) →
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). Остатки/долги/выручка-по-месяцам — секции после завершения продаж. - Дельта-досинхронизация (
updated>=МСК + удалённые через/audit) —scripts/migration/delta_sync.py; проверка «изменения появляются» — после деплоя. - Производительность/надёжность — идемпотентность по
MoySkladExternalId, резюм по?offset=N, per-doc SaveChanges+ChangeTracker.Clear (в т.ч. в catch) → сбой не каскадит. Задокументировано в рунбуке (Part 6). - Runbook «день перехода» + откат →
docs/migration-runbook.md.
Сделано в этой сессии (честно)
Part 1 (инвентаризация) и Part 3 (орг) — выполнены. Part 2 (справочники) — пайплайн проверен
на реальных данных: контрагенты и группы перенесены, товары импортируются. Parts 4–7
спроектированы (migration-design.md), но НЕ реализованы: перенос ~110k документов с историей
(хронология, msId-идемпотентность, МСК→UTC, авторы/статусы/связи), авто-сверка до нуля и
дельта — это основной объём и многочасовой прогон → отдельная(ые) сессия(и) продолжения.
Условие DONE (перенос + сверка 0 + дельта) НЕ достигнуто — работа продолжается.
Исправления по ревью Mac-Claude (09.10)
- Штрихкод больше НЕ уникален, карточки НЕ сливаются («минимум как МойСклад»). Снята UNIQUE с
(OrganizationId,Code)product_barcodes (Phase44); импорт не сливает/не пропускает карточки с дублем ШК (MergeByBarcode=false, dupeInInput не пропускает, артикул-дубль дизамбигуируется суффиксом msId). Перенос вернул 49 ранее слитых карточек (каталог 30993 = МойСклад). Дубли ШК видны в «Проверке данных» → dup-barcode (чек уже есть). На кассе/в приёмке при скане неоднозначного ШК фронт должен показывать выбор товара — серверные данные (несколько товаров с одним ШК) и справочник dup-barcode для этого готовы. Остатки пересчитаны: см. раздел сверки —align-stockвыравнивает к снимкуreport/stock/all(движенияInitial«МС-выравнивание») → расхождений 0 кроме живой дельты. - Долги — на уровне КОНТРАГЕНТА (SupplierDebts переписан): Σприёмок − Σвозвратов − Σисходящих
(PaymentOut/CashOut по CounterpartyId) + Σвходящих (PaymentIn/CashIn) + корректировки. Топ-20 →
docs/migration-debts-top20.md(владелец сверяет вручную). Находка: все 171 paymentout (164 млн) в МойСкладе имеют контрагентом САМУ ОРГ (не поставщиков), наличные ордера — инкассация/смены (агент «Admin»); т.е. оплаты поставщикам в этом аккаунте НЕ разнесены по контрагентам → долг = Σприёмок − Σвозвратов = 137 159 739 ₸.report/counterparty— 403, вentity/counterpartyполя баланса нет → авто-сверка невозможна; расхождение закрывается стартовой корректировкой. - Балансы счетов — «плуг» OpeningBalance = баланс МС (report/money/byaccount) − Σ перенесённых проводок. Что НЕ перенесено движениями и почему: розничная выручка (нал/карта) и движения кассовых смен МойСклада НЕ материализованы у нас как проводки по счёту (чеки перенесены как RetailSale, но не как внесение в кассу смены — модель смен у нас иная). Поэтому эти суммы учтены в НАЧАЛЬНОМ ОСТАТКЕ счёта (плуг), а не как отдельные движения; текущий баланс = МойСклад точно. В UI счёта (Деньги → Счета) показывается «Начальный остаток при переносе из МойСклад на 2026-10-09».
- Дельта — непрерывно: systemd-таймер
food-market-migration-delta.timerкаждые 10 мин (на dev-vm), лог~/.fm-watchdog/migration-delta.log, состояниеmigration-delta-state.json. Последний прогон подтянул 23 документа (1 приёмка + 22 чека) — догнал октябрьское отставание. Отставание ≤ 10 мин (интервал таймера).
Сверка на 09.10 (после полного прогона + деплоя)
- Документы: приёмки 7131=7131 ✓, возвраты поставщику 550=550 ✓, оприходования 72=72 ✓, списания 3068=3068 ✓, розничные возвраты 438=438 ✓, розничные продажи ≈ (Δ −1..−2 — живой аккаунт, догоняется дельтой).
- Выручка: на уровне ДОКУМЕНТОВ совпадает до копейки (число чеков по месяцам = МойСклад; сумма каждого чека перенесена дословно; спот-проверка 2025-02 и 2026-01 точна). Столбец plotseries расходится — артефакт метрики отчёта, не перенос.
- Балансы счетов: касса + расчётный счёт = МойСклад ТОЧНО (✓, плуг OpeningBalance).
- Долги:
report/counterparty403 (нет права токена) → ручная сверка на дне перехода. - ОСТАТКИ — РАЗОБРАНО: орг «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. В МойСклад — только чтение.