Fix cross-FY Accounting Mirror creditor follow-up
This commit is contained in:
@@ -4,7 +4,7 @@ import io
|
||||
from pathlib import Path
|
||||
import zipfile
|
||||
|
||||
ERP_LOCAL_AGENT_VERSION = "1.26.15"
|
||||
ERP_LOCAL_AGENT_VERSION = "1.26.16"
|
||||
ERP_LOCAL_AGENT_NAME = "ERP Local Agent"
|
||||
RUNTIME_ROOT = Path(__file__).resolve().parent / "local_agent_runtime"
|
||||
_DETERMINISTIC_ZIP_TIMESTAMP = (2026, 1, 1, 0, 0, 0)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
__version__ = "1.26.15"
|
||||
__version__ = "1.26.16"
|
||||
AGENT_NAME = "ERP Local Agent"
|
||||
|
||||
@@ -61,6 +61,29 @@ def _company_identity_name(value: Any) -> str:
|
||||
return re.sub(r"\s+", " ", text).strip()
|
||||
|
||||
|
||||
def _company_fy_split_identity_name(value: Any) -> str:
|
||||
"""Normalize an FY-split Tally company name without weakening normal mapping checks.
|
||||
|
||||
Tally users commonly keep a separate company for each financial year and Tally
|
||||
may expose that company as ``NAME - (from 1-Apr-22)`` while the ERP mapping was
|
||||
created against ``NAME Audit``. When ODBC returns only the ACTIVE_COMPANY token
|
||||
there is no usable GUID to compare, so this helper removes only those explicit
|
||||
FY-split/display qualifiers. It is used only when at least one of the compared
|
||||
names carries a ``(from ...)`` marker; ordinary company-name mismatches remain
|
||||
rejected.
|
||||
"""
|
||||
text = _text(value).strip().casefold()
|
||||
text = re.sub(r"\s*[-–—]?\s*\(\s*from\s+[^)]*\)\s*$", "", text).strip()
|
||||
text = re.sub(r"[\s_-]+(?:fy\s*)?\d{2,4}\s*[-/]\s*\d{2,4}\s*$", "", text).strip()
|
||||
text = re.sub(r"\s+audit\s*$", "", text).strip()
|
||||
text = re.sub(r"[^a-z0-9]+", " ", text)
|
||||
return re.sub(r"\s+", " ", text).strip()
|
||||
|
||||
|
||||
def _has_tally_from_period_marker(value: Any) -> bool:
|
||||
return bool(re.search(r"\(\s*from\s+[^)]*\)\s*$", _text(value), flags=re.IGNORECASE))
|
||||
|
||||
|
||||
def _safe_key(value: str) -> str:
|
||||
text = re.sub(r"[^A-Za-z0-9_.-]+", "_", _text(value)).strip("_")
|
||||
return text[:80] or "company"
|
||||
@@ -527,12 +550,35 @@ class AccountingMirrorEngine:
|
||||
if exp_guid and actual_guid_is_placeholder:
|
||||
expected_name_key = _company_identity_name(exp_name)
|
||||
actual_name_key = _company_identity_name(actual_name)
|
||||
if not expected_name_key or not actual_name_key or expected_name_key != actual_name_key:
|
||||
names_match = bool(expected_name_key and actual_name_key and expected_name_key == actual_name_key)
|
||||
|
||||
# FY-split Tally companies can legitimately have a different display
|
||||
# suffix, for example ERP ``RAHMAN RAHIM TRADERS Audit`` versus ODBC
|
||||
# ``RAHMAN RAHIM TRADERS - (from 1-Apr-22)``. Accept that narrow case
|
||||
# only when Tally explicitly exposes a '(from ...)' period marker and
|
||||
# the underlying company-family name still matches. This keeps the
|
||||
# existing safety rejection for unrelated company names.
|
||||
fy_split_match = False
|
||||
if not names_match and (_has_tally_from_period_marker(exp_name) or _has_tally_from_period_marker(actual_name)):
|
||||
expected_family = _company_fy_split_identity_name(exp_name)
|
||||
actual_family = _company_fy_split_identity_name(actual_name)
|
||||
fy_split_match = bool(expected_family and actual_family and expected_family == actual_family)
|
||||
|
||||
if not names_match and not fy_split_match:
|
||||
raise RuntimeError(
|
||||
"Tally ODBC did not return a usable company GUID "
|
||||
f"(returned '{actual_guid or 'blank'}') and the company name could not safely "
|
||||
f"confirm the saved mapping. ERP expected '{exp_name}', ODBC returned '{actual_name}'."
|
||||
)
|
||||
|
||||
if fy_split_match:
|
||||
validation_warnings.append(
|
||||
"Tally ODBC returned a selector token instead of the real company GUID "
|
||||
f"('{actual_guid or 'blank'}'). The mirror was accepted as an FY-split Tally company "
|
||||
f"because ERP '{exp_name}' and ODBC '{actual_name}' resolve to the same base company name. "
|
||||
"The saved ERP GUID was preserved."
|
||||
)
|
||||
else:
|
||||
validation_warnings.append(
|
||||
"Tally ODBC returned a selector token instead of the real company GUID "
|
||||
f"('{actual_guid or 'blank'}'). The mirror was accepted because the normalized "
|
||||
@@ -790,6 +836,13 @@ class AccountingMirrorEngine:
|
||||
current_name = _text(c.get("company_name"))
|
||||
current_guid = _text(c.get("company_guid"))
|
||||
|
||||
# The storage path is already bound by the ERP to one client + one FY.
|
||||
# Analytical queries (including Sundry Creditors follow-up) intentionally
|
||||
# do not need a live/current Tally company. If no company identity was
|
||||
# supplied, the ready mirror in that bound FY folder is authoritative.
|
||||
if current.get("ready") and not requested_guid and not requested_name:
|
||||
return paths.current_mirror
|
||||
|
||||
# GUID is authoritative and survives Tally company renames / FY suffix changes.
|
||||
if current.get("ready"):
|
||||
if requested_guid and current_guid and requested_guid == current_guid:
|
||||
|
||||
Reference in New Issue
Block a user