feat(migration-review): долги топ-20, UI «начальный остаток при переносе», дельта-таймер, прогресс
Some checks failed
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
Docker Web / Build + push Web (push) Has been cancelled
Docker Web / Deploy Web on stage (push) Has been cancelled

- 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>
This commit is contained in:
nns 2026-10-09 20:43:44 +05:00
parent 150947c5fb
commit 99c2fc1e5a
5 changed files with 102 additions and 4 deletions

View file

@ -0,0 +1,10 @@
[Unit]
Description=food-market migration delta sync (MoySklad -> ALL MARKET stage)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /home/nns/food-market/scripts/migration/delta_sync.py
User=nns
# лог прогона — для контроля отставания
StandardOutput=append:/home/nns/.fm-watchdog/migration-delta.log
StandardError=append:/home/nns/.fm-watchdog/migration-delta.log

View file

@ -0,0 +1,8 @@
[Unit]
Description=Run migration delta sync every 10 min (until cutover)
[Timer]
OnBootSec=5min
OnUnitActiveSec=10min
AccuracySec=30s
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,45 @@
# Долги поставщикам — топ-20 (перенос МойСклад → ALL MARKET)
_Сгенерировано автоматически. Долг считается на уровне КОНТРАГЕНТА (как МойСклад):_
`долг = Σ приёмок (проведённых) − Σ возвратов поставщику − Σ исходящих платежей/РКО контрагенту`
`+ Σ входящих от него + корректировки взаиморасчётов`.
**Итого долг по всем поставщикам: 137 159 739.17 ₸.**
## ⚠ Важно для сверки (прочитать)
- Отчёт МойСклада `report/counterparty` недоступен выданным read-only токеном (**403**), а в
`entity/counterparty` поля баланса/долга НЕТ (есть только `salesAmount`, `bonusPoints`).
Поэтому **авто-сверка долга с цифрой МойСклада невозможна** — владелец сверяет вручную
(МойСклад → Контрагенты → Взаиморасчёты) против таблицы ниже.
- **Почему оплаты не уменьшают долг:** все 171 исходящих безналичных платежа (paymentout, 164 млн ₸)
в МойСкладе имеют контрагента = **сама организация «FOOD MARKET»**, а не поставщиков. Наличные
ордера (cashin/cashout) — это инкассация/движения смен (агент «Admin»), тоже не привязаны к
поставщикам. Т.е. в этом аккаунте МойСклада оплаты поставщикам НЕ разнесены по контрагентам →
долг = Σ приёмок − Σ возвратов. Если в МойСкладе взаиморасчёты по поставщику меньше (оплаты
учтены иначе) — завести стартовую **корректировку взаиморасчётов** на разницу (документ есть
в системе: Деньги → Корректировки), тогда долг в ALL MARKET сойдётся с МойСкладом.
## Топ-20 поставщиков по долгу
| # | Поставщик (имя МойСклад) | БИН | Долг ALL MARKET ₸ | Σ приёмок ₸ | Σ возвратов ₸ |
|---|---|---|---|---|---|
| 1 | Мегаполис сигареты | | 12623868.60 | 12634424.60 | 10556.00 |
| 2 | Овощи фрукты оптом, базар Амир | | 5934388.98 | 5934388.98 | 0.00 |
| 3 | ЭкоНурия экотовары | | 4904127.00 | 4904127.00 | 0.00 |
| 4 | Coca-Cola 87712007078 | | 4603846.29 | 4603846.29 | 0.00 |
| 5 | Канц товары,1000мелочей магазин Елнур | | 3706898.00 | 3709210.00 | 2312.00 |
| 6 | Родина/Зенченко | | 2608483.00 | 2612783.00 | 4300.00 |
| 7 | Магазин Алматы/ Обувь оптом 87015549581 | | 2483102.00 | 2483102.00 | 0.00 |
| 8 | ТОО RG Brands Kazakhstan | | 2430866.23 | 2443377.90 | 12511.67 |
| 9 | сигареты КЕНТ, Neo | | 2328085.60 | 2328085.60 | 0.00 |
| 10 | Алмалы Астана ТОО (Горилла) | | 2139725.00 | 2143574.00 | 3849.00 |
| 11 | ИП Алтын тау Сигареты ESSE | | 2009150.00 | 2009150.00 | 0.00 |
| 12 | Базар Сухофрукты бутик-73 87052988444 | | 1969936.00 | 1969936.00 | 0.00 |
| 13 | ЭкоНурия Bitony | | 1900951.75 | 1900951.75 | 0.00 |
| 14 | Шымкентбай Шарын | | 1829765.00 | 1829765.00 | 0.00 |
| 15 | Боржоми Жигер трейд | | 1820522.00 | 1828490.00 | 7968.00 |
| 16 | Большая семья,магнат | | 1809237.00 | 1809237.00 | 0.00 |
| 17 | 1000 мелочей Ардагер,Амир базары | | 1756000.04 | 1756000.04 | 0.00 |
| 18 | Романовский продукт 87025051187 | | 1672585.00 | 1692515.00 | 19930.00 |
| 19 | KDV Казахстан(яшкино) | | 1619745.40 | 1619745.40 | 0.00 |
| 20 | КИНДЕР Прима | | 1593057.00 | 1593057.00 | 0.00 |

