Initial import NDC_1C

This commit is contained in:
2026-03-26 10:38:25 +03:00
commit a162d77ef7
2943 changed files with 3615871 additions and 0 deletions
+228
View File
@@ -0,0 +1,228 @@
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
import sys
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.client import ODataClient, utc_now_iso
from config.settings import LOGS_DIR, load_settings
LINES_ENTITY_SET = "Document_РеализацияТоваровУслуг_Товары"
POSTING_ENTITY_SET = "AccountingRegister_Хозрасчетный_RecordType"
RECORDER_TYPE = "StandardODATA.Document_РеализацияТоваровУслуг"
LINE_FIELDS = ["Ref_Key", "LineNumber", "СчетУчета_Key", "Субконто", "Субконто_Type"]
POSTING_FIELDS = ["Recorder", "Recorder_Type", "LineNumber", "AccountDr_Key", "AccountCr_Key"]
def _extract_rows(payload: dict[str, Any]) -> list[dict[str, Any]]:
rows = payload.get("value")
if rows is None and isinstance(payload.get("d"), dict):
rows = payload["d"].get("results")
if rows is None:
return []
if isinstance(rows, list):
return rows
return [rows]
def _safe_read(
client: ODataClient,
entity_set: str,
*,
select_fields: list[str],
top: int = 200,
filter_expr: str | None = None,
) -> list[dict[str, Any]]:
params: dict[str, Any] = {"$select": ",".join(select_fields)}
if filter_expr:
params["$filter"] = filter_expr
try:
response = client.read_entity_set(entity_set, top=top, extra_params=params)
return _extract_rows(response.payload)
except Exception as exc:
print(f"[warn] read failed for {entity_set} (filter={filter_expr!r}): {exc}")
return []
def _group_lines_by_document(rows: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]:
grouped: dict[str, list[dict[str, Any]]] = {}
for row in rows:
ref_key = row.get("Ref_Key")
if not isinstance(ref_key, str) or not ref_key:
continue
grouped.setdefault(ref_key, []).append(row)
return grouped
def _pick_document_for_probe(grouped: dict[str, list[dict[str, Any]]]) -> tuple[str | None, list[dict[str, Any]]]:
best_doc: str | None = None
best_lines: list[dict[str, Any]] = []
best_score = -1
for doc_key, rows in grouped.items():
score = sum(1 for row in rows if row.get("Субконто_Type"))
if score > best_score:
best_score = score
best_doc = doc_key
best_lines = rows
return best_doc, best_lines
def _to_line_key(value: Any) -> str:
if value is None:
return ""
return str(value)
def main() -> int:
settings = load_settings()
client = ODataClient(settings)
line_rows = _safe_read(
client,
LINES_ENTITY_SET,
select_fields=LINE_FIELDS,
top=3000,
)
posting_rows_all = _safe_read(
client,
POSTING_ENTITY_SET,
select_fields=POSTING_FIELDS,
top=5000,
)
if not line_rows:
print("[error] No document line rows were fetched for probe.")
return 1
if not posting_rows_all:
print("[error] No posting rows were fetched for probe.")
return 1
grouped_lines = _group_lines_by_document(line_rows)
sales_postings = [
row
for row in posting_rows_all
if row.get("Recorder_Type") == RECORDER_TYPE and isinstance(row.get("Recorder"), str)
]
postings_by_doc: dict[str, list[dict[str, Any]]] = {}
for row in sales_postings:
recorder = row.get("Recorder")
if isinstance(recorder, str) and recorder:
postings_by_doc.setdefault(recorder, []).append(row)
tested_document_key: str | None = None
selected_lines: list[dict[str, Any]] = []
posting_rows: list[dict[str, Any]] = []
best_score = -1
for doc_key, doc_postings in postings_by_doc.items():
doc_lines = grouped_lines.get(doc_key, [])
if not doc_lines:
continue
subconto_typed = sum(1 for row in doc_lines if row.get("Субконто_Type"))
score = subconto_typed * 1000 + len(doc_postings)
if score > best_score:
best_score = score
tested_document_key = doc_key
selected_lines = doc_lines
posting_rows = doc_postings
if not tested_document_key:
tested_document_key, selected_lines = _pick_document_for_probe(grouped_lines)
if not tested_document_key:
print("[error] No suitable document key found in lines/postings overlap.")
return 1
posting_rows = postings_by_doc.get(tested_document_key, [])
postings_by_line: dict[str, list[dict[str, Any]]] = {}
for row in posting_rows:
key = _to_line_key(row.get("LineNumber"))
if key:
postings_by_line.setdefault(key, []).append(row)
joined_rows: list[dict[str, Any]] = []
chart_account_subconto_fields: list[str] = []
for line in selected_lines:
line_no = _to_line_key(line.get("LineNumber"))
if not line_no:
continue
candidates = postings_by_line.get(line_no, [])
if not candidates:
continue
line_account = line.get("СчетУчета_Key")
for posting in candidates:
account_dr = posting.get("AccountDr_Key")
account_cr = posting.get("AccountCr_Key")
account_match = bool(
isinstance(line_account, str)
and line_account
and line_account in {account_dr, account_cr}
)
joined_rows.append(
{
"line_number": line_no,
"recorder": posting.get("Recorder"),
"account_dr_key": account_dr,
"account_cr_key": account_cr,
"line_account_key": line_account,
"subconto_value": line.get("Субконто"),
"subconto_type": line.get("Субконто_Type"),
"account_match": account_match,
}
)
break
chain_a_status = "derivable" if joined_rows else "opaque"
chain_f_status = (
"derivable"
if any(row.get("subconto_type") for row in joined_rows)
else "opaque"
)
report = {
"generated_at": utc_now_iso(),
"endpoint": settings.service_root,
"tested_document_key": tested_document_key,
"posting_rows_for_document": len(posting_rows),
"line_rows_for_document": len(selected_lines),
"joined_rows": len(joined_rows),
"joined_sample": joined_rows[:10],
"chart_account_subconto_fields": chart_account_subconto_fields,
"chain_A_status": chain_a_status,
"chain_F_status": chain_f_status,
"chain_F_note": (
"Derivable by data-driven mapping (Account in posting + Subconto_Type in linked document lines). "
"Direct normative mapping from ChartOfAccounts fields is not exposed."
),
}
output_path = LOGS_DIR / "deep_subconto_join_probe.json"
output_path.write_text(
json.dumps(report, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(f"[ok] saved: {output_path}")
print(
f"[ok] chain A={chain_a_status}, chain F={chain_f_status}, "
f"joined_rows={len(joined_rows)}"
)
if chain_a_status != "derivable" or chain_f_status != "derivable":
print("[warn] Expected derivable statuses were not reached.")
return 2
return 0
if __name__ == "__main__":
raise SystemExit(main())