Add Phase 5 Income-tax depreciation tool
This commit is contained in:
@@ -8,7 +8,7 @@ import os
|
||||
import sqlite3
|
||||
from typing import Iterator, Sequence
|
||||
|
||||
ACT_SCHEMA_VERSION = 4
|
||||
ACT_SCHEMA_VERSION = 5
|
||||
|
||||
|
||||
class AccountingActStoreError(RuntimeError):
|
||||
@@ -32,7 +32,8 @@ class AccountingActStore:
|
||||
Phase 1 provides accounting storage and read-only Tally discovery.
|
||||
Phase 2 adds durable client/registration -> Tally company mapping keyed by
|
||||
Tally GUID. Phase 3 adds read-only Tally accounting master snapshot tables.
|
||||
Phase 4 adds read-only voucher/transaction tables. Tally write-back remains
|
||||
Phase 4 adds read-only voucher/transaction tables. Phase 5 adds draft-only
|
||||
Income-tax depreciation rules and calculation tables. Tally write-back remains
|
||||
out of scope.
|
||||
"""
|
||||
|
||||
@@ -199,7 +200,27 @@ class AccountingActStore:
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_tally_vouchers_company_date ON tally_vouchers(tally_guid, voucher_date);
|
||||
CREATE INDEX IF NOT EXISTS ix_tally_voucher_ledger_entries_voucher ON tally_voucher_ledger_entries(voucher_id, line_no);
|
||||
CREATE INDEX IF NOT EXISTS ix_tally_voucher_inventory_entries_voucher ON tally_voucher_inventory_entries(voucher_id, line_no);
|
||||
CREATE INDEX IF NOT EXISTS ix_tally_bank_allocations_ledger_entry ON tally_bank_allocations(ledger_entry_id, line_no);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS it_depreciation_rate_rules (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT, tally_guid TEXT NOT NULL, ledger_key TEXT NOT NULL, ledger_guid TEXT NOT NULL DEFAULT '',
|
||||
ledger_name TEXT NOT NULL, rate_percent REAL NOT NULL DEFAULT 0, updated_by_user_id INTEGER, updated_at_utc TEXT NOT NULL,
|
||||
UNIQUE(tally_guid, ledger_key)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS it_depreciation_runs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT, tally_guid TEXT NOT NULL, company_name TEXT NOT NULL, mapping_id INTEGER,
|
||||
fy_start TEXT NOT NULL, fy_end TEXT NOT NULL, depreciation_expense_ledger TEXT NOT NULL DEFAULT '', depreciation_reserve_ledger TEXT NOT NULL DEFAULT '',
|
||||
requested_by_user_id INTEGER, status TEXT NOT NULL DEFAULT 'draft', created_at_utc TEXT NOT NULL, total_depreciation REAL NOT NULL DEFAULT 0, details_json TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS it_depreciation_run_lines (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT, run_id INTEGER NOT NULL, line_no INTEGER NOT NULL, ledger_key TEXT NOT NULL, ledger_guid TEXT NOT NULL DEFAULT '',
|
||||
ledger_name TEXT NOT NULL, parent_group TEXT NOT NULL DEFAULT '', opening_wdv REAL NOT NULL DEFAULT 0, additions_full_rate REAL NOT NULL DEFAULT 0,
|
||||
additions_half_rate REAL NOT NULL DEFAULT 0, reductions REAL NOT NULL DEFAULT 0, rate_percent REAL NOT NULL DEFAULT 0, depreciation_amount REAL NOT NULL DEFAULT 0,
|
||||
closing_wdv REAL NOT NULL DEFAULT 0, payload_json TEXT NOT NULL DEFAULT '{}', FOREIGN KEY(run_id) REFERENCES it_depreciation_runs(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_it_dep_rate_rules_company ON it_depreciation_rate_rules(tally_guid, ledger_name);
|
||||
CREATE INDEX IF NOT EXISTS ix_it_dep_runs_company_fy ON it_depreciation_runs(tally_guid, fy_start, fy_end, id);
|
||||
CREATE INDEX IF NOT EXISTS ix_it_dep_lines_run ON it_depreciation_run_lines(run_id, line_no);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_tally_groups_company ON tally_groups(tally_guid, name);
|
||||
CREATE INDEX IF NOT EXISTS ix_tally_ledgers_company ON tally_ledgers(tally_guid, name);
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
{% set agent = live_result.agent if live_result else None %}
|
||||
{% set tally = live_result.tally if live_result else None %}
|
||||
{% set accounting = live_result.accounting if live_result else None %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-brand-600">Tools · Tally · Phase 5</p>
|
||||
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Depreciation (Income Tax)</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Review synchronized Fixed Assets, assign rates ledger-wise and generate a draft depreciation calculation. No entry is posted to Tally.</p>
|
||||
</div>
|
||||
<a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}&refresh=1{% endif %}" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Back to Tally</a>
|
||||
</div>
|
||||
|
||||
{% if calculated %}<div class="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm font-medium text-emerald-800">Income-tax depreciation draft calculated and saved in the client accounting database. Nothing was posted to Tally.</div>{% endif %}
|
||||
{% if command_error %}<div class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">{{ command_error }}</div>{% endif %}
|
||||
|
||||
<section class="grid gap-4 md:grid-cols-3">
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">ERP Local Agent</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if agent_online else 'text-red-700' }}">{{ 'Connected' if agent_online else 'Offline' }}</div><div class="mt-1 text-xs text-slate-500">Agent {{ agent.version if agent and agent.version else '-' }}</div></div>
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">TallyPrime</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if tally and tally.connected else 'text-slate-700' }}">{{ 'Connected' if tally and tally.connected else 'Not connected' }}</div><div class="mt-1 text-xs text-slate-500">{{ tally.company_count if tally else 0 }} currently loaded</div></div>
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Phase 5</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if agent and agent.it_depreciation_capability else 'text-slate-700' }}">{{ 'Available' if agent and agent.it_depreciation_capability else 'Update Agent' }}</div><div class="mt-1 text-xs text-slate-500">Draft calculation only</div></div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<form method="get" action="/tools/tally/depreciation" class="grid gap-4 lg:grid-cols-4 lg:items-end">
|
||||
<div><label class="text-sm font-medium text-slate-700">ERP Client</label><select name="client_id" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" onchange="this.form.submit()"><option value="">Select client</option>{% for client in clients %}<option value="{{ client.id }}" {% if selected_client and selected_client.id == client.id %}selected{% endif %}>{{ client.client_name }}</option>{% endfor %}</select></div>
|
||||
<div><label class="text-sm font-medium text-slate-700">Currently Open in TallyPrime</label><select name="tally_guid" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" {% if not selected_client %}disabled{% endif %}><option value="">Select mapped open company</option>{% if tally %}{% for company in tally.companies or [] %}<option value="{{ company.guid }}" {% if selected_tally_guid == company.guid %}selected{% endif %}>{{ company.name }}{% if company.gstin %} · {{ company.gstin }}{% endif %}</option>{% endfor %}{% endif %}</select></div>
|
||||
<div><label class="text-sm font-medium text-slate-700">Financial Year</label><div class="mt-1 grid grid-cols-2 gap-2"><input type="date" name="fy_start" value="{{ fy_start }}" class="w-full rounded-xl border border-slate-300 px-3 py-2"><input type="date" name="fy_end" value="{{ fy_end }}" class="w-full rounded-xl border border-slate-300 px-3 py-2"></div></div>
|
||||
<button type="submit" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Load Fixed Assets</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
{% if preview %}
|
||||
{% set coverage = preview.transaction_coverage %}
|
||||
<div class="rounded-xl border {{ 'border-emerald-200 bg-emerald-50 text-emerald-800' if coverage.complete else 'border-amber-200 bg-amber-50 text-amber-900' }} px-4 py-3 text-sm">
|
||||
{% if coverage.complete %}Phase 4 transaction data completely covers {{ preview.fy_start }} to {{ preview.fy_end }}.{% else %}Phase 4 transaction data does not fully cover this financial year. Synchronize the missing period before calculation.{% endif %}
|
||||
</div>
|
||||
|
||||
<form method="post" action="/tools/tally/depreciation/calculate" class="space-y-5">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}"><input type="hidden" name="tally_guid" value="{{ selected_tally_guid }}"><input type="hidden" name="fy_start" value="{{ fy_start }}"><input type="hidden" name="fy_end" value="{{ fy_end }}">
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div><label class="text-sm font-medium text-slate-700">Depreciation Expense Ledger</label><select name="depreciation_expense_ledger" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Depreciation</option>{% for name in preview.all_ledgers %}<option value="{{ name }}">{{ name }}</option>{% endfor %}</select></div>
|
||||
<div><label class="text-sm font-medium text-slate-700">Depreciation Reserve / Credit Ledger</label><select name="depreciation_reserve_ledger" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Depreciation Reserve</option>{% for name in preview.all_ledgers %}<option value="{{ name }}">{{ name }}</option>{% endfor %}</select></div>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-slate-500">These ledger selections are stored with the draft for the proposed journal. Phase 5 does not create or post any Tally voucher.</p>
|
||||
</section>
|
||||
|
||||
<section class="overflow-hidden rounded-2xl bg-white shadow-soft">
|
||||
<div class="border-b border-slate-200 px-5 py-4"><h2 class="font-semibold text-slate-900">Fixed Asset Ledgers</h2><p class="mt-1 text-sm text-slate-500">Rate is selected by the reviewer. Transaction date is used only as a review proxy for the 180-day put-to-use test; verify where actual put-to-use differs.</p></div>
|
||||
<div class="overflow-x-auto"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500"><tr><th class="px-4 py-3">Ledger</th><th class="px-4 py-3 text-right">Opening WDV</th><th class="px-4 py-3 text-right">Additions ≥180 days</th><th class="px-4 py-3 text-right">Additions <180 days</th><th class="px-4 py-3 text-right">Reductions</th><th class="px-4 py-3">Rate</th></tr></thead><tbody class="divide-y divide-slate-100">
|
||||
{% for row in preview.fixed_asset_ledgers %}<tr><td class="px-4 py-3"><div class="font-medium text-slate-900">{{ row.ledger_name }}</div><div class="text-xs text-slate-500">{{ row.parent_group }}</div></td><td class="px-4 py-3 text-right">{{ '%.2f'|format(row.opening_wdv) }}</td><td class="px-4 py-3 text-right">{{ '%.2f'|format(row.additions_full_rate) }}</td><td class="px-4 py-3 text-right">{{ '%.2f'|format(row.additions_half_rate) }}</td><td class="px-4 py-3 text-right">{{ '%.2f'|format(row.reductions) }}</td><td class="px-4 py-3"><select name="rate__{{ row.ledger_key }}" class="rounded-lg border border-slate-300 px-2 py-1.5"><option value="0" {% if row.rate_percent == 0 %}selected{% endif %}>0%</option>{% for rate in [5,10,15,20,25,30,40,45,60,80,100] %}<option value="{{ rate }}" {% if row.rate_percent == rate %}selected{% endif %}>{{ rate }}%</option>{% endfor %}</select></td></tr>{% else %}<tr><td colspan="6" class="px-4 py-8 text-center text-slate-500">No ledgers under Fixed Assets were found in the synchronized master snapshot.</td></tr>{% endfor %}
|
||||
</tbody></table></div>
|
||||
<div class="flex justify-end border-t border-slate-200 px-5 py-4"><button type="submit" {% if not coverage.complete or not preview.fixed_asset_ledgers %}disabled{% endif %} class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-50">Calculate & Save Draft</button></div>
|
||||
</section>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if depreciation_run %}
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft"><div class="flex flex-wrap justify-between gap-4"><div><h2 class="font-semibold text-slate-900">Draft Calculation #{{ depreciation_run.id }}</h2><p class="mt-1 text-sm text-slate-500">{{ depreciation_run.company_name }} · {{ depreciation_run.fy_start }} to {{ depreciation_run.fy_end }}</p></div><div class="text-right"><div class="text-xs uppercase tracking-wide text-slate-500">Total Depreciation</div><div class="text-2xl font-semibold text-slate-900">₹ {{ '%.2f'|format(depreciation_run.total_depreciation) }}</div></div></div>
|
||||
<div class="mt-4 rounded-xl border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-800">Proposed only: Debit {{ depreciation_run.depreciation_expense_ledger or 'Depreciation' }} and credit {{ depreciation_run.depreciation_reserve_ledger or 'Depreciation Reserve' }} for ₹ {{ '%.2f'|format(depreciation_run.total_depreciation) }}. Tally write-back remains disabled.</div>
|
||||
<div class="mt-4 overflow-x-auto"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left text-xs uppercase text-slate-500"><tr><th class="px-3 py-2">Ledger</th><th class="px-3 py-2 text-right">Rate</th><th class="px-3 py-2 text-right">Depreciation</th><th class="px-3 py-2 text-right">Closing WDV</th></tr></thead><tbody class="divide-y divide-slate-100">{% for row in depreciation_run.lines %}<tr><td class="px-3 py-2">{{ row.ledger_name }}</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.rate_percent) }}%</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.depreciation_amount) }}</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.closing_wdv) }}</td></tr>{% endfor %}</tbody></table></div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<div class="rounded-xl border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-800">Phase 5 is review/calculation only. Phase 6 will add controlled, approved Tally write-back.</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -9,10 +9,13 @@
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-brand-600">Tools · Accounting</p>
|
||||
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Tally Accounting Masters</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Phase 3 permanently maps an ERP client or registration to a loaded TallyPrime company using the Tally GUID.</p>
|
||||
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Tally Accounting</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Company mapping, master sync and transaction sync remain available. Phase 5 adds review-only accounting tools without Tally write-back.</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% if selected_client %}<a href="/tools/tally/depreciation?client_id={{ selected_client.id }}" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Depreciation (IT)</a>{% endif %}
|
||||
<a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}&refresh=1{% else %}?refresh=1{% endif %}" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white shadow-soft hover:bg-brand-700">Refresh Tally Companies</a>
|
||||
</div>
|
||||
<a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}&refresh=1{% else %}?refresh=1{% endif %}" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white shadow-soft hover:bg-brand-700">Refresh Tally Companies</a>
|
||||
</div>
|
||||
|
||||
{% if initialized %}
|
||||
|
||||
@@ -469,3 +469,77 @@ def sync_tally_transactions(
|
||||
return RedirectResponse(url=f"/tools/tally?client_id={client.id}&refresh=1&transactions_synced=1", status_code=303)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@router.get("/depreciation")
|
||||
def depreciation_it_tool(
|
||||
request: Request,
|
||||
client_id: int | None = None,
|
||||
tally_guid: str = "",
|
||||
fy_start: str = "",
|
||||
fy_end: str = "",
|
||||
calculated: int = 0,
|
||||
run_id: int | None = None,
|
||||
error: str = "",
|
||||
):
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user, response = _require_partner(request, db, "accounting.tally.depreciation_it")
|
||||
if response: return response
|
||||
clients, scope = _visible_clients(db, request, user)
|
||||
selected_client = next((row for row in clients if client_id and int(row.id) == int(client_id)), None)
|
||||
node = get_active_storage_node_for_branch(db, scope.tenant_id, scope.branch_id)
|
||||
online = _node_online(node)
|
||||
today = date.today(); start_year = today.year if today.month >= 4 else today.year - 1
|
||||
start_text = fy_start or date(start_year,4,1).isoformat(); end_text = fy_end or date(start_year+1,3,31).isoformat()
|
||||
live_result = None; preview = None; depreciation_run = None; command_error = error or ""
|
||||
if selected_client and node and online:
|
||||
try:
|
||||
status_response = request_agent_command(node.node_code,"phase5_status",{"client_id":int(selected_client.id)},timeout_seconds=20)
|
||||
if status_response.get("ok"): live_result=status_response.get("result") or {}
|
||||
else: command_error=str(status_response.get("error") or "Local Agent status failed.")
|
||||
chosen_guid=str(tally_guid or "").strip()
|
||||
if chosen_guid:
|
||||
preview_response=request_agent_command(node.node_code,"accounting_depreciation_preview",{"client_id":int(selected_client.id),"tally_guid":chosen_guid,"fy_start":start_text,"fy_end":end_text},timeout_seconds=60)
|
||||
if preview_response.get("ok"): preview=(preview_response.get("result") or {}).get("preview")
|
||||
else: command_error=str(preview_response.get("error") or "Depreciation preview failed.")
|
||||
if run_id:
|
||||
run_response=request_agent_command(node.node_code,"accounting_get_it_depreciation_run",{"client_id":int(selected_client.id),"run_id":int(run_id)},timeout_seconds=30)
|
||||
if run_response.get("ok"): depreciation_run=(run_response.get("result") or {}).get("depreciation")
|
||||
except Exception as exc: command_error=str(exc)
|
||||
base={"request":request,"current_user":user,"current_user_roles":get_user_roles(db,user.id),"current_user_permissions":get_user_permissions(db,user.id),"csrf_token":get_or_create_csrf_token(request)}
|
||||
base.update({"title":"Income-tax Depreciation","clients":clients,"selected_client":selected_client,"storage_node":node,"agent_online":online,"live_result":live_result,"preview":preview,"selected_tally_guid":str(tally_guid or ""),"fy_start":start_text,"fy_end":end_text,"calculated":bool(calculated),"depreciation_run":depreciation_run,"command_error":command_error})
|
||||
return templates.TemplateResponse("modules/accounting/templates/accounting/depreciation_it.html",base)
|
||||
finally: db.close()
|
||||
|
||||
|
||||
@router.post("/depreciation/calculate")
|
||||
async def calculate_it_depreciation(request: Request):
|
||||
form = await request.form()
|
||||
csrf_token = str(form.get("csrf_token") or ""); validate_csrf(request, csrf_token)
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user, response = _require_partner(request, db, "accounting.tally.depreciation_it")
|
||||
if response: return response
|
||||
try: client_id=int(form.get("client_id")); start=date.fromisoformat(str(form.get("fy_start") or "")); end=date.fromisoformat(str(form.get("fy_end") or ""))
|
||||
except Exception as exc: return RedirectResponse(url=f"/tools/tally/depreciation?error={quote('Invalid client or financial-year dates.')}",status_code=303)
|
||||
client, _clients, scope = _find_visible_client(db, request, user, client_id)
|
||||
if not client: return _denied()
|
||||
tally_guid=str(form.get("tally_guid") or "").strip()
|
||||
assignments=[]
|
||||
for key,value in form.multi_items():
|
||||
if not str(key).startswith("rate__"): continue
|
||||
ledger_key=str(key)[6:]
|
||||
try: rate=float(str(value or "0").strip() or 0)
|
||||
except Exception: rate=-1
|
||||
assignments.append({"ledger_key":ledger_key,"rate_percent":rate})
|
||||
node=get_active_storage_node_for_branch(db,scope.tenant_id,scope.branch_id)
|
||||
if not node or not _node_online(node): return RedirectResponse(url=f"/tools/tally/depreciation?client_id={client.id}&tally_guid={quote(tally_guid)}&fy_start={start.isoformat()}&fy_end={end.isoformat()}&error={quote('ERP Local Agent is offline for the active branch.')}",status_code=303)
|
||||
try:
|
||||
result=request_agent_command(node.node_code,"accounting_calculate_it_depreciation",{"client_id":int(client.id),"tally_guid":tally_guid,"fy_start":start.isoformat(),"fy_end":end.isoformat(),"assignments":assignments,"depreciation_expense_ledger":str(form.get("depreciation_expense_ledger") or ""),"depreciation_reserve_ledger":str(form.get("depreciation_reserve_ledger") or ""),"requested_by_user_id":int(user.id)},timeout_seconds=120)
|
||||
if not result.get("ok"): raise RuntimeError(str(result.get("error") or "Income-tax depreciation calculation failed."))
|
||||
dep=(result.get("result") or {}).get("depreciation") or {}; rid=int(dep.get("run_id"))
|
||||
except Exception as exc:
|
||||
return RedirectResponse(url=f"/tools/tally/depreciation?client_id={client.id}&tally_guid={quote(tally_guid)}&fy_start={start.isoformat()}&fy_end={end.isoformat()}&error={quote(str(exc))}",status_code=303)
|
||||
return RedirectResponse(url=f"/tools/tally/depreciation?client_id={client.id}&tally_guid={quote(tally_guid)}&fy_start={start.isoformat()}&fy_end={end.isoformat()}&calculated=1&run_id={rid}",status_code=303)
|
||||
finally: db.close()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user