View file

@ -84,6 +84,34 @@ Part 1 (инвентаризация) и Part 3 (орг) — выполнены.
дельта — это основной объём и многочасовой прогон → отдельная(ые) сессия(и) продолжения.
Условие DONE (перенос + сверка 0 + дельта) НЕ достигнуто — работа продолжается.
## Исправления по ревью Mac-Claude (09.10)
1. **Штрихкод больше НЕ уникален, карточки НЕ сливаются** («минимум как МойСклад»). Снята UNIQUE с
`(OrganizationId,Code)` product_barcodes (Phase44); импорт не сливает/не пропускает карточки с
дублем ШК (MergeByBarcode=false, dupeInInput не пропускает, артикул-дубль дизамбигуируется
суффиксом msId). Перенос вернул **49 ранее слитых карточек** (каталог 30993 = МойСклад). Дубли
ШК видны в «Проверке данных» → dup-barcode (чек уже есть). На кассе/в приёмке при скане
неоднозначного ШК фронт должен показывать выбор товара — серверные данные (несколько товаров с
одним ШК) и справочник dup-barcode для этого готовы. **Остатки пересчитаны**: см. раздел сверки —
`align-stock` выравнивает к снимку `report/stock/all` (движения `Initial` «МС-выравнивание») →
расхождений 0 кроме живой дельты.
2. **Долги — на уровне КОНТРАГЕНТА** (SupplierDebts переписан): Σприёмок − Σвозвратов − Σисходящих
(PaymentOut/CashOut по CounterpartyId) + Σвходящих (PaymentIn/CashIn) + корректировки. Топ-20 →
`docs/migration-debts-top20.md` (владелец сверяет вручную). **Находка:** все 171 paymentout (164 млн)
в МойСкладе имеют контрагентом САМУ ОРГ (не поставщиков), наличные ордера — инкассация/смены
(агент «Admin»); т.е. оплаты поставщикам в этом аккаунте НЕ разнесены по контрагентам → долг =
Σприёмок − Σвозвратов = **137 159 739 ₸**. `report/counterparty` — 403, в `entity/counterparty`
поля баланса нет → авто-сверка невозможна; расхождение закрывается стартовой корректировкой.
3. **Балансы счетов — «плуг»** OpeningBalance = баланс МС (report/money/byaccount) − Σ перенесённых
проводок. **Что НЕ перенесено движениями и почему:** розничная выручка (нал/карта) и движения
кассовых смен МойСклада НЕ материализованы у нас как проводки по счёту (чеки перенесены как
RetailSale, но не как внесение в кассу смены — модель смен у нас иная). Поэтому эти суммы учтены
в НАЧАЛЬНОМ ОСТАТКЕ счёта (плуг), а не как отдельные движения; текущий баланс = МойСклад точно.
В UI счёта (Деньги → Счета) показывается «Начальный остаток при переносе из МойСклад на 2026-10-09».
4. **Дельта — непрерывно**: 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 — живой

View file

@ -120,10 +120,17 @@ export function MoneyAccountsPage() {
empty="Счетов пока нет. Добавьте кассу магазина и, если нужно, банковские счета."
columns={[
{ header: 'Название', cell: (r) => (
<span className="flex items-center gap-1.5">
{r.name}
{r.isDefault && <Star className="w-3.5 h-3.5 text-amber-500" aria-label="По умолчанию" />}
{!r.isActive && <Archive className="w-3.5 h-3.5 text-slate-400" aria-label="Архив" />}
<span className="flex flex-col">
<span className="flex items-center gap-1.5">
{r.name}
{r.isDefault && <Star className="w-3.5 h-3.5 text-amber-500" aria-label="По умолчанию" />}
{!r.isActive && <Archive className="w-3.5 h-3.5 text-slate-400" aria-label="Архив" />}
</span>
{r.name.includes('из МойСклад') && (
<span className="text-xs text-slate-400">
Начальный остаток при переносе из МойСклад на {r.openingDate?.slice(0, 10)}: {fmt(r.openingBalance ?? 0)}
</span>
)}
</span>
)},
{ header: 'Тип', width: '150px', cell: (r) => ACCOUNT_KIND_LABEL[r.kind] ?? r.kind },