Fix Client FY specific Accounting Mirror path resolution
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
__version__ = "1.26.17"
|
||||
__version__ = "1.26.18"
|
||||
AGENT_NAME = "ERP Local Agent"
|
||||
|
||||
@@ -167,10 +167,22 @@ class LocalAccountingStore:
|
||||
for path in accounting_clients.glob(f"*/*/{filename}"):
|
||||
candidates.append(path)
|
||||
|
||||
# Earlier/future FY client Accounting locations (for client rename or FY
|
||||
# resolver changes). The active target is excluded below.
|
||||
for path in self.storage_root.glob(f"FY*/Clients/*/*/Accounting/{filename}"):
|
||||
candidates.append(path)
|
||||
# Earlier versions also searched every FY folder for the newest client
|
||||
# database. That is unsafe now that Accounting Mirrors are authoritative
|
||||
# per Client + Financial Year: a 2022-23 database must never be copied into
|
||||
# the 2021-22 location merely because it is newer. Preserve the useful
|
||||
# client-rename recovery behaviour, but restrict it to the SAME FY as the
|
||||
# target path.
|
||||
try:
|
||||
relative_target = target_db.resolve().relative_to(self.storage_root.resolve())
|
||||
fy_segment = relative_target.parts[0] if relative_target.parts else ""
|
||||
except Exception:
|
||||
fy_segment = ""
|
||||
if fy_segment.lower().startswith("fy"):
|
||||
for path in self.storage_root.glob(
|
||||
f"{fy_segment}/Clients/*/*/Accounting/{filename}"
|
||||
):
|
||||
candidates.append(path)
|
||||
|
||||
unique = []
|
||||
seen = set()
|
||||
|
||||
@@ -219,6 +219,20 @@ class AgentCommandProcessor:
|
||||
if client_id in (None, "") or not relative_dir:
|
||||
self.tally.bind_context(None, payload)
|
||||
return
|
||||
|
||||
# Client + Financial Year is the authoritative Accounting Mirror identity.
|
||||
# Do not allow a stale Local-Agent path binding to redirect an FY request to
|
||||
# another year's .act file. ERP supplies paths in the canonical form
|
||||
# FY2022-23/Clients/.../Accounting.
|
||||
requested_fy = str(payload.get("financial_year") or "").strip()
|
||||
first_segment = relative_dir.replace("\\", "/").split("/", 1)[0].strip()
|
||||
path_fy = first_segment[2:] if first_segment.lower().startswith("fy") else ""
|
||||
if requested_fy and path_fy and requested_fy != path_fy:
|
||||
raise ValueError(
|
||||
f"Accounting Mirror FY/path mismatch: ERP requested FY {requested_fy}, "
|
||||
f"but storage path targets FY {path_fy}."
|
||||
)
|
||||
|
||||
self.store.bind_client_path(int(client_id), relative_dir)
|
||||
self.tally.bind_context(int(client_id), payload)
|
||||
|
||||
@@ -4355,7 +4369,30 @@ class AgentCommandProcessor:
|
||||
publish(status="failed", percent=100, stage="Busy", error="Another Accounting Mirror export is already running on this workstation.", finished_at_utc=datetime.now(timezone.utc).isoformat())
|
||||
return
|
||||
try:
|
||||
publish(status="running", percent=2, stage="Starting", message="Starting read-only ODBC export from TallyPrime.")
|
||||
# Re-bind inside the worker thread. The export is asynchronous and the
|
||||
# Local Agent may have served other Client/FY requests after the job was
|
||||
# queued. Rebinding here guarantees that the selected ERP FY controls
|
||||
# the mirror path used by extraction, publication and registry metadata.
|
||||
self._bind_accounting_storage(payload)
|
||||
bound_db = self.store.db_path(client_id)
|
||||
requested_fy = str(payload.get("financial_year") or "").strip()
|
||||
expected_dir = str(payload.get("accounting_relative_dir") or "").strip().replace("\\", "/")
|
||||
actual_path = str(bound_db).replace("\\", "/")
|
||||
if expected_dir and expected_dir.casefold() not in actual_path.casefold():
|
||||
raise RuntimeError(
|
||||
"Accounting Mirror storage binding did not resolve to the ERP-selected Client/FY path. "
|
||||
f"Expected directory '{expected_dir}', resolved '{bound_db}'."
|
||||
)
|
||||
|
||||
publish(
|
||||
status="running", percent=2, stage="Starting",
|
||||
message=(
|
||||
"Starting read-only ODBC export from TallyPrime for "
|
||||
f"FY {requested_fy or 'selected period'} into {bound_db}."
|
||||
),
|
||||
financial_year=requested_fy,
|
||||
mirror_db_path=str(bound_db),
|
||||
)
|
||||
|
||||
def mirror_progress(progress: dict[str, Any]) -> None:
|
||||
updates = dict(progress or {})
|
||||
@@ -4381,6 +4418,8 @@ class AgentCommandProcessor:
|
||||
voucher_period = mirror_summary.get("voucher_period") or {}
|
||||
odbc_from = str(voucher_period.get("from_date") or "").strip()
|
||||
odbc_to = str(voucher_period.get("to_date") or "").strip()
|
||||
# Keep the same Client/FY binding for all post-extraction reads.
|
||||
self._bind_accounting_storage(payload)
|
||||
requested_transactions = self.tally.mirror.transactions(
|
||||
client_id, mirror_company_name, date_from, date_to, tally_guid
|
||||
)
|
||||
@@ -4428,6 +4467,7 @@ class AgentCommandProcessor:
|
||||
)
|
||||
|
||||
publish(status="running", percent=98, stage="Updating client accounting database", message="Refreshing the existing client .act transaction snapshot from the completed Accounting Mirror. Master analysis remains available directly from the mirror without clearing any existing auxiliary masters.", mirror_db_path=str(mirror_result.get("mirror_db_path") or ""), warnings=validation_warnings)
|
||||
self._bind_accounting_storage(payload)
|
||||
transactions = self.tally.mirror.transactions(client_id, mirror_company_name, date_from, date_to, tally_guid)
|
||||
transaction_sync = self.store.replace_transaction_snapshot(
|
||||
client_id, mapping={**mapping, "company_name": mirror_company_name}, transactions=transactions,
|
||||
|
||||
Reference in New Issue
Block a user