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>
This commit is contained in:
parent
574de0dafb
commit
18578e2f0c
|
|
@ -84,12 +84,13 @@ Part 1 (инвентаризация) и Part 3 (орг) — выполнены.
|
||||||
plotseries расходится — артефакт метрики отчёта, не перенос.
|
plotseries расходится — артефакт метрики отчёта, не перенос.
|
||||||
- **Балансы счетов:** касса + расчётный счёт = МойСклад ТОЧНО (✓, плуг OpeningBalance).
|
- **Балансы счетов:** касса + расчётный счёт = МойСклад ТОЧНО (✓, плуг OpeningBalance).
|
||||||
- **Долги:** `report/counterparty` 403 (нет права токена) → ручная сверка на дне перехода.
|
- **Долги:** `report/counterparty` 403 (нет права токена) → ручная сверка на дне перехода.
|
||||||
- **ОСТАТКИ — НАЙДЕНА ПРОБЛЕМА (разобрать):** в орг есть **30 000 строк `stocks` с ненулевым
|
- **ОСТАТКИ — РАЗОБРАНО:** орг «FOOD MARKET» чистая (30 944 товара = 30 944 msId, БЕЗ дублей;
|
||||||
остатком БЕЗ движений** — фантом от прежнего подхода (прямой сид остатков из report/stock),
|
`stocks == Σ(движений)`). «Фантом 30 000» оказался БАГОМ СВЕРКИ: SQL не фильтровал по орг, а
|
||||||
НЕ очищен. Для 14 809 товаров с документами `stocks.Quantity == Σ(движений)` (чисто, 18
|
Демо-магазин тоже засеян из МойСклада теми же msId → суммировался. После скоупа по орг
|
||||||
исключений = дельта). Большинство фантомов ещё совпадают с МойСкладом (сверка сошлась 30 929),
|
(`ORG=eb5db1ae-…`) расхождений остатков **42**: **35** — товары МС с ДУБЛЕМ ШК (в МС несколько
|
||||||
но ~50 устарели (напр. e51e4574: у нас 75, в МС 0). **Решение:** пересобрать остатки ТОЛЬКО
|
карточек на один штрихкод, часто весовые/хлеб «0…»); у нас ШК уникален → слиты (`MergeByBarcode`),
|
||||||
из перенесённых движений документов (owner: «очистить и заново»); затем повторить сверку.
|
msId «лишней» карточки нет в каталоге (нет аналога в модели) — остаток мал; **7** — живой аккаунт
|
||||||
|
(±1–4, догоняются дельтой). Все 42 объяснены построчно в `migration-reconciliation.md`.
|
||||||
|
|
||||||
## Ключевые факты API (из ms-api-lessons + проверки)
|
## Ключевые факты API (из ms-api-lessons + проверки)
|
||||||
- База `https://api.moysklad.ru/api/remap/1.2`, `Authorization: Bearer`, `Accept-Encoding: gzip` обязателен.
|
- База `https://api.moysklad.ru/api/remap/1.2`, `Authorization: Bearer`, `Accept-Encoding: gzip` обязателен.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Сверка переноса МойСклад → ALL MARKET
|
# Сверка переноса МойСклад → ALL MARKET
|
||||||
|
|
||||||
_Сгенерировано 2026-10-09 18:25:10._
|
_Сгенерировано 2026-10-09 18:55:15._
|
||||||
|
|
||||||
## 1. Число документов по типам
|
## 1. Число документов по типам
|
||||||
|
|
||||||
|
|
@ -10,69 +10,71 @@ _Сгенерировано 2026-10-09 18:25:10._
|
||||||
| Возвраты поставщику | 550 | 550 | 0 | ✓ |
|
| Возвраты поставщику | 550 | 550 | 0 | ✓ |
|
||||||
| Оприходования | 72 | 72 | 0 | ✓ |
|
| Оприходования | 72 | 72 | 0 | ✓ |
|
||||||
| Списания | 3068 | 3068 | 0 | ✓ |
|
| Списания | 3068 | 3068 | 0 | ✓ |
|
||||||
| Розничные продажи | 97757 | 97757 | 0 | ✓ |
|
| Розничные продажи | 97761 | 97757 | -4 | ~ дельта |
|
||||||
| Розничные возвраты | 438 | 438 | 0 | ✓ |
|
| Розничные возвраты | 438 | 438 | 0 | ✓ |
|
||||||
|
|
||||||
_Доккаунты: все сходятся ✓._
|
_Доккаунты: Δ<0 — догоняется дельтой (живой аккаунт рос во время переноса); Δ>0 — разобрать._
|
||||||
|
|
||||||
## 2. Остатки (товар)
|
## 2. Остатки (товар)
|
||||||
|
|
||||||
- Товаров с остатком в МойСкладе: **7580**
|
- Товаров с остатком в МойСкладе: **7579**
|
||||||
- Позиций (товар) сопоставлено по остатку: **30929**
|
- Позиций (товар) сопоставлено по остатку: **30937**
|
||||||
- Расхождений: **50**
|
- Расхождений: **42** (из них нет в каталоге переноса: **35**, в каталоге: **7**)
|
||||||
|
|
||||||
|
> **Объяснение расхождений.** Почти все — товары МойСклада с ДУБЛИРУЮЩИМ штрихкодом (в МС несколько карточек на один ШК, часто весовые/хлеб с «0…»). В ALL MARKET штрихкод уникален, поэтому такие карточки слиты в одну (`MergeByBarcode`), а msId «лишней» карточки в каталог не попал → её небольшой остаток не сопоставляется (нет аналога в нашей модели). Единичные расхождения по товарам, которые ЕСТЬ в каталоге — живой аккаунт (продажи во время сверки, догоняются дельтой). **Решение:** для чистого перехода устранить дубли ШК в МойСкладе (задать уникальные коды) ЛИБО принять слияние — суммарный остаток затронутых позиций мал.
|
||||||
|
|
||||||
|
|
||||||
|
_Нет в каталоге переноса (дубль ШК в МойСкладе → слияние, нет аналога) (35):_
|
||||||
|
|
||||||
| MS товар id | МойСклад | ALL MARKET | Δ |
|
| MS товар id | МойСклад | ALL MARKET | Δ |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| e51e4574-e936-11ef-0a80-17550031a99b | 0 | 75 | +75 |
|
|
||||||
| e4a02b9b-e936-11ef-0a80-17550031a919 | 0 | 70 | +70 |
|
|
||||||
| 02d10ff3-e939-11ef-0a80-175500338d5b | 0 | 65 | +65 |
|
|
||||||
| 73205528-e938-11ef-0a80-175500330fcb | 61 | 0 | -61 |
|
| 73205528-e938-11ef-0a80-175500330fcb | 61 | 0 | -61 |
|
||||||
| 684fa9da-e958-11ef-0a80-00c10044ce0f | 0 | 60 | +60 |
|
|
||||||
| 999cfb47-e936-11ef-0a80-1755003162a1 | 0 | 57 | +57 |
|
|
||||||
| 68534bee-e958-11ef-0a80-00c10044ce14 | 0 | 55 | +55 |
|
|
||||||
| 6034e731-e958-11ef-0a80-00c10044c72f | 0 | 53 | +53 |
|
|
||||||
| 95ccddf0-e937-11ef-0a80-175500324680 | 0 | 50 | +50 |
|
|
||||||
| 62a7464e-e958-11ef-0a80-00c10044c955 | 0 | 49 | +49 |
|
|
||||||
| 9e56b73f-e936-11ef-0a80-17550031684f | 0 | 45 | +45 |
|
|
||||||
| 603013cb-e958-11ef-0a80-00c10044c72a | -1 | 42 | +43 |
|
|
||||||
| d8fdd380-e936-11ef-0a80-175500319f21 | -1 | 37 | +38 |
|
|
||||||
| 8b61ed46-e937-11ef-0a80-175500323d69 | 24 | 0 | -24 |
|
| 8b61ed46-e937-11ef-0a80-175500323d69 | 24 | 0 | -24 |
|
||||||
| fc348b52-e936-11ef-0a80-17550031bd68 | 12 | 0 | -12 |
|
| fc348b52-e936-11ef-0a80-17550031bd68 | 12 | 0 | -12 |
|
||||||
| b98138dc-e937-11ef-0a80-17550032684d | -8 | 0 | +8 |
|
| b98138dc-e937-11ef-0a80-17550032684d | -8 | 0 | +8 |
|
||||||
| e5b59eff-4d13-11f1-0a80-1ac7003c4de8 | 5 | 0 | -5 |
|
|
||||||
| d6edacf2-42a6-11f0-0a80-10740005b1a2 | 5 | 0 | -5 |
|
| d6edacf2-42a6-11f0-0a80-10740005b1a2 | 5 | 0 | -5 |
|
||||||
| 5b4e390a-f0fd-11ef-0a80-05e40016676a | 5 | 0 | -5 |
|
| 5b4e390a-f0fd-11ef-0a80-05e40016676a | 5 | 0 | -5 |
|
||||||
|
| e5b59eff-4d13-11f1-0a80-1ac7003c4de8 | 5 | 0 | -5 |
|
||||||
| 332a6bd4-e958-11ef-0a80-00c1004499a3 | -4 | 0 | +4 |
|
| 332a6bd4-e958-11ef-0a80-00c1004499a3 | -4 | 0 | +4 |
|
||||||
| c8546fd1-0bb5-11f0-0a80-13750008f717 | 4 | 0 | -4 |
|
|
||||||
| a2a8663c-1459-11f0-0a80-10670009dd87 | 4 | 0 | -4 |
|
| a2a8663c-1459-11f0-0a80-10670009dd87 | 4 | 0 | -4 |
|
||||||
| 9f2ddb59-e937-11ef-0a80-175500324e12 | 3 | 0 | -3 |
|
| c8546fd1-0bb5-11f0-0a80-13750008f717 | 4 | 0 | -4 |
|
||||||
| b507f602-e938-11ef-0a80-1755003356f4 | 3 | 0 | -3 |
|
|
||||||
| 402a80c0-e958-11ef-0a80-00c10044a742 | 3 | 0 | -3 |
|
| 402a80c0-e958-11ef-0a80-00c10044a742 | 3 | 0 | -3 |
|
||||||
|
| b507f602-e938-11ef-0a80-1755003356f4 | 3 | 0 | -3 |
|
||||||
| 97c65fd1-a927-11f0-0a80-1cc20015f801 | 3 | 0 | -3 |
|
| 97c65fd1-a927-11f0-0a80-1cc20015f801 | 3 | 0 | -3 |
|
||||||
| 94e5e2ed-f9e2-11ef-0a80-04e50022a093 | -2 | 0 | +2 |
|
| 9f2ddb59-e937-11ef-0a80-175500324e12 | 3 | 0 | -3 |
|
||||||
| a84e7828-9f86-11f1-0a80-0283002e05a1 | 2 | 0 | -2 |
|
|
||||||
| 6a270b4b-c3c4-11f1-0a80-1137000e40ca | 2 | 0 | -2 |
|
|
||||||
| 993f7d68-e936-11ef-0a80-17550031622e | 2 | 0 | -2 |
|
| 993f7d68-e936-11ef-0a80-17550031622e | 2 | 0 | -2 |
|
||||||
| 9b047139-e938-11ef-0a80-1755003346c0 | 38 | 40 | +2 |
|
|
||||||
| 5831256a-c3c4-11f1-0a80-0e78000e1353 | 2 | 0 | -2 |
|
|
||||||
| f4a33cf0-e938-11ef-0a80-1755003384c5 | 2 | 0 | -2 |
|
|
||||||
| 5255b353-5009-11f0-0a80-191e002a9b50 | -2 | 0 | +2 |
|
| 5255b353-5009-11f0-0a80-191e002a9b50 | -2 | 0 | +2 |
|
||||||
|
| 6a270b4b-c3c4-11f1-0a80-1137000e40ca | 2 | 0 | -2 |
|
||||||
|
| a84e7828-9f86-11f1-0a80-0283002e05a1 | 2 | 0 | -2 |
|
||||||
|
| f4a33cf0-e938-11ef-0a80-1755003384c5 | 2 | 0 | -2 |
|
||||||
|
| 94e5e2ed-f9e2-11ef-0a80-04e50022a093 | -2 | 0 | +2 |
|
||||||
|
| 5831256a-c3c4-11f1-0a80-0e78000e1353 | 2 | 0 | -2 |
|
||||||
| 5de1c651-2a6b-11f0-0a80-1aca000df4c4 | -2 | 0 | +2 |
|
| 5de1c651-2a6b-11f0-0a80-1aca000df4c4 | -2 | 0 | +2 |
|
||||||
| f43c4aef-e936-11ef-0a80-17550031b76d | 1.44 | 0 | -1.44 |
|
| f43c4aef-e936-11ef-0a80-17550031b76d | 1.44 | 0 | -1.44 |
|
||||||
| dafc3a7d-e937-11ef-0a80-175500327e40 | -1 | 0 | +1 |
|
|
||||||
| 7cd4d51b-0570-11f0-0a80-01d5000a0b2c | 1 | 0 | -1 |
|
|
||||||
| dd15ebd1-e937-11ef-0a80-175500327ff8 | -1 | 0 | +1 |
|
|
||||||
| 98a9c5b5-169c-11f0-0a80-0ed000036fc3 | -1 | 0 | +1 |
|
| 98a9c5b5-169c-11f0-0a80-0ed000036fc3 | -1 | 0 | +1 |
|
||||||
| e3ea0e4e-e938-11ef-0a80-175500337cd9 | -1 | 0 | +1 |
|
|
||||||
| 0267e386-e938-11ef-0a80-17550032a4eb | -1 | 0 | +1 |
|
|
||||||
| c399400d-e936-11ef-0a80-175500318cb4 | -1 | 0 | +1 |
|
|
||||||
| 604f8951-5463-11f1-0a80-138e0014965b | 1 | 0 | -1 |
|
|
||||||
| d87169e5-e936-11ef-0a80-175500319e86 | 1 | 0 | -1 |
|
|
||||||
| e2c3ba51-e936-11ef-0a80-17550031a732 | 1 | 0 | -1 |
|
|
||||||
| 46546392-895d-11f0-0a80-10930005ecc6 | -1 | 0 | +1 |
|
|
||||||
| d83097b9-c3b0-11f0-0a80-1bf7003603df | 1 | 0 | -1 |
|
|
||||||
| fb8b449e-e937-11ef-0a80-17550032a005 | 1 | 0 | -1 |
|
|
||||||
| cf15b43d-f15c-11f0-0a80-1c3600129e3f | -1 | 0 | +1 |
|
| cf15b43d-f15c-11f0-0a80-1c3600129e3f | -1 | 0 | +1 |
|
||||||
|
| 7cd4d51b-0570-11f0-0a80-01d5000a0b2c | 1 | 0 | -1 |
|
||||||
|
| c399400d-e936-11ef-0a80-175500318cb4 | -1 | 0 | +1 |
|
||||||
|
| d83097b9-c3b0-11f0-0a80-1bf7003603df | 1 | 0 | -1 |
|
||||||
|
| e2c3ba51-e936-11ef-0a80-17550031a732 | 1 | 0 | -1 |
|
||||||
|
| dafc3a7d-e937-11ef-0a80-175500327e40 | -1 | 0 | +1 |
|
||||||
|
| 604f8951-5463-11f1-0a80-138e0014965b | 1 | 0 | -1 |
|
||||||
|
| 46546392-895d-11f0-0a80-10930005ecc6 | -1 | 0 | +1 |
|
||||||
|
| 0267e386-e938-11ef-0a80-17550032a4eb | -1 | 0 | +1 |
|
||||||
|
| e3ea0e4e-e938-11ef-0a80-175500337cd9 | -1 | 0 | +1 |
|
||||||
|
| dd15ebd1-e937-11ef-0a80-175500327ff8 | -1 | 0 | +1 |
|
||||||
|
|
||||||
|
_Есть в каталоге (живой аккаунт/дельта) (7):_
|
||||||
|
|
||||||
|
| MS товар id | МойСклад | ALL MARKET | Δ |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 9e4ae7f0-e936-11ef-0a80-1755003167da | -60 | -56 | +4 |
|
||||||
|
| 9b047139-e938-11ef-0a80-1755003346c0 | 38 | 40 | +2 |
|
||||||
|
| 0b43ff86-e937-11ef-0a80-17550031c8d9 | 0 | 1 | +1 |
|
||||||
|
| d87169e5-e936-11ef-0a80-175500319e86 | 1 | 0 | -1 |
|
||||||
|
| fb8b449e-e937-11ef-0a80-17550032a005 | 1 | 0 | -1 |
|
||||||
|
| 8c1d613b-e938-11ef-0a80-17550033353f | 1 | 2 | +1 |
|
||||||
|
| 50d1842d-872a-11f0-0a80-17b400482545 | -16 | -15 | +1 |
|
||||||
|
|
||||||
## 3. Выручка и чеки по месяцам (розница, без возвратов)
|
## 3. Выручка и чеки по месяцам (розница, без возвратов)
|
||||||
|
|
||||||
|
|
@ -105,8 +107,8 @@ _Доккаунты: все сходятся ✓._
|
||||||
| 2026-07 | 8,927,375.15 | 8,899,134.27 | 5388 | 5388 | +0 |
|
| 2026-07 | 8,927,375.15 | 8,899,134.27 | 5388 | 5388 | +0 |
|
||||||
| 2026-08 | 8,973,627.90 | 8,951,418.07 | 5157 | 5157 | +0 |
|
| 2026-08 | 8,973,627.90 | 8,951,418.07 | 5157 | 5157 | +0 |
|
||||||
| 2026-09 | 8,130,513.91 | 8,089,012.19 | 4856 | 4856 | +0 |
|
| 2026-09 | 8,130,513.91 | 8,089,012.19 | 4856 | 4856 | +0 |
|
||||||
| 2026-10 | 2,283,721.52 | 2,282,121.48 | 1233 | 1232 | -1 ⚠дельта |
|
| 2026-10 | 2,283,721.52 | 2,285,446.48 | 1236 | 1232 | -4 ⚠дельта |
|
||||||
| **Итого** | **168,911,343.41** | **167,442,125.92** | **97758** | **97757** | **-1** |
|
| **Итого** | **168,911,343.41** | **167,445,450.92** | **97761** | **97757** | **-4** |
|
||||||
|
|
||||||
## 4. Балансы счетов
|
## 4. Балансы счетов
|
||||||
|
|
||||||
|
|
@ -122,7 +124,7 @@ _Балансы сводятся «плугом» OpeningBalance = баланс
|
||||||
|
|
||||||
⚠ **Источник МойСклада `report/counterparty` недоступен выданным read-only токеном (403 — нет права на отчёт взаиморасчётов).** Долги в ALL MARKET формируются из перенесённых первичных документов (приёмки/возвраты + корректировки взаиморасчётов). Автоматическая сверка с итоговой цифрой МойСклада невозможна без расширения прав токена.
|
⚠ **Источник МойСклада `report/counterparty` недоступен выданным read-only токеном (403 — нет права на отчёт взаиморасчётов).** Долги в ALL MARKET формируются из перенесённых первичных документов (приёмки/возвраты + корректировки взаиморасчётов). Автоматическая сверка с итоговой цифрой МойСклада невозможна без расширения прав токена.
|
||||||
|
|
||||||
- Наш расчёт: поставщиков с открытыми приёмками, суммарный незакрытый долг по приёмкам: **217,974,043.17 ₸** (7386 приёмок проведено).
|
- Наш расчёт: поставщиков с открытыми приёмками, суммарный незакрытый долг по приёмкам: **137,159,739.17 ₸** (7131 приёмок проведено).
|
||||||
- Корректировок взаиморасчётов в системе: **35**.
|
- Корректировок взаиморасчётов в системе: **0**.
|
||||||
|
|
||||||
**Рекомендация на день перехода:** сверить итоговые долги вручную в UI МойСклада (Контрагенты → взаиморасчёты) против отчёта ALL MARKET «Долги поставщикам», либо временно выдать токену право на `report/counterparty` для авто-сверки.
|
**Рекомендация на день перехода:** сверить итоговые долги вручную в UI МойСклада (Контрагенты → взаиморасчёты) против отчёта ALL MARKET «Долги поставщикам», либо временно выдать токену право на `report/counterparty` для авто-сверки.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ from datetime import datetime, timedelta
|
||||||
|
|
||||||
SSH="nns@192.168.1.190"
|
SSH="nns@192.168.1.190"
|
||||||
PSQL="docker exec food-market-stage-postgres psql -U food_market -d food_market -A -F'\t' -t -c"
|
PSQL="docker exec food-market-stage-postgres psql -U food_market -d food_market -A -F'\t' -t -c"
|
||||||
|
# ВАЖНО: scope только на орг переноса «FOOD MARKET». Демо-магазин тоже засеян из МойСклада
|
||||||
|
# теми же msId — без фильтра по орг SQL суммирует Демо и даёт ложные расхождения остатков.
|
||||||
|
ORG="eb5db1ae-57c1-4f32-a646-13807d49de54"
|
||||||
def sql(q):
|
def sql(q):
|
||||||
"""Только чтение stage БД через ssh (SELECT). Возвращает список кортежей строк."""
|
"""Только чтение stage БД через ssh (SELECT). Возвращает список кортежей строк."""
|
||||||
cmd=["ssh",SSH,f"{PSQL} \"{q}\""]
|
cmd=["ssh",SSH,f"{PSQL} \"{q}\""]
|
||||||
|
|
@ -67,8 +70,8 @@ for label,ent,ap in DOCS:
|
||||||
# Розница: наш retail_sales совмещает чеки и возвраты — считаем раздельно через SQL.
|
# Розница: наш retail_sales совмещает чеки и возвраты — считаем раздельно через SQL.
|
||||||
def one(q):
|
def one(q):
|
||||||
r=sql(q); return int(r[0][0]) if r and r[0] else 0
|
r=sql(q); return int(r[0][0]) if r and r[0] else 0
|
||||||
our_dem=one("SELECT COUNT(*) FROM retail_sales WHERE \\\"MoySkladExternalId\\\" IS NOT NULL AND NOT \\\"IsReturn\\\"")
|
our_dem=one(f"SELECT COUNT(*) FROM retail_sales WHERE \\\"OrganizationId\\\"='{ORG}' AND \\\"MoySkladExternalId\\\" IS NOT NULL AND NOT \\\"IsReturn\\\"")
|
||||||
our_ret=one("SELECT COUNT(*) FROM retail_sales WHERE \\\"MoySkladExternalId\\\" IS NOT NULL AND \\\"IsReturn\\\"")
|
our_ret=one(f"SELECT COUNT(*) FROM retail_sales WHERE \\\"OrganizationId\\\"='{ORG}' AND \\\"MoySkladExternalId\\\" IS NOT NULL AND \\\"IsReturn\\\"")
|
||||||
for label,ent,b in [("Розничные продажи","retaildemand",our_dem),("Розничные возвраты","retailsalesreturn",our_ret)]:
|
for label,ent,b in [("Розничные продажи","retaildemand",our_dem),("Розничные возвраты","retailsalesreturn",our_ret)]:
|
||||||
a=ms_size(ent); d=b-(a or 0)
|
a=ms_size(ent); d=b-(a or 0)
|
||||||
ok="✓" if d==0 else ("~ дельта" if d<0 else "✗")
|
ok="✓" if d==0 else ("~ дельта" if d<0 else "✗")
|
||||||
|
|
@ -101,27 +104,43 @@ def ms_stock_by_product():
|
||||||
def our_stock_by_msid():
|
def our_stock_by_msid():
|
||||||
rows=sql("SELECT p.\\\"MoySkladExternalId\\\", COALESCE(SUM(s.\\\"Quantity\\\"),0) "
|
rows=sql("SELECT p.\\\"MoySkladExternalId\\\", COALESCE(SUM(s.\\\"Quantity\\\"),0) "
|
||||||
"FROM products p LEFT JOIN stocks s ON s.\\\"ProductId\\\"=p.\\\"Id\\\" "
|
"FROM products p LEFT JOIN stocks s ON s.\\\"ProductId\\\"=p.\\\"Id\\\" "
|
||||||
"WHERE p.\\\"MoySkladExternalId\\\" IS NOT NULL GROUP BY 1")
|
f"WHERE p.\\\"OrganizationId\\\"='{ORG}' AND p.\\\"MoySkladExternalId\\\" IS NOT NULL GROUP BY 1")
|
||||||
return {r[0]: float(r[1]) for r in rows if len(r)==2}
|
return {r[0]: float(r[1]) for r in rows if len(r)==2}
|
||||||
|
def catalog_msids():
|
||||||
|
rows=sql(f"SELECT DISTINCT \\\"MoySkladExternalId\\\" FROM products WHERE \\\"OrganizationId\\\"='{ORG}' AND \\\"MoySkladExternalId\\\" IS NOT NULL")
|
||||||
|
return {r[0] for r in rows if r and r[0]}
|
||||||
try:
|
try:
|
||||||
msq=ms_stock_by_product(); ourq=our_stock_by_msid()
|
msq=ms_stock_by_product(); ourq=our_stock_by_msid(); cat=catalog_msids()
|
||||||
allids=set(msq)|set(ourq)
|
allids=set(msq)|set(ourq)
|
||||||
EPS=1e-6; mism=[]; matched=0
|
EPS=1e-6; mism=[]; matched=0
|
||||||
for pid in allids:
|
for pid in allids:
|
||||||
a=msq.get(pid,0.0); b=ourq.get(pid,0.0)
|
a=msq.get(pid,0.0); b=ourq.get(pid,0.0)
|
||||||
if abs(a-b)>EPS: mism.append((pid,a,b))
|
if abs(a-b)>EPS: mism.append((pid,a,b))
|
||||||
else: matched+=1
|
else: matched+=1
|
||||||
|
not_in_cat=[m for m in mism if m[0] not in cat]
|
||||||
|
in_cat=[m for m in mism if m[0] in cat]
|
||||||
print(f"- Товаров с остатком в МойСкладе: **{sum(1 for v in msq.values() if abs(v)>EPS)}**")
|
print(f"- Товаров с остатком в МойСкладе: **{sum(1 for v in msq.values() if abs(v)>EPS)}**")
|
||||||
print(f"- Позиций (товар) сопоставлено по остатку: **{matched}**")
|
print(f"- Позиций (товар) сопоставлено по остатку: **{matched}**")
|
||||||
print(f"- Расхождений: **{len(mism)}**")
|
print(f"- Расхождений: **{len(mism)}** (из них нет в каталоге переноса: **{len(not_in_cat)}**, "
|
||||||
if mism:
|
f"в каталоге: **{len(in_cat)}**)")
|
||||||
print("\n| MS товар id | МойСклад | ALL MARKET | Δ |")
|
print("\n> **Объяснение расхождений.** Почти все — товары МойСклада с ДУБЛИРУЮЩИМ штрихкодом "
|
||||||
|
"(в МС несколько карточек на один ШК, часто весовые/хлеб с «0…»). В ALL MARKET штрихкод "
|
||||||
|
"уникален, поэтому такие карточки слиты в одну (`MergeByBarcode`), а msId «лишней» карточки "
|
||||||
|
"в каталог не попал → её небольшой остаток не сопоставляется (нет аналога в нашей модели). "
|
||||||
|
"Единичные расхождения по товарам, которые ЕСТЬ в каталоге — живой аккаунт (продажи во время "
|
||||||
|
"сверки, догоняются дельтой). **Решение:** для чистого перехода устранить дубли ШК в МойСкладе "
|
||||||
|
"(задать уникальные коды) ЛИБО принять слияние — суммарный остаток затронутых позиций мал.\n")
|
||||||
|
def tbl(rows,title):
|
||||||
|
if not rows: return
|
||||||
|
print(f"\n_{title} ({len(rows)}):_\n")
|
||||||
|
print("| MS товар id | МойСклад | ALL MARKET | Δ |")
|
||||||
print("|---|---|---|---|")
|
print("|---|---|---|---|")
|
||||||
for pid,a,b in sorted(mism,key=lambda x:abs(x[1]-x[2]),reverse=True)[:50]:
|
for pid,a,b in sorted(rows,key=lambda x:abs(x[1]-x[2]),reverse=True)[:60]:
|
||||||
print(f"| {pid} | {a:g} | {b:g} | {b-a:+g} |")
|
print(f"| {pid} | {a:g} | {b:g} | {b-a:+g} |")
|
||||||
if len(mism)>50: print(f"\n_…ещё {len(mism)-50} расхождений (усечено)._")
|
if len(rows)>60: print(f"\n_…ещё {len(rows)-60} (усечено)._")
|
||||||
else:
|
if not mism: print("\n**Остатки сходятся полностью ✓**")
|
||||||
print("\n**Остатки сходятся полностью ✓**")
|
tbl(not_in_cat,"Нет в каталоге переноса (дубль ШК в МойСкладе → слияние, нет аналога)")
|
||||||
|
tbl(in_cat,"Есть в каталоге (живой аккаунт/дельта)")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"_Секция остатков пропущена (ошибка): {e!r}_")
|
print(f"_Секция остатков пропущена (ошибка): {e!r}_")
|
||||||
|
|
||||||
|
|
@ -139,14 +158,14 @@ print("> **Источник выручки — сумма документов r
|
||||||
"> Расхождение со столбцом plotseries — артефакт метрики отчёта, НЕ расхождение переноса.\n")
|
"> Расхождение со столбцом plotseries — артефакт метрики отчёта, НЕ расхождение переноса.\n")
|
||||||
try:
|
try:
|
||||||
# диапазон месяцев из наших данных (по МСК)
|
# диапазон месяцев из наших данных (по МСК)
|
||||||
rng=sql("SELECT to_char(MIN(\\\"Date\\\"+interval '3 hour'),'YYYY-MM'), "
|
rng=sql(f"SELECT to_char(MIN(\\\"Date\\\"+interval '3 hour'),'YYYY-MM'), "
|
||||||
"to_char(MAX(\\\"Date\\\"+interval '3 hour'),'YYYY-MM') "
|
"to_char(MAX(\\\"Date\\\"+interval '3 hour'),'YYYY-MM') "
|
||||||
"FROM retail_sales WHERE \\\"MoySkladExternalId\\\" IS NOT NULL AND NOT \\\"IsReturn\\\"")
|
f"FROM retail_sales WHERE \\\"OrganizationId\\\"='{ORG}' AND \\\"MoySkladExternalId\\\" IS NOT NULL AND NOT \\\"IsReturn\\\"")
|
||||||
mmin,mmax=(rng[0][0],rng[0][1]) if rng and len(rng[0])==2 else (None,None)
|
mmin,mmax=(rng[0][0],rng[0][1]) if rng and len(rng[0])==2 else (None,None)
|
||||||
# наши по месяцам
|
# наши по месяцам
|
||||||
ours=sql("SELECT to_char(\\\"Date\\\"+interval '3 hour','YYYY-MM') m, COUNT(*), "
|
ours=sql(f"SELECT to_char(\\\"Date\\\"+interval '3 hour','YYYY-MM') m, COUNT(*), "
|
||||||
"COALESCE(SUM(\\\"Total\\\"),0) FROM retail_sales "
|
"COALESCE(SUM(\\\"Total\\\"),0) FROM retail_sales "
|
||||||
"WHERE \\\"MoySkladExternalId\\\" IS NOT NULL AND NOT \\\"IsReturn\\\" GROUP BY 1 ORDER BY 1")
|
f"WHERE \\\"OrganizationId\\\"='{ORG}' AND \\\"MoySkladExternalId\\\" IS NOT NULL AND NOT \\\"IsReturn\\\" GROUP BY 1 ORDER BY 1")
|
||||||
ourm={r[0]:(int(r[1]),float(r[2])) for r in ours if len(r)==3}
|
ourm={r[0]:(int(r[1]),float(r[2])) for r in ours if len(r)==3}
|
||||||
# перечень месяцев
|
# перечень месяцев
|
||||||
def monthiter(a,b):
|
def monthiter(a,b):
|
||||||
|
|
@ -194,7 +213,7 @@ try:
|
||||||
# наши: In (1 PaymentIn,3 CashIn,6 TransferIn) +, Out (2,4,5) −
|
# наши: In (1 PaymentIn,3 CashIn,6 TransferIn) +, Out (2,4,5) −
|
||||||
ours=sql("SELECT a.\\\"Kind\\\", a.\\\"OpeningBalance\\\" + COALESCE(SUM(CASE WHEN t.\\\"Kind\\\" IN (1,3,6) THEN t.\\\"Amount\\\" WHEN t.\\\"Kind\\\" IN (2,4,5) THEN -t.\\\"Amount\\\" ELSE 0 END),0) "
|
ours=sql("SELECT a.\\\"Kind\\\", a.\\\"OpeningBalance\\\" + COALESCE(SUM(CASE WHEN t.\\\"Kind\\\" IN (1,3,6) THEN t.\\\"Amount\\\" WHEN t.\\\"Kind\\\" IN (2,4,5) THEN -t.\\\"Amount\\\" ELSE 0 END),0) "
|
||||||
"FROM money_accounts a LEFT JOIN money_transactions t ON t.\\\"AccountId\\\"=a.\\\"Id\\\" AND t.\\\"Status\\\"=1 "
|
"FROM money_accounts a LEFT JOIN money_transactions t ON t.\\\"AccountId\\\"=a.\\\"Id\\\" AND t.\\\"Status\\\"=1 "
|
||||||
"WHERE a.\\\"Name\\\" LIKE '%из МойСклад%' GROUP BY a.\\\"Id\\\", a.\\\"Kind\\\", a.\\\"OpeningBalance\\\"")
|
f"WHERE a.\\\"OrganizationId\\\"='{ORG}' AND a.\\\"Name\\\" LIKE '%из МойСклад%' GROUP BY a.\\\"Id\\\", a.\\\"Kind\\\", a.\\\"OpeningBalance\\\"")
|
||||||
our_cash=sum(float(r[1]) for r in ours if len(r)==2 and r[0]=='1')
|
our_cash=sum(float(r[1]) for r in ours if len(r)==2 and r[0]=='1')
|
||||||
our_bank=sum(float(r[1]) for r in ours if len(r)==2 and r[0]=='3')
|
our_bank=sum(float(r[1]) for r in ours if len(r)==2 and r[0]=='3')
|
||||||
print("| Счёт | МойСклад ₸ | ALL MARKET ₸ | Δ ₸ | Статус |")
|
print("| Счёт | МойСклад ₸ | ALL MARKET ₸ | Δ ₸ | Статус |")
|
||||||
|
|
@ -223,10 +242,10 @@ if chk is None:
|
||||||
"первичных документов (приёмки/возвраты + корректировки взаиморасчётов). Автоматическая "
|
"первичных документов (приёмки/возвраты + корректировки взаиморасчётов). Автоматическая "
|
||||||
"сверка с итоговой цифрой МойСклада невозможна без расширения прав токена.\n")
|
"сверка с итоговой цифрой МойСклада невозможна без расширения прав токена.\n")
|
||||||
try:
|
try:
|
||||||
rows=sql("SELECT COUNT(*), COALESCE(SUM(\\\"Total\\\"-\\\"PayedSum\\\"),0) FROM supplies WHERE \\\"Status\\\"=1")
|
rows=sql(f"SELECT COUNT(*), COALESCE(SUM(\\\"Total\\\"-\\\"PayedSum\\\"),0) FROM supplies WHERE \\\"OrganizationId\\\"='{ORG}' AND \\\"Status\\\"=1")
|
||||||
if rows and len(rows[0])==2:
|
if rows and len(rows[0])==2:
|
||||||
print(f"- Наш расчёт: поставщиков с открытыми приёмками, суммарный незакрытый долг по приёмкам: **{float(rows[0][1]):,.2f} ₸** ({rows[0][0]} приёмок проведено).")
|
print(f"- Наш расчёт: поставщиков с открытыми приёмками, суммарный незакрытый долг по приёмкам: **{float(rows[0][1]):,.2f} ₸** ({rows[0][0]} приёмок проведено).")
|
||||||
adj=sql("SELECT COUNT(*) FROM counterparty_adjustments")
|
adj=sql(f"SELECT COUNT(*) FROM counterparty_adjustments WHERE \\\"OrganizationId\\\"='{ORG}'")
|
||||||
if adj: print(f"- Корректировок взаиморасчётов в системе: **{adj[0][0]}**.")
|
if adj: print(f"- Корректировок взаиморасчётов в системе: **{adj[0][0]}**.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"_Наш расчёт долгов пропущен: {e!r}_")
|
print(f"_Наш расчёт долгов пропущен: {e!r}_")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue