# Рунбук «День перехода» — МойСклад → 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. Заранее (за несколько дней до перехода) 1. **Полный перенос истории** (один раз, многочасовой). Запускать ПОСЛЕДОВАТЕЛЬНО (одна орг, гонка остатков при параллельном запуске): - Справочники: товары, группы, контрагенты (несут `MoySkladExternalId`). - Документы (хронология, МСК→UTC, суммы ÷100, авторы/статусы): `supply` → `enter` → `loss` → `purchasereturn` → `retaildemand` → `retailsalesreturn` → `inventory`. - Деньги + плуг балансов: `type=money` (платежи/ордера/внесения/выплаты + OpeningBalance). - Эндпоинт: `POST /api/admin/moysklad/import-supplies?type=[&offset=N]`. 2. **Сверка** (`scripts/migration/reconcile.py` → `docs/migration-reconciliation.md`): число документов, остатки товар×склад, выручка+чеки по месяцам, балансы счетов, долги. Все расхождения = 0 или объяснены построчно. 3. **Включить дельту** (`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) — вернуться на МойСклад Переход спроектирован как **неразрушающий для источника**: в МойСклад мы НИЧЕГО не писали, поэтому откат = продолжить работать в МойСкладе. 1. Владелец объявляет возврат на МойСклад; кассиры переключают POS обратно на МойСклад/старую кассу. 2. Продажи, совершённые в ALL MARKET за время после часа X, переносятся в МойСклад вручную (их немного — окно короткое) ИЛИ принимается, что они останутся только в ALL MARKET как параллельный период (по решению владельца). 3. Орг ALL MARKET на стенде остаётся как есть (данные не удаляются) — для повторной попытки. 4. Причина отката фиксируется; дефект устраняется; повторный день перехода назначается заново. **Критерий «не откатываемся»:** сверка 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 (сверка это учитывает).