from __future__ import annotations from dataclasses import dataclass from urllib.parse import urlencode @dataclass(frozen=True) class TaskToolDefinition: code: str name: str description: str launch_url: str | None = None _TASK_TOOLS: tuple[TaskToolDefinition, ...] = ( TaskToolDefinition(code="NONE", name="None", description="Normal task workflow without an attached ERP tool."), TaskToolDefinition( code="ACCOUNTING_OPENING_BALANCE", name="Opening Balance Review", description="Launch the existing Tally opening-balance comparison and verification tool.", launch_url="/tools/accounting/opening-balances", ), TaskToolDefinition( code="ACCOUNTING_BANK_RECONCILIATION", name="Bank Reconciliation", description="Launch the existing accounting bank-reconciliation workspace.", launch_url="/tools/accounting/bank-reconciliation", ), TaskToolDefinition( code="ACCOUNTING_DEPRECIATION_IT", name="Depreciation (IT)", description="Launch the existing Income-tax depreciation workspace connected to Tally.", launch_url="/tools/tally/depreciation", ), TaskToolDefinition( code="ACCOUNTING_CASH_PAYMENT_COMPLIANCE", name="Cash Payment Tax Compliance Review", description="Review Tally cash payments against the configurable limit, same-day aggregation and possible split-payment patterns.", launch_url="/tools/accounting/cash-payments", ), TaskToolDefinition( code="ACCOUNTING_TDS_COMPLIANCE", name="TDS Compliance Review", description="Review expenses, purchases, capital purchases and creditor transactions in Tally for TDS applicability, threshold crossing and short/non-deduction.", launch_url="/tools/accounting/tds/compliance", ), TaskToolDefinition( code="ACCOUNTING_TDS_RECONCILIATION", name="TDS Reconciliation", description="Compare Tally TDS liability with return/form, challan and TRACES data.", launch_url="/tools/accounting/tds/reconciliation", ), ) def list_task_tools() -> list[TaskToolDefinition]: return list(_TASK_TOOLS) def get_task_tool(code: str | None) -> TaskToolDefinition: normalized = normalize_task_tool_code(code) for tool in _TASK_TOOLS: if tool.code == normalized: return tool return _TASK_TOOLS[0] def normalize_task_tool_code(code: str | None) -> str: value = (code or "NONE").strip().upper() or "NONE" valid = {tool.code for tool in _TASK_TOOLS} if value not in valid: raise ValueError(f"Unsupported task tool: {value}") return value def build_task_tool_launch_url( tool: TaskToolDefinition, *, client_id: int, task_id: int, tool_run_id: int, ) -> str: if not tool.launch_url: raise ValueError("This ERP task tool does not expose a launch URL.") query = urlencode( { "client_id": int(client_id), "task_id": int(task_id), "task_tool_run_id": int(tool_run_id), "task_return_url": f"/services/work-tracker/tasks/{int(task_id)}/edit", "task_result_url": f"/services/work-tracker/tool-runs/{int(tool_run_id)}/result", } ) separator = "&" if "?" in tool.launch_url else "?" return f"{tool.launch_url}{separator}{query}"