Move Tally accounting DB into FY client engagement folder
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user