chore: establish project baseline
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
"""Build docs/SGV_PRODUCT_FISCAL_UI_MAP_V1.json from the SGV form definition (Delphi DFM inside SGV.exe) — READ-ONLY.
|
||||
|
||||
Evidence hierarchy per UI control:
|
||||
1. DFM binding: data-aware control on form Tfrmproduto whose DataSource is dsproduto2 (dataset qrproduto =
|
||||
'select * from c000025') and whose DataField names the column; the on-screen caption/position matches the label.
|
||||
2. Column type/domain compatibility (live schema + live values).
|
||||
3. Optional operator UI diff (docs/SGV_PRODUCT_FISCAL_UI_DIFF_SESSION_V1.json), which can only confirm/upgrade.
|
||||
Classes: CONFIRMED_STRONG | PROBABLE | AMBIGUOUS | NO_MATCH.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "agent" / "src"))
|
||||
from gti_fiscal_agent.firebird_reader import _connect # noqa: E402
|
||||
|
||||
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())
|
||||
|
||||
DFM_JSON = ROOT / "docs" / "runs" / "sgv_product_form_bindings.json"
|
||||
SESSION = ROOT / "docs" / "SGV_PRODUCT_FISCAL_UI_DIFF_SESSION_V1.json"
|
||||
dfm = next(f for f in json.loads(DFM_JSON.read_text(encoding="utf-8")) if f["form_class"] == "Tfrmproduto")
|
||||
bound = {c["DataField"]: c for c in dfm["controls"] if c.get("DataField") and c.get("DataSource") == "dsproduto2"}
|
||||
by_label = {}
|
||||
if SESSION.exists():
|
||||
for r in json.loads(SESSION.read_text(encoding="utf-8")).get("results", []):
|
||||
by_label[r["ui_label"]] = r
|
||||
|
||||
# ui_label, screen area, column (None = no column), direction, semantic, expected control name, notes
|
||||
UI = [
|
||||
("NCM", "Aba Estoque > Posição do Estoque/Informações", "CLASSIFICACAO_FISCAL", "n/a", "NCM canônico (8 dígitos); C000025.NCM is not on the screen", "DBEdit14", "NCM lookup control; column NCM (C000025.NCM) has no control."),
|
||||
("CFOP (Nota Fiscal de Entrada)", "Config. Impostos > Nota Fiscal de Entrada", "CFOP_ENTRADA", "entrada", "CFOP padrão de entrada", "DBEdit72", "Column is empty (0/521) but the screen writes it."),
|
||||
("CST de ICMS (Nota Fiscal de Entrada)", "Config. Impostos > Nota Fiscal de Entrada", "CST_ENTRADA", "entrada", "CST ICMS de entrada", "DBEdit73", "Column is empty (0/521) but the screen writes it."),
|
||||
("CFOP (Nota Fiscal de Saída)", "Config. Impostos > Nota Fiscal de Saída GRUPO DE ICMS", "CFOP", "saída", "CFOP padrão de saída", "DbEdit71", ""),
|
||||
("CST de ICMS (Nota Fiscal de Saída)", "Config. Impostos > Nota Fiscal de Saída GRUPO DE ICMS", "CST", "saída", "CST ICMS 3 dígitos (origem+CST)", "dbedit13", "CRT=1 emission uses CSOSN."),
|
||||
("Aliq. ICMS", "Config. Impostos > Nota Fiscal de Saída GRUPO DE ICMS", "ALIQUOTA", "saída", "alíquota ICMS %", "ealiq", "Binding proven; emitted NFC-e values differ from this default (see earlier missions)."),
|
||||
("CEST", "Config. Impostos > Nota Fiscal de Saída GRUPO DE ICMS", "CEST", "n/a", "CEST (7 dígitos)", "DBEdit74", ""),
|
||||
("Seletor ao lado do CEST (T/I/N/S)", "Config. Impostos > Nota Fiscal de Saída GRUPO DE ICMS", "SITUACAO_TRIBUTARIA", "saída", "situação tributária do produto: T=Tributado, I=Isento, N=Não Tributado, S=Substituição Tributária", "wwDBComboBox2", "Hint of the control lists the domain; 'S' is the ERP's own ST flag."),
|
||||
("Cofins Entrada", "Config. Impostos > CST PIS/COFINS", "COD_ENTRADA_COFINS", "entrada", "CST COFINS de entrada", "RzDBButtonEdit3", ""),
|
||||
("Cofins Saída", "Config. Impostos > CST PIS/COFINS", "COD_SAIDA_COFINS", "saída", "CST COFINS de saída", "RzDBButtonEdit1", ""),
|
||||
("Alíquota (Cofins)", "Config. Impostos > CST PIS/COFINS", "ALIQ_COFINS", "saída", "alíquota COFINS (texto)", "RzDBEdit4", ""),
|
||||
("Pis Entrada", "Config. Impostos > CST PIS/COFINS", "COD_ENTRADA_PIS", "entrada", "CST PIS de entrada", "RzDBButtonEdit5", ""),
|
||||
("Pis Saída", "Config. Impostos > CST PIS/COFINS", "COD_SAIDA_PIS", "saída", "CST PIS de saída", "RzDBButtonEdit2", ""),
|
||||
("Alíquota (Pis)", "Config. Impostos > CST PIS/COFINS", "ALIQ_PIS", "saída", "alíquota PIS (texto)", "RzDBEdit3", ""),
|
||||
("CSOSN Classificação", "Config. Impostos > CSOSN", "CSOSN", "saída", "CSOSN (código)", "DBEdit75", ""),
|
||||
("CSOSN Informações - Significados", "Config. Impostos > CSOSN", None, "n/a", "descrição do CSOSN (apoio visual)", "ComboBox1", "Plain TComboBox, not data-aware: presentation helper, no column."),
|
||||
("Alíquota IPI %", "Config. Impostos > IPI", "IPI", "saída", "alíquota IPI %", "RzDBEdit2", "Cálculo Preço tab has other IPI/PIS/COFINS controls bound to C000026 (price calculation), not fiscal defaults."),
|
||||
("CST IPI", "Config. Impostos > IPI", "CST_IPI", "saída", "CST do IPI", "RzDBEdit1", ""),
|
||||
("Código da origem da Mercadoria (0/1/2)", "Config. Impostos", "COD_ORIGEM_MERCADORIA", "n/a", "origem da mercadoria (0 Nacional, 1 Estrangeira-Importação Direta, 2 Estrangeira-Mercado Interno)", "CodOrigemMercadoria",
|
||||
"Radio group RgOrigemMercadoria is not data-aware; a hidden data-aware edit bound to COD_ORIGEM_MERCADORIA sits beside it. The radio->edit copy lives in event code."),
|
||||
("IAT", "Config. Impostos", "IAT", "n/a", "arredondamento/truncamento (A/T)", "wwDBComboBox3", ""),
|
||||
("Incidência", "Config. Impostos", "INCIDENCIA_PISCOFINS", "n/a", "tipo de incidência PIS/COFINS", "wwDBComboBox1", ""),
|
||||
("PIS/COFINS (combo)", "Config. Impostos", "PISCOFINS", "n/a", "flag PIS/COFINS (N/S)", "ecombo_piscofins", ""),
|
||||
("IPPT", "Config. Impostos", "IPPT", "n/a", "produção própria/terceiros (P/T)", "wwDBComboBox4", ""),
|
||||
]
|
||||
|
||||
cur = _connect(os.environ).cursor()
|
||||
cur.execute("select trim(rf.RDB$FIELD_NAME), f.RDB$FIELD_TYPE, coalesce(f.RDB$CHARACTER_LENGTH,f.RDB$FIELD_LENGTH), f.RDB$FIELD_SCALE from RDB$RELATION_FIELDS rf join RDB$FIELDS f on f.RDB$FIELD_NAME=rf.RDB$FIELD_SOURCE where rf.RDB$RELATION_NAME='C000025'")
|
||||
TN = {7: "SMALLINT", 8: "INTEGER", 14: "CHAR", 37: "VARCHAR", 27: "DOUBLE PRECISION", 16: "NUMERIC", 10: "FLOAT", 35: "TIMESTAMP", 12: "DATE"}
|
||||
meta = {}
|
||||
for n, t, ln, sc in cur.fetchall():
|
||||
meta[n] = f"{TN.get(t, t)}({ln})" if t in (14, 37) else (f"NUMERIC(*,{-sc})" if t == 16 and sc else TN.get(t, str(t)))
|
||||
|
||||
|
||||
def stats(col):
|
||||
cur.execute(f'select "{col}", count(*) from C000025 group by "{col}" order by 2 desc')
|
||||
vals = [(None if v is None else str(v).strip(), n) for v, n in cur.fetchall()]
|
||||
return sum(n for v, n in vals if v not in (None, "")), [v for v, _ in vals[:5]]
|
||||
|
||||
|
||||
entries = []
|
||||
for label, area, col, direction, sem, ctrl, notes in UI:
|
||||
diff = by_label.get(label)
|
||||
if col is None:
|
||||
entries.append({"ui_label": label, "screen_area": area, "table": "C000025", "field": None, "db_type": None, "direction": direction, "semantic": sem, "before": None, "after": None,
|
||||
"confirmed_by_ui_diff": False, "classification": "NO_MATCH", "confidence": "HIGH", "binding_type": "NOT_DATA_AWARE", "evidence_basis": f"DFM: {ctrl} is not data-aware",
|
||||
"write_target": False, "current_population": None, "sample_values": [], "notes": notes})
|
||||
continue
|
||||
c = bound.get(col)
|
||||
dfm_ok = bool(c) and c["name"] == ctrl and col in meta
|
||||
pop, samples = stats(col) if col in meta else (None, [])
|
||||
if not dfm_ok:
|
||||
klass, binding, basis = "NO_MATCH", "NO_DFM_BINDING", f"expected control {ctrl} bound to {col} not found in DFM"
|
||||
elif ctrl == "CodOrigemMercadoria":
|
||||
klass, binding, basis = "CONFIRMED_STRONG", "DFM_RADIO_ONCLICK_HIDDEN_EDIT", "DFM: RgOrigemMercadoria (TRadioGroup, no DataField, OnClick=RgOrigemMercadoriaClick) + hidden data-aware TDBEdit CodOrigemMercadoria bound to COD_ORIGEM_MERCADORIA in the same tab, the only origin-bound control of the form; the handler and both symbols exist in the executable; values 0/1/2 observed in 5 databases"
|
||||
else:
|
||||
klass, binding = "CONFIRMED_STRONG", "DFM_DATAFIELD"
|
||||
basis = f"DFM: {c['class']} {c['name']} DataField={col} DataSource=dsproduto2 (qrproduto = select * from c000025)"
|
||||
ui_diff = bool(diff and diff.get("binding_type") == "DIRECT_BINDING" and col in diff.get("db_fields_changed", []))
|
||||
if ui_diff:
|
||||
klass, binding = "CONFIRMED_STRONG", "DFM_DATAFIELD+UI_DIFF"
|
||||
entries.append({"ui_label": label, "screen_area": area, "table": "C000025", "field": col, "db_type": meta.get(col), "direction": direction, "semantic": sem, "before": None, "after": None,
|
||||
"confirmed_by_ui_diff": ui_diff, "classification": klass, "confidence": "HIGH" if klass == "CONFIRMED_STRONG" else "MEDIUM", "binding_type": binding, "evidence_basis": basis,
|
||||
"dfm_control": {"class": c["class"], "name": c["name"], "left": c.get("Left"), "top": c.get("Top"), "container_path": [p.split(":")[1] for p in c["path"][-3:]]} if c else None,
|
||||
"write_target": klass == "CONFIRMED_STRONG", "current_population": pop, "sample_values": samples, "notes": notes})
|
||||
cur.connection.close()
|
||||
|
||||
not_on_screen = [f for f in ("ORIGEM", "GRUPO_ICMS", "MARGEM_AGREGADA", "CFOP_CUPOM", "NCM") if f not in bound]
|
||||
cnt = lambda k: sum(1 for e in entries if e["classification"] == k) # noqa: E731
|
||||
doc = {"artifact": "SGV_PRODUCT_FISCAL_UI_MAP_V1", "product_table": "C000025", "company_table": "C000004", "company_fields": {"CRT": "C000004.CRT", "UF": "C000004.UF"},
|
||||
"evidence_source": "Delphi DFM of form Tfrmproduto extracted from SGV_5.0.6.124.exe (docs/runs/sgv_product_form_bindings.json); dsproduto2 -> qrproduto = 'select * from c000025'",
|
||||
"architecture_rule": "GTI Fiscal writes ONLY the canonical product record (C000025). Transactional/historical tables are evidence only.",
|
||||
"transactional_table_writes_allowed": False,
|
||||
"forbidden_write_tables": ["NFCE", "NFCE_ITENS", "C000032", "C000061", "C000062", "C000088", "INVENTARIO", "C000026 (price calculation)", "any historical/document/item table"],
|
||||
"columns_with_no_screen_control": [f for f in not_on_screen],
|
||||
"summary": {"ui_fields_total": len(entries), "CONFIRMED_STRONG": cnt("CONFIRMED_STRONG"), "PROBABLE": cnt("PROBABLE"), "AMBIGUOUS": cnt("AMBIGUOUS"), "NO_MATCH": cnt("NO_MATCH"),
|
||||
"write_target_fields": [e["field"] for e in entries if e["write_target"]],
|
||||
"fields_requiring_manual_ui_diff": [e["ui_label"] for e in entries if e["classification"] in ("PROBABLE", "AMBIGUOUS")],
|
||||
"unidentified_controls": ["Panel1.ealiquota -> CODALIQUOTA (data-aware, not visible in the supplied screenshots; column empty 0/521)"]},
|
||||
"fields": entries}
|
||||
(ROOT / "docs" / "SGV_PRODUCT_FISCAL_UI_MAP_V1.json").write_text(json.dumps(doc, ensure_ascii=False, indent=1), encoding="utf-8")
|
||||
print(json.dumps(doc["summary"], ensure_ascii=False))
|
||||
Reference in New Issue
Block a user