Fix depreciation mirror fallback and TDS compliance routing
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
__version__ = "1.25.0"
|
||||
__version__ = "1.25.2"
|
||||
AGENT_NAME = "ERP Local Agent"
|
||||
|
||||
@@ -796,9 +796,112 @@ class LocalAccountingStore:
|
||||
# flagged only when fewer than 180 calendar days remain including that date.
|
||||
return ((fy_end - acquired).days + 1) < 180
|
||||
|
||||
@staticmethod
|
||||
def _looks_like_fixed_asset_group(name: str) -> bool:
|
||||
"""Conservative fallback for mirrors that do not expose Tally Group Master.
|
||||
|
||||
The proven ODBC mirror v3.0.1 always contains each ledger's parent group,
|
||||
but intentionally does not depend on the later Group Master TDL that caused
|
||||
the v3.1 loading failure. Prefer the real group hierarchy when available;
|
||||
otherwise recognise standard fixed-asset parent/subgroup names from the
|
||||
mirrored ledger rows themselves.
|
||||
"""
|
||||
value = re.sub(r"[^a-z0-9]+", " ", str(name or "").strip().casefold()).strip()
|
||||
if not value:
|
||||
return False
|
||||
|
||||
exact = {
|
||||
"fixed assets",
|
||||
"fixed asset",
|
||||
"plant machinery",
|
||||
"plant and machinery",
|
||||
"machinery",
|
||||
"furniture fixtures",
|
||||
"furniture and fixtures",
|
||||
"furniture fittings",
|
||||
"furniture and fittings",
|
||||
"office equipment",
|
||||
"office equipments",
|
||||
"computer",
|
||||
"computers",
|
||||
"computer equipment",
|
||||
"computer equipments",
|
||||
"computer peripherals",
|
||||
"computer and peripherals",
|
||||
"vehicles",
|
||||
"vehicle",
|
||||
"motor vehicles",
|
||||
"motor vehicle",
|
||||
"motor cars",
|
||||
"motor car",
|
||||
"building",
|
||||
"buildings",
|
||||
"factory building",
|
||||
"office building",
|
||||
"land",
|
||||
"electrical equipment",
|
||||
"electrical equipments",
|
||||
"tools equipment",
|
||||
"tools and equipment",
|
||||
"factory equipment",
|
||||
"factory equipments",
|
||||
"leasehold improvements",
|
||||
"leasehold improvement",
|
||||
"intangible assets",
|
||||
"intangible asset",
|
||||
"software",
|
||||
"software assets",
|
||||
}
|
||||
if value in exact:
|
||||
return True
|
||||
|
||||
excluded = {
|
||||
"current assets",
|
||||
"current asset",
|
||||
"loans advances asset",
|
||||
"loans and advances asset",
|
||||
"bank accounts",
|
||||
"bank account",
|
||||
"cash in hand",
|
||||
"stock in hand",
|
||||
"sundry debtors",
|
||||
"deposits asset",
|
||||
"investments",
|
||||
}
|
||||
if value in excluded:
|
||||
return False
|
||||
|
||||
# Common descriptive subgroup names. Keep this deliberately narrower
|
||||
# than a generic "contains asset" test so current assets are not pulled
|
||||
# into the depreciation schedule.
|
||||
keywords = (
|
||||
"plant machinery",
|
||||
"plant and machinery",
|
||||
"furniture fixture",
|
||||
"office equipment",
|
||||
"computer equipment",
|
||||
"computer peripheral",
|
||||
"motor vehicle",
|
||||
"motor car",
|
||||
"factory equipment",
|
||||
"electrical equipment",
|
||||
"leasehold improvement",
|
||||
"fixed asset",
|
||||
)
|
||||
return any(token in value for token in keywords)
|
||||
|
||||
def _fixed_asset_group_names(self, db: sqlite3.Connection, tally_guid: str) -> set[str]:
|
||||
rows = db.execute("SELECT name, parent FROM tally_groups WHERE tally_guid=?", (tally_guid,)).fetchall()
|
||||
all_groups = {str(r["name"] or "").strip(): str(r["parent"] or "").strip() for r in rows}
|
||||
rows = db.execute(
|
||||
"SELECT name, parent FROM tally_groups WHERE tally_guid=?",
|
||||
(tally_guid,),
|
||||
).fetchall()
|
||||
all_groups = {
|
||||
str(r["name"] or "").strip(): str(r["parent"] or "").strip()
|
||||
for r in rows
|
||||
if str(r["name"] or "").strip()
|
||||
}
|
||||
|
||||
# Preferred path: full Tally group hierarchy is available.
|
||||
fixed = {name for name in all_groups if name.casefold() == "fixed assets"}
|
||||
changed = True
|
||||
while changed:
|
||||
@@ -806,8 +909,24 @@ class LocalAccountingStore:
|
||||
fixed_fold = {x.casefold() for x in fixed}
|
||||
for name, parent in all_groups.items():
|
||||
if name not in fixed and parent.casefold() in fixed_fold:
|
||||
fixed.add(name); changed = True
|
||||
return fixed
|
||||
fixed.add(name)
|
||||
changed = True
|
||||
if fixed:
|
||||
return fixed
|
||||
|
||||
# Proven ODBC mirror fallback: group_master may intentionally be absent,
|
||||
# but every mirrored ledger still carries its parent group.
|
||||
ledger_parents = db.execute(
|
||||
"""SELECT DISTINCT TRIM(COALESCE(parent,'')) AS parent
|
||||
FROM tally_ledgers
|
||||
WHERE tally_guid=? AND TRIM(COALESCE(parent,''))<>''""",
|
||||
(tally_guid,),
|
||||
).fetchall()
|
||||
return {
|
||||
str(row["parent"] or "").strip()
|
||||
for row in ledger_parents
|
||||
if self._looks_like_fixed_asset_group(str(row["parent"] or ""))
|
||||
}
|
||||
|
||||
def _transaction_coverage(self, db: sqlite3.Connection, tally_guid: str, fy_start: date, fy_end: date) -> dict:
|
||||
today = date.today()
|
||||
@@ -857,7 +976,7 @@ class LocalAccountingStore:
|
||||
with self.connect(client_id) as db:
|
||||
fixed_groups = self._fixed_asset_group_names(db, tally_guid)
|
||||
if not fixed_groups:
|
||||
raise ValueError("Fixed Assets group was not found in synchronized Tally masters. Synchronize Phase 3 masters first.")
|
||||
raise ValueError("No Fixed Asset ledger/group could be identified in the local Accounting Mirror. Verify that fixed-asset ledgers are grouped under Fixed Assets (or a standard fixed-asset subgroup) in Tally, then refresh the mirror.")
|
||||
ledgers = db.execute(
|
||||
"SELECT master_guid, name, parent, opening_balance, closing_balance, payload_json FROM tally_ledgers WHERE tally_guid=? ORDER BY name COLLATE NOCASE",
|
||||
(tally_guid,),
|
||||
|
||||
Reference in New Issue
Block a user