- scripts/migration/delta_sync.py: раз в N мин updated>=lastSync по всем типам, удаления через /audit, состояние в ~/.fm-watchdog, перекрытие окна 2 мин - docs/migration-runbook.md: фазы A/B/C, откат на МойСклад, надёжность, ограничения токена - reconcile.py: секции балансов счетов (report/money/byaccount) и долгов (403-ограничение) - progress: Parts 5/6/7 закрыты Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9 KiB
9 KiB
Рунбук «День перехода» — МойСклад → ALL MARKET
Решение владельца 09.10.2026: перенести ВСЁ из МойСклада с историей и продолжить работу в ALL MARKET. Этот документ — пошаговый регламент перехода, кто что делает, как откатиться и что проверить после. Фискализации/маркировки нет (не блокеры).
- Источник: аккаунт МойСклад, орг «FOOD MARKET» (
bb8f74d4-5eb6-11ef-0a80-106e00048af9). Только чтение. - Приёмник: ALL MARKET, орг «FOOD MARKET» на стенде (простой режим),
admin
foodmarket-migr@food-market.local. Демо-магазин и «Магазин у дома» НЕ трогаем. - Токен МойСклада —
~/.food-market-secrets/moysklad.env(в МойСклад ничего не писать).
Фаза A. Заранее (за несколько дней до перехода)
- Полный перенос истории (один раз, многочасовой). Запускать ПОСЛЕДОВАТЕЛЬНО (одна орг,
гонка остатков при параллельном запуске):
- Справочники: товары, группы, контрагенты (несут
MoySkladExternalId). - Документы (хронология, МСК→UTC, суммы ÷100, авторы/статусы):
supply→enter→loss→purchasereturn→retaildemand→retailsalesreturn→inventory. - Деньги + плуг балансов:
type=money(платежи/ордера/внесения/выплаты + OpeningBalance). - Эндпоинт:
POST /api/admin/moysklad/import-supplies?type=<T>[&offset=N].
- Справочники: товары, группы, контрагенты (несут
- Сверка (
scripts/migration/reconcile.py→docs/migration-reconciliation.md): число документов, остатки товар×склад, выручка+чеки по месяцам, балансы счетов, долги. Все расхождения = 0 или объяснены построчно. - Включить дельту (
scripts/migration/delta_sync.py, cron раз в 10–15 мин) с момента окончания полного переноса: подтягивает только измененияupdated>=lastSync. Проверить, что свежие чеки из МойСклада появляются в ALL MARKET в течение интервала.
Фаза B. День перехода (час X)
| Шаг | Кто | Действие |
|---|---|---|
| B1 | Владелец | Объявить стоп-кассе в МойСкладе: последний чек пробит, новые продажи приостановлены на ~15 мин. |
| B2 | Инженер | Остановить cron дельты (чтобы зафиксировать окно). |
| B3 | Инженер | Финальная дельта вручную: python3 scripts/migration/delta_sync.py — подтянет последние чеки/платежи за минуты. |
| B4 | Инженер | Обработать удаления из отчёта дельты (lastDeletions в состоянии): удалить соответствующие документы в ALL MARKET по регламенту (сверять по MoySkladExternalId). |
| B5 | Инженер | Прогнать сверку reconcile.py. Убедиться: документы, остатки, выручка, балансы = 0 расхождений (долги — см. ограничение токена ниже). |
| B6 | Владелец+Инженер | Контрольная ручная проверка в UI: 3–5 товаров (остаток), 2–3 поставщика (долг), баланс кассы и расчётного счёта, выручка за вчера. |
| B7 | Владелец | Переключить персонал/кассы на ALL MARKET (POS → адрес нового сервера, логины кассиров). |
| B8 | Владелец | Возобновить продажи в ALL MARKET. МойСклад переводится в режим «только архив» (не продавать). |
Фаза C. После перехода (контроль)
- Первый день: следить за остатками/выручкой в ALL MARKET, сверять итоги дня с ожиданиями.
- Держать аккаунт МойСклада доступным на чтение ≥1 месяц (архив истории, разбор спорных).
- Не удалять токен/данные МойСклада, пока переход не признан успешным.
Откат (rollback) — вернуться на МойСклад
Переход спроектирован как неразрушающий для источника: в МойСклад мы НИЧЕГО не писали, поэтому откат = продолжить работать в МойСкладе.
- Владелец объявляет возврат на МойСклад; кассиры переключают POS обратно на МойСклад/старую кассу.
- Продажи, совершённые в ALL MARKET за время после часа X, переносятся в МойСклад вручную (их немного — окно короткое) ИЛИ принимается, что они останутся только в ALL MARKET как параллельный период (по решению владельца).
- Орг ALL MARKET на стенде остаётся как есть (данные не удаляются) — для повторной попытки.
- Причина отката фиксируется; дефект устраняется; повторный день перехода назначается заново.
Критерий «не откатываемся»: сверка B5 = 0 расхождений (или объяснены) и ручная проверка B6 совпала. Если расходится критично (остатки/деньги) — откат по пунктам выше.
Надёжность и производительность (Part 6)
- Идемпотентность: повторный запуск любого типа пропускает уже перенесённое по
MoySkladExternalId(дублей нет). Проверено на приёмках/оприходованиях/списаниях. - Резюмируемость: падение/рестарт в середине — продолжить с
?offset=N(N ≈ число уже перенесённых минус 200 для перекрытия); идемпотентность убирает повтор. - Устойчивость к сбоям: каждый документ — отдельный
SaveChanges+ChangeTracker.Clear(в т.ч. в catch), поэтому ошибка одного документа не роняет прогон и не каскадит. - Лимиты МойСклада: 45 запросов/3с, таймаут 130с,
Accept-Encoding: gzip, 429 с повтором поX-RateLimit-Retry-TimeInterval. Страница 100 док.,order=moment,asc.
Известные ограничения (объяснённые расхождения)
- Долги контрагентов: отчёт МойСклада
report/counterpartyнедоступен выданным read-only токеном (403 — нет права на взаиморасчёты). Долги в ALL MARKET формируются из перенесённых первичных документов; итоговую цифру сверять вручную в UI МойСклада (шаг B6) или временно выдать токену право на отчёт для авто-сверкиreconcile.py(секция 5). - Балансы счетов: розничная наличность/движения кассовых смен до перехода не
материализуются подокументно; баланс сводится «плугом»
OpeningBalance = баланс МойСклада (report/money/byaccount) − Σ перенесённых проводок. Текущий баланс счёта в ALL MARKET точно совпадает с МойСкладом; подробная история доналичных движений остаётся в архиве МойСклада. - Нулевые остатки:
report/stock/allне отдаёт нулевые позиции — товар без строки считается остатком 0 (сверка это учитывает).