Show Tally data and mirror paths on engagement workspace
This commit is contained in:
@@ -6,9 +6,10 @@ from typing import Any, Iterable
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.modules.documents.models import DocumentStorageJob
|
||||
from app.modules.documents.models import BranchStorageNode, DocumentStorageJob, EngagementDocument
|
||||
from app.modules.documents.services import client_folder_parts, get_active_storage_node_for_branch, sanitize_segment
|
||||
from app.modules.services.models import FirmServiceTaskTemplate, ServiceTaskCategory
|
||||
from app.modules.accounting.accounting_mirror_models import AccountingMirrorRegistry
|
||||
|
||||
|
||||
|
||||
@@ -61,6 +62,81 @@ def _latest_local_final_path(db, version: Any) -> str:
|
||||
return str(getattr(job, "local_final_path", "") or "").strip() if job else ""
|
||||
|
||||
|
||||
def _current_mirror_for_year(db, *, tenant_id: int, client_id: int, financial_year: str):
|
||||
if not tenant_id or not client_id or not financial_year:
|
||||
return None
|
||||
return db.execute(
|
||||
select(AccountingMirrorRegistry).where(
|
||||
AccountingMirrorRegistry.tenant_id == int(tenant_id),
|
||||
AccountingMirrorRegistry.client_id == int(client_id),
|
||||
AccountingMirrorRegistry.financial_year == str(financial_year).strip(),
|
||||
AccountingMirrorRegistry.is_current.is_(True),
|
||||
AccountingMirrorRegistry.is_active.is_(True),
|
||||
AccountingMirrorRegistry.status == "active",
|
||||
).order_by(AccountingMirrorRegistry.version_no.desc(), AccountingMirrorRegistry.id.desc()).limit(1)
|
||||
).scalar_one_or_none()
|
||||
|
||||
|
||||
def _mirror_row(db, *, tenant_id: int, client_id: int, financial_year: str) -> dict[str, Any]:
|
||||
mirror = _current_mirror_for_year(
|
||||
db, tenant_id=tenant_id, client_id=client_id, financial_year=financial_year
|
||||
)
|
||||
if not mirror:
|
||||
return {
|
||||
"available": False,
|
||||
"financial_year": financial_year,
|
||||
"local_path": "",
|
||||
"relative_path": "",
|
||||
"version_no": 0,
|
||||
"company_name": "",
|
||||
"last_synced_at_utc": None,
|
||||
}
|
||||
|
||||
relative_path = (Path(str(mirror.accounting_relative_dir or "")) / str(mirror.mirror_file_name or "")).as_posix()
|
||||
local_path = ""
|
||||
node = db.get(BranchStorageNode, int(mirror.storage_node_id)) if getattr(mirror, "storage_node_id", None) else None
|
||||
if node:
|
||||
root = str(getattr(node, "storage_root_path", "") or "").strip()
|
||||
local_path = _join_local_path(root, relative_path) if root else ""
|
||||
|
||||
return {
|
||||
"available": True,
|
||||
"financial_year": financial_year,
|
||||
"local_path": local_path,
|
||||
"relative_path": relative_path,
|
||||
"version_no": int(getattr(mirror, "version_no", 0) or 0),
|
||||
"company_name": str(getattr(mirror, "company_name", "") or ""),
|
||||
"last_synced_at_utc": getattr(mirror, "last_synced_at_utc", None),
|
||||
"storage_node_name": str(getattr(node, "node_name", "") or "") if node else "",
|
||||
}
|
||||
|
||||
|
||||
def _tally_row_from_document(db, doc: Any, storage_root_path: str) -> dict[str, Any] | None:
|
||||
tally_year = _tally_document_year(doc)
|
||||
if not tally_year:
|
||||
return None
|
||||
versions = list(getattr(doc, "versions", None) or [])
|
||||
latest = versions[0] if versions else None
|
||||
latest_relative_path = str(getattr(latest, "local_relative_path", "") or "") if latest else ""
|
||||
local_final_path = _latest_local_final_path(db, latest) if latest else ""
|
||||
# local_final_path is already the workstation/storage-node acknowledged final
|
||||
# destination. For extracted ZIP/TAR Tally uploads it is the directory that
|
||||
# can be pasted into TallyPrime, so never prepend storage_root_path to it.
|
||||
local_path = local_final_path or (_join_local_path(storage_root_path, latest_relative_path) if latest_relative_path else "")
|
||||
return {
|
||||
"id": int(getattr(doc, "id", 0) or 0),
|
||||
"engagement_id": int(getattr(doc, "engagement_id", 0) or 0),
|
||||
"title": str(getattr(doc, "title", "") or "Tally Data"),
|
||||
"financial_year": tally_year,
|
||||
"version_no": int(getattr(doc, "current_version_no", 0) or 0),
|
||||
"latest_filename": str(getattr(latest, "original_filename", "") or "") if latest else "",
|
||||
"latest_relative_path": latest_relative_path,
|
||||
"local_final_path": local_final_path,
|
||||
"local_path": local_path,
|
||||
"storage_status": str(getattr(latest, "storage_status", "") or "") if latest else "",
|
||||
}
|
||||
|
||||
|
||||
def _task_category_options(db, engagement, tasks: list[Any]) -> list[dict[str, Any]]:
|
||||
"""Return the service's real task-category master mapped to this engagement's tasks.
|
||||
|
||||
@@ -198,6 +274,7 @@ def build_engagement_resource_context(db, engagement, tasks: Iterable[Any], docu
|
||||
accounting_local_path = ""
|
||||
storage_root_path = ""
|
||||
storage_node_name = ""
|
||||
tenant_id = int(getattr(engagement, "tenant_id", 0) or 0)
|
||||
if client and client_id and fy:
|
||||
fy_folder = sanitize_segment(f"FY{fy}", "FY")
|
||||
letter, client_folder = client_folder_parts(client, client_id)
|
||||
@@ -206,7 +283,7 @@ def build_engagement_resource_context(db, engagement, tasks: Iterable[Any], docu
|
||||
accounting_relative_path = (relative_dir / f"client_{client_id:08d}.act").as_posix()
|
||||
node = get_active_storage_node_for_branch(
|
||||
db,
|
||||
int(getattr(engagement, "tenant_id", 0) or 0),
|
||||
tenant_id,
|
||||
getattr(engagement, "branch_id", None),
|
||||
)
|
||||
if node:
|
||||
@@ -214,6 +291,36 @@ def build_engagement_resource_context(db, engagement, tasks: Iterable[Any], docu
|
||||
storage_node_name = str(getattr(node, "node_name", "") or "").strip()
|
||||
accounting_local_path = _join_local_path(storage_root_path, accounting_relative_path)
|
||||
|
||||
# Client/FY accounting resources are shared across engagements for the same
|
||||
# client. This lets Tax Audit, Statutory Audit, ITR, etc. reuse the same Tally
|
||||
# dataset and registered mirror instead of requiring duplicate uploads.
|
||||
client_tally_documents: list[Any] = []
|
||||
if tenant_id and client_id and tally_year_options:
|
||||
client_tally_documents = db.execute(
|
||||
select(EngagementDocument).where(
|
||||
EngagementDocument.tenant_id == tenant_id,
|
||||
EngagementDocument.client_id == client_id,
|
||||
EngagementDocument.is_deleted.is_(False),
|
||||
EngagementDocument.description.like(f"{_TALLY_MARKER_PREFIX}%"),
|
||||
EngagementDocument.financial_year.in_(tally_year_options),
|
||||
).order_by(EngagementDocument.updated_at_utc.desc(), EngagementDocument.id.desc())
|
||||
).scalars().unique().all()
|
||||
|
||||
client_tally_by_fy: dict[str, dict[str, Any]] = {}
|
||||
for tally_doc in client_tally_documents:
|
||||
row = _tally_row_from_document(db, tally_doc, storage_root_path)
|
||||
if row and row["financial_year"] not in client_tally_by_fy:
|
||||
client_tally_by_fy[row["financial_year"]] = row
|
||||
|
||||
accounting_years: list[dict[str, Any]] = []
|
||||
for year in tally_year_options:
|
||||
accounting_years.append({
|
||||
"financial_year": year,
|
||||
"is_engagement_year": year == fy,
|
||||
"tally": client_tally_by_fy.get(year),
|
||||
"mirror": _mirror_row(db, tenant_id=tenant_id, client_id=client_id, financial_year=year),
|
||||
})
|
||||
|
||||
categories = _task_category_options(db, engagement, tasks)
|
||||
default_upload_task_id = next((row["task_id"] for row in categories if row.get("task_id")), None)
|
||||
|
||||
@@ -241,10 +348,11 @@ def build_engagement_resource_context(db, engagement, tasks: Iterable[Any], docu
|
||||
}
|
||||
document_options.append(row)
|
||||
if tally_year:
|
||||
tally_local_path = local_final_path or (_join_local_path(storage_root_path, latest_relative_path) if latest_relative_path else "")
|
||||
tally_data_files.append({
|
||||
**row,
|
||||
"financial_year": tally_year,
|
||||
"local_path": _join_local_path(storage_root_path, displayed_relative_path) if displayed_relative_path else "",
|
||||
"local_path": tally_local_path,
|
||||
})
|
||||
|
||||
tally_data_files.sort(key=lambda row: row.get("financial_year", ""), reverse=True)
|
||||
@@ -256,6 +364,7 @@ def build_engagement_resource_context(db, engagement, tasks: Iterable[Any], docu
|
||||
"storage_root_path": storage_root_path,
|
||||
"storage_node_name": storage_node_name,
|
||||
"tally_year_options": tally_year_options,
|
||||
"accounting_years": accounting_years,
|
||||
"tally_data_files": tally_data_files,
|
||||
"task_categories": categories,
|
||||
"default_upload_task_id": default_upload_task_id,
|
||||
|
||||
Reference in New Issue
Block a user