Move Tally accounting DB into FY client engagement folder

This commit is contained in:
A R R R Associates
2026-08-20 16:17:17 +05:30
parent 3912d2fd16
commit f7ebd32597
6 changed files with 63 additions and 27 deletions
@@ -1,4 +1,4 @@
ERP Local Agent 1.8.1
ERP Local Agent 1.8.2
Existing storage, WebSocket tunnel, Tally and client .act functionality are preserved.
@@ -42,3 +42,13 @@ Accounting storage policy 1.8.1:
- Canonical layout: <STORAGE_ROOT>\Accounting\Clients\<Letter>\<ExistingClientFolder>\client_XXXXXXXX.act
- Existing databases from <STORAGE_ROOT>\Accounting\client_XXXXXXXX or <ERP_Local_Agent>\data\accounting\client_XXXXXXXX are migrated by SQLite backup on first use.
- Migration sources are retained as recovery copies; no existing .act file is deleted.
Accounting engagement-folder policy 1.8.2:
- Final canonical Accounting DB path follows the existing Engagement hierarchy:
<STORAGE_ROOT>\FY2026-27\Clients\<Letter>\<ExistingClientFolder>\Accounting\client_XXXXXXXX.act
- ERP uses the same sanitize_segment("FY"+financial_year) and client_folder_parts() convention as Engagement storage.
- Current Tally page/mapping/master sync use the current financial year.
- Transaction sync uses the financial year containing date_from.
- Depreciation/approval/write-back use the selected depreciation financial year.
- Existing .act databases from original flat, v1.8.0 Local-Agent-data, and v1.8.1 Accounting\Clients locations are migrated safely by SQLite backup and retained as recovery copies.
- The Accounting folder is the stable base for later Reports, Exports, Workpapers and Backups; this release does not change report-generation behavior.
@@ -1,2 +1,2 @@
__version__ = "1.8.1"
__version__ = "1.8.2"
AGENT_NAME = "ERP Local Agent"
@@ -39,9 +39,9 @@ class LocalAccountingStore:
"""
def __init__(self, storage_root: Path):
# Accounting follows the same branch Storage Node root used by Permanent
# and Engagement storage. The ERP supplies the exact client-relative
# directory built with its shared client_folder_parts() policy.
# Accounting follows the same branch Storage Node and FY/client hierarchy used
# by Engagement storage. ERP supplies FY.../Clients/<letter>/<client>/Accounting
# using the existing sanitize_segment() + client_folder_parts() policy.
self.storage_root = Path(storage_root).resolve()
self.storage_root.mkdir(parents=True, exist_ok=True)
@@ -89,10 +89,20 @@ class LocalAccountingStore:
raise ValueError("Unsafe Accounting storage segment received from ERP.")
safe_parts.append(value)
relative = Path(*safe_parts)
# Accounting paths are intentionally constrained to Accounting/Clients/...
parts_lower = [p.lower() for p in relative.parts]
if len(parts_lower) < 4 or parts_lower[0] != "accounting" or parts_lower[1] != "clients":
raise ValueError("Accounting storage path does not follow the ERP Storage Node client policy.")
# Canonical Accounting path mirrors Engagement storage:
# FY2026-27/Clients/A/CLIENT_FOLDER/Accounting
parts = list(relative.parts)
parts_lower = [p.lower() for p in parts]
if (
len(parts) != 5
or not parts_lower[0].startswith("fy")
or parts_lower[1] != "clients"
or len(parts[2]) != 1
or parts_lower[4] != "accounting"
):
raise ValueError(
"Accounting storage path must follow FY<year>/Clients/<letter>/<client>/Accounting."
)
return relative
def bind_client_path(self, client_id: int, accounting_relative_dir: str) -> Path:
@@ -150,12 +160,17 @@ class LocalAccountingStore:
# Temporary v1.8.0 Local-Agent data location.
candidates.append(self.legacy_agent_root / key / filename)
# If a client was renamed, an earlier canonical client-folder path may exist.
# v1.8.1 interim Accounting/Clients/<letter>/<client> location.
accounting_clients = self.storage_root / "Accounting" / "Clients"
if accounting_clients.exists():
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)
unique = []
seen = set()
for path in candidates:
@@ -201,7 +216,7 @@ class LocalAccountingStore:
# Do not silently create a new Accounting DB in a non-policy location.
# Existing legacy DBs remain discoverable only after ERP supplies its
# canonical client path on the next status/initialize command.
return self.storage_root / "Accounting" / "Clients" / "_UNBOUND" / self._client_key(client_id)
return self.storage_root / "_UNBOUND_ACCOUNTING" / self._client_key(client_id)
return self.storage_root / relative
def db_path(self, client_id: int) -> Path: