Files
gti d21a9d1140 Add 'fiscal/' from commit '102ba84500dbd9655a0471288c3e278652593ca9'
git-subtree-dir: fiscal
git-subtree-mainline: d95165fc8f
git-subtree-split: 102ba84500
2026-09-29 14:43:23 -03:00

101 lines
4.2 KiB
Python

"""SGV UI oracle: snapshot/diff of ONE C000025 row (READ-ONLY; never writes to Firebird).
python scripts/sgv_product_snapshot.py before <CODIGO>
-> reads the whole row and saves docs/runs/snapshots/<CODIGO>_before.json
(operator changes exactly ONE field in the SGV product screen and saves)
python scripts/sgv_product_snapshot.py diff <CODIGO> [--ui-label "CFOP saída"]
-> re-reads, prints FIELD/BEFORE/AFTER for every changed column, and (with --ui-label) records the
result in docs/runs/sgv_ui_diff_ledger.json. A label is CONFIRMED only when exactly ONE
non-technical column changed.
"""
import argparse
import json
import os
import sys
from datetime import date, datetime
from decimal import Decimal
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "agent" / "src"))
SNAP_DIR = ROOT / "docs" / "runs" / "snapshots"
LEDGER = ROOT / "docs" / "runs" / "sgv_ui_diff_ledger.json"
# Columns the ERP rewrites by itself on every save; ignored ONLY because they are clearly automatic.
TECHNICAL = {"ULTIMA_ALTERACAO", "DATA_ALTERACAO", "DATA_ULTIMA_ALTERACAO", "USUARIO_ALTERACAO"}
def norm(v):
if isinstance(v, (bytes, bytearray)):
import hashlib
return "blob:" + hashlib.sha256(bytes(v)).hexdigest()[:16]
if isinstance(v, (datetime, date)):
return v.isoformat()
if isinstance(v, Decimal):
return str(v)
return v.strip() if isinstance(v, str) else v
def read_row(env, code):
from gti_fiscal_agent.firebird_reader import _connect # read-only isolation
conn = _connect(env)
try:
cur = conn.cursor()
cur.execute("SELECT * FROM C000025 WHERE CODIGO = ?", (code,))
row = cur.fetchone()
if row is None:
raise SystemExit(f"product {code} not found")
names = [c[0].strip() for c in cur.description]
blob = {}
return {n: norm(v) for n, v in zip(names, row)}
finally:
conn.close()
def compute_diff(before, after):
out = []
for f in sorted(set(before) | set(after)):
if before.get(f) != after.get(f):
out.append({"FIELD": f, "BEFORE": before.get(f), "AFTER": after.get(f), "technical": f in TECHNICAL})
return out
def load_env():
for line in (ROOT / ".env").read_text(encoding="utf-8").splitlines():
if "=" in line and not line.startswith("#"):
k, _, v = line.partition("="); os.environ.setdefault(k.strip(), v.strip())
def record(label, code, changes):
real = [c for c in changes if not c["technical"]]
ledger = json.loads(LEDGER.read_text(encoding="utf-8")) if LEDGER.exists() else []
entry = {"ui_label": label, "product": code, "recorded": datetime.now().isoformat(timespec="seconds"), "changed_columns": [c["FIELD"] for c in real],
"before": {c["FIELD"]: c["BEFORE"] for c in real}, "after": {c["FIELD"]: c["AFTER"] for c in real},
"confirmed": len(real) == 1, "note": "" if len(real) == 1 else ("no column changed" if not real else "more than one column changed; repeat changing a single field")}
ledger.append(entry)
LEDGER.write_text(json.dumps(ledger, ensure_ascii=False, indent=1), encoding="utf-8")
return entry
def main(argv=None):
ap = argparse.ArgumentParser()
ap.add_argument("action", choices=["before", "diff"]); ap.add_argument("codigo"); ap.add_argument("--ui-label")
a = ap.parse_args(argv)
load_env()
SNAP_DIR.mkdir(parents=True, exist_ok=True)
path = SNAP_DIR / f"{a.codigo}_before.json"
if a.action == "before":
path.write_text(json.dumps(read_row(os.environ, a.codigo), ensure_ascii=False, indent=1, default=str), encoding="utf-8")
print(f"snapshot saved: {path}"); return
if not path.exists():
raise SystemExit("run 'before' first")
changes = compute_diff(json.loads(path.read_text(encoding="utf-8")), read_row(os.environ, a.codigo))
print(f"{'FIELD':<28}{'BEFORE':<28}AFTER")
for c in changes:
print(f"{c['FIELD']:<28}{str(c['BEFORE']):<28}{c['AFTER']}{' (technical, ignored)' if c['technical'] else ''}")
if a.ui_label:
print(json.dumps(record(a.ui_label, a.codigo, changes), ensure_ascii=False))
if __name__ == "__main__":
main()