food-market/scripts/migration/reconcile.py
nns 2d57ceab54
Some checks are pending
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 API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
feat(migration): сверка по числу документов + loss/purchasereturn/enter перенесены
loss 3068/3068, purchasereturn 550/550, enter 72/72 — сходятся точно. Приёмки 7130
(Δ -1 к живому МС). Розничные продажи — перенос продолжается (резюм по offset).
scripts/migration/reconcile.py сравнивает МойСклад vs ALL MARKET (read-only) →
docs/migration-reconciliation.md. Счётчики МС растут (живой аккаунт) → нужна дельта.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:01:29 +05:00

55 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Сверка переноса МойСклад → ALL MARKET (только чтение обоих). Сравнивает:
- число документов каждого типа; - остатки товар×склад (report/stock/all); - выручку и
число чеков по месяцам. Пишет docs/migration-reconciliation.md. Расхождения помечает."""
import os, json, gzip, time, urllib.request, urllib.error, sys, ssl
from collections import defaultdict
MS="https://api.moysklad.ru/api/remap/1.2"
AM="https://test.admin.food-market.kz"
MSTOK=[l.split("=",1)[1].strip() for l in open(os.path.expanduser("~/.food-market-secrets/moysklad.env")) if l.startswith("MOYSKLAD_TOKEN=")][0]
_ctx=ssl.create_default_context(); _ctx.check_hostname=False; _ctx.verify_mode=ssl.CERT_NONE
def _get(url,hdr):
for i in range(4):
try:
req=urllib.request.Request(url,headers=hdr)
with urllib.request.urlopen(req,timeout=130,context=_ctx) as r:
raw=r.read()
if r.headers.get("Content-Encoding")=="gzip": raw=gzip.decompress(raw)
return json.loads(raw.decode("utf-8"))
except urllib.error.HTTPError as e:
if e.code==429: time.sleep(float(e.headers.get("X-RateLimit-Retry-TimeInterval",2000))/1000+0.5); continue
if e.code>=500: time.sleep(2*(i+1)); continue
raise
except Exception: time.sleep(2*(i+1))
return None
def ms(path): return _get(MS+path,{"Authorization":f"Bearer {MSTOK}","Accept-Encoding":"gzip"})
def am_token():
import urllib.parse
data=urllib.parse.urlencode({"grant_type":"password","username":"foodmarket-migr@food-market.local","password":"Migrate12345!","client_id":"food-market-web","scope":"openid profile email roles api"}).encode()
req=urllib.request.Request(AM+"/connect/token",data=data,headers={"Content-Type":"application/x-www-form-urlencoded"})
with urllib.request.urlopen(req,timeout=60,context=_ctx) as r: return json.loads(r.read())["access_token"]
TOK=am_token()
def am(path): return _get(AM+path,{"Authorization":f"Bearer {TOK}","Accept-Encoding":"gzip"})
def ms_size(ent): d=ms(f"/entity/{ent}?limit=1"); return d.get("meta",{}).get("size") if d else None
def am_total(path): d=am(path+("&" if "?" in path else "?")+"take=1&pageSize=1"); return d.get("total") if d else None
print("# Сверка переноса МойСклад → ALL MARKET\n")
print(f"_Сгенерировано {time.strftime('%Y-%m-%d %H:%M:%S')}._\n")
print("## 1. Число документов по типам\n")
print("| Тип | МойСклад | ALL MARKET | Δ | Статус |")
print("|---|---|---|---|---|")
DOCS=[("Приёмки","supply","/api/purchases/supplies"),("Возвраты поставщику","purchasereturn","/api/purchases/supplier-returns"),
("Оприходования","enter","/api/inventory/enters"),("Списания","loss","/api/inventory/losses"),
("Розничные продажи","retaildemand","/api/sales/retail")]
allzero=True
for label,ent,ap in DOCS:
a=ms_size(ent); b=am_total(ap);
try: d=(b or 0)-(a or 0)
except: d="?"
ok="✓" if d==0 else ("~ идёт перенос" if isinstance(d,int) and d<0 else "✗")
if d!=0: allzero=False
print(f"| {label} | {a} | {b} | {d} | {ok} |")
time.sleep(0.15)
print(f"\n_Доккаунты: {'все сходятся' if allzero else 'есть расхождения/перенос продолжается (см. Δ<0 — ещё не все)'}._")