Add previous FY mirror selection and force fresh TDS review

This commit is contained in:
A R R R Associates
2026-09-06 22:12:56 +05:30
parent 5c19f23c2d
commit bb9d4f7f47
8 changed files with 122 additions and 23 deletions
+12 -2
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
from datetime import date, datetime from datetime import date, datetime
from decimal import Decimal, InvalidOperation from decimal import Decimal, InvalidOperation
from urllib.parse import quote from urllib.parse import quote
from uuid import uuid4
from fastapi import APIRouter, Request from fastapi import APIRouter, Request
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
@@ -139,13 +140,22 @@ def tds_root(request: Request, client_id: int | None = None):
@router.get("/compliance") @router.get("/compliance")
def compliance(request:Request,client_id:int|None=None,tally_guid:str="",date_from:str="",date_to:str="",run:int=0): def compliance(request:Request,client_id:int|None=None,tally_guid:str="",date_from:str="",date_to:str="",run:int=0,run_token:str=""):
db=CommonSessionLocal() db=CommonSessionLocal()
try: try:
user,response=_require_partner(request,db,"accounting.tally.view") user,response=_require_partner(request,db,"accounting.tally.view")
if response:return response if response:return response
clients,selected,node,online,live,review,start,end,error,history=_workspace(request,db,user,client_id,tally_guid,date_from,date_to,run) clients,selected,node,online,live,review,start,end,error,history=_workspace(request,db,user,client_id,tally_guid,date_from,date_to,run)
return _render(request,db,user,"modules/accounting/templates/accounting/tds_compliance.html",clients=clients,selected_client=selected,node=node,node_online=online,live=live,review=review,date_from=start,date_to=end,tally_guid=tally_guid,error=error,rules=_rules(db,_tenant_id(request,user)),history=history) response=_render(
request,db,user,"modules/accounting/templates/accounting/tds_compliance.html",
clients=clients,selected_client=selected,node=node,node_online=online,live=live,
review=review,date_from=start,date_to=end,tally_guid=tally_guid,error=error,
rules=_rules(db,_tenant_id(request,user)),history=history,
run_token=(run_token or uuid4().hex)
)
response.headers["Cache-Control"]="no-store, no-cache, must-revalidate, max-age=0"
response.headers["Pragma"]="no-cache"
return response
finally: db.close() finally: db.close()
@@ -1,6 +1,7 @@
{% extends "ui/templates/base/layout.html" %} {% extends "ui/templates/base/layout.html" %}
{% block content %} {% block content %}
{% set mirror = mirror_status.mirror if mirror_status and mirror_status.mirror else {} %} {% set mirror = mirror_status.mirror if mirror_status and mirror_status.mirror else {} %}
{% set mirror_effective_ready = mirror.ready_for_financial_year or preview %}
<div class="space-y-6"> <div class="space-y-6">
<div class="flex flex-wrap items-start justify-between gap-4"> <div class="flex flex-wrap items-start justify-between gap-4">
<div> <div>
@@ -18,7 +19,7 @@
<section class="grid gap-4 md:grid-cols-3"> <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> <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>
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">SQLite Mirror</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if mirror.ready_for_financial_year else 'text-amber-700' }}">{{ 'Ready' if mirror.ready_for_financial_year else 'Mirror required' }}</div>{% if mirror.ready_for_financial_year and mirror.company %}<div class="mt-1 text-xs text-slate-500">{{ mirror.company.company_name }} · {{ fy_start }} to {{ fy_end }}</div>{% elif mirror.financial_year_reason %}<div class="mt-1 text-xs text-amber-700">{{ mirror.financial_year_reason }}</div>{% endif %}</div> <div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">SQLite Mirror</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if mirror_effective_ready else 'text-amber-700' }}">{{ 'Ready' if mirror_effective_ready else 'Mirror required' }}</div>{% if mirror_effective_ready and mirror.company %}<div class="mt-1 text-xs text-slate-500">{{ mirror.company.company_name }} · {{ fy_start }} to {{ fy_end }}</div>{% elif mirror.financial_year_reason %}<div class="mt-1 text-xs text-amber-700">{{ mirror.financial_year_reason }}</div>{% endif %}</div>
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Write-back</div><div class="mt-2 text-lg font-semibold text-slate-700">Controlled</div><div class="mt-1 text-xs text-slate-500">Tally is contacted only after approval/posting.</div></div> <div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Write-back</div><div class="mt-2 text-lg font-semibold text-slate-700">Controlled</div><div class="mt-1 text-xs text-slate-500">Tally is contacted only after approval/posting.</div></div>
</section> </section>
@@ -307,9 +307,16 @@
<span class="rounded-full bg-emerald-50 px-3 py-1 text-xs font-semibold text-emerald-700">Read-only export</span> <span class="rounded-full bg-emerald-50 px-3 py-1 text-xs font-semibold text-emerald-700">Read-only export</span>
</div> </div>
<div class="mt-4 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900"><strong>Before export:</strong> open the mapped company in TallyPrime and set Tally's active period (Alt+F2) to cover the full period you want exported. The ODBC mirror reads Tally's active period; the ERP does not change Tally's period automatically.</div> <div class="mt-4 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900"><strong>Before export:</strong> open the mapped company in TallyPrime and set Tally's active period (Alt+F2) to cover the full period you want exported. The ODBC mirror reads Tally's active period; the ERP does not change Tally's period automatically.</div>
<form method="post" action="/tools/tally/full-export" class="mt-5 grid gap-4 lg:grid-cols-4 lg:items-end"> <form method="post" action="/tools/tally/full-export" class="mt-5 grid gap-4 lg:grid-cols-5 lg:items-end" id="mirrorExportForm">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" /> <input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="client_id" value="{{ selected_client.id }}" /> <input type="hidden" name="client_id" value="{{ selected_client.id }}" />
<label class="text-sm font-medium text-slate-700">Target Financial Year
<select id="mirrorFinancialYear" name="financial_year" required class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm">
{% for fy in mirror_fy_options %}
<option value="{{ fy }}" {% if fy == selected_financial_year %}selected{% endif %}>FY {{ fy }}</option>
{% endfor %}
</select>
</label>
<label class="text-sm font-medium text-slate-700">Mapped Tally Company <label class="text-sm font-medium text-slate-700">Mapped Tally Company
<select name="tally_guid" required class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm"> <select name="tally_guid" required class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm">
<option value="">Select mapped company</option> <option value="">Select mapped company</option>
@@ -320,10 +327,14 @@
{% endfor %} {% endfor %}
</select> </select>
</label> </label>
<label class="text-sm font-medium text-slate-700">From Date<input type="date" name="date_from" required value="{{ transaction_date_from }}" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm" /></label> <label class="text-sm font-medium text-slate-700">From Date<input id="mirrorDateFrom" type="date" name="date_from" required value="{{ transaction_date_from }}" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm" /></label>
<label class="text-sm font-medium text-slate-700">To Date<input type="date" name="date_to" required value="{{ transaction_date_to }}" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm" /></label> <label class="text-sm font-medium text-slate-700">To Date<input id="mirrorDateTo" type="date" name="date_to" required value="{{ transaction_date_to }}" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm" /></label>
<button id="fullExportButton" type="submit" class="rounded-xl bg-emerald-700 px-4 py-2 text-sm font-semibold text-white hover:bg-emerald-800">Mirror Tally to SQLite</button> <button id="fullExportButton" type="submit" class="rounded-xl bg-emerald-700 px-4 py-2 text-sm font-semibold text-white hover:bg-emerald-800">Mirror Tally to SQLite</button>
</form> </form>
<div class="mt-3 flex flex-wrap items-center gap-3 text-sm">
<button type="button" id="mirrorPreviousFY" class="rounded-lg border border-slate-300 bg-white px-3 py-1.5 font-semibold text-slate-700">Use Previous FY</button>
<span class="text-slate-500">Select an FY here to create or refresh that year's separate local mirror in the client's FY storage hierarchy.</span>
</div>
<div id="fullExportProgress" class="mt-5 hidden rounded-xl border border-slate-200 bg-slate-50 p-4"> <div id="fullExportProgress" class="mt-5 hidden rounded-xl border border-slate-200 bg-slate-50 p-4">
<div class="flex items-center justify-between gap-3"><div><div id="fullExportStage" class="font-semibold text-slate-900">Waiting…</div><div id="fullExportMessage" class="mt-1 text-xs text-slate-500"></div></div><div id="fullExportPct" class="text-lg font-bold text-slate-900">0%</div></div> <div class="flex items-center justify-between gap-3"><div><div id="fullExportStage" class="font-semibold text-slate-900">Waiting…</div><div id="fullExportMessage" class="mt-1 text-xs text-slate-500"></div></div><div id="fullExportPct" class="text-lg font-bold text-slate-900">0%</div></div>
<div class="mt-3 h-3 overflow-hidden rounded-full bg-slate-200"><div id="fullExportBar" class="h-full bg-emerald-600 transition-all duration-300" style="width:0%"></div></div> <div class="mt-3 h-3 overflow-hidden rounded-full bg-slate-200"><div id="fullExportBar" class="h-full bg-emerald-600 transition-all duration-300" style="width:0%"></div></div>
@@ -576,4 +587,28 @@
pollFullExport(); pollFullExport();
})(); })();
</script> </script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const fy=document.getElementById("mirrorFinancialYear");
const from=document.getElementById("mirrorDateFrom");
const to=document.getElementById("mirrorDateTo");
const previous=document.getElementById("mirrorPreviousFY");
function applyFY(value){
const m=/^(\d{4})-(\d{2})$/.exec(value||"");
if(!m||!from||!to)return;
const y=parseInt(m[1],10);
from.value=String(y).padStart(4,"0")+"-04-01";
to.value=String(y+1).padStart(4,"0")+"-03-31";
}
if(fy)fy.addEventListener("change",function(){applyFY(fy.value);});
if(previous&&fy)previous.addEventListener("click",function(){
if(fy.selectedIndex+1<fy.options.length){
fy.selectedIndex+=1;
applyFY(fy.value);
}
});
});
</script>
{% endblock %} {% endblock %}
@@ -1,7 +1,12 @@
{% extends "ui/templates/base/layout.html" %} {% extends "ui/templates/base/layout.html" %}
{% block content %} {% block content %}
<div class="space-y-6"> <div class="space-y-6">
<div class="flex flex-wrap items-start justify-between gap-3"><div><p class="text-xs font-semibold uppercase tracking-[0.16em] text-brand-600">TDS &amp; Withholding Tax</p><h1 class="mt-1 text-2xl font-semibold text-slate-900">TDS Compliance Review</h1><p class="mt-1 text-sm text-slate-500">Review expenses, purchases, journals and creditor postings from the client SQLite Accounting Mirror. Rules and thresholds come from the editable TDS Rule Master.</p></div><div class="flex gap-2"><a href="/tools/accounting/tds/rules" class="rounded-xl border px-3 py-2 text-sm font-semibold">Rule Master</a><a href="/tools/tally" class="rounded-xl border px-3 py-2 text-sm font-semibold">Tally</a></div></div>{% if error %}<div class="rounded-xl bg-red-50 p-3 text-sm text-red-800">{{ error }}</div>{% endif %}<form method="get" class="rounded-2xl bg-white p-5 shadow-soft"><div class="grid gap-3 lg:grid-cols-3"><label class="text-sm">Client<select name="client_id" required class="mt-1 w-full rounded-xl border px-3 py-2"><option value="">Select client</option>{% for c in clients %}<option value="{{ c.id }}" {% if selected_client and c.id==selected_client.id %}selected{% endif %}>{{ c.client_name }}</option>{% endfor %}</select></label><label class="text-sm">From<input type="date" name="date_from" value="{{ date_from }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">To<input type="date" name="date_to" value="{{ date_to }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label></div><input type="hidden" name="run" value="1"><button class="mt-4 rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Run TDS Review</button></form>{% if review %}<div class="flex justify-end gap-2"><a href="/tools/tally/analysis/{{ review.analysis_run_id }}/download?client_id={{ selected_client.id }}&financial_year={{ date_from[:4] }}-{{ date_to[2:4] }}&format=xlsx" class="rounded-xl border border-emerald-300 bg-emerald-50 px-3 py-2 text-sm font-semibold text-emerald-800">Download Excel</a><a href="/tools/tally/analysis/{{ review.analysis_run_id }}/download?client_id={{ selected_client.id }}&financial_year={{ date_from[:4] }}-{{ date_to[2:4] }}&format=pdf" class="rounded-xl border px-3 py-2 text-sm font-semibold">Download PDF</a></div><div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-5"> <div class="flex flex-wrap items-start justify-between gap-3"><div><p class="text-xs font-semibold uppercase tracking-[0.16em] text-brand-600">TDS &amp; Withholding Tax</p><h1 class="mt-1 text-2xl font-semibold text-slate-900">TDS Compliance Review</h1><p class="mt-1 text-sm text-slate-500">Review expenses, purchases, journals and creditor postings from the client SQLite Accounting Mirror. Rules and thresholds come from the editable TDS Rule Master.</p></div><div class="flex gap-2"><a href="/tools/accounting/tds/rules" class="rounded-xl border px-3 py-2 text-sm font-semibold">Rule Master</a><a href="/tools/tally" class="rounded-xl border px-3 py-2 text-sm font-semibold">Tally</a></div></div>{% if error %}<div class="rounded-xl bg-red-50 p-3 text-sm text-red-800">{{ error }}</div>{% endif %}<form method="get" class="rounded-2xl bg-white p-5 shadow-soft"><div class="grid gap-3 lg:grid-cols-3"><label class="text-sm">Client<select name="client_id" required class="mt-1 w-full rounded-xl border px-3 py-2"><option value="">Select client</option>{% for c in clients %}<option value="{{ c.id }}" {% if selected_client and c.id==selected_client.id %}selected{% endif %}>{{ c.client_name }}</option>{% endfor %}</select></label><label class="text-sm">From<input type="date" name="date_from" value="{{ date_from }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">To<input type="date" name="date_to" value="{{ date_to }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label></div><input type="hidden" name="run" value="1"><input type="hidden" name="run_token" value="{{ run_token }}"><button class="mt-4 rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Run TDS Review</button></form>{% if review %}
<div class="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-slate-200 bg-white px-4 py-3 text-sm">
<div><span class="font-semibold">Analysis Run #{{ review.analysis_run_id }}</span><span class="ml-2 text-slate-500">{{ review.company_name }} · {{ review.date_from }} to {{ review.date_to }}</span></div>
<div class="text-xs text-slate-500">Fresh run {{ run_token[:8] }}</div>
</div>
<div class="flex justify-end gap-2"><a href="/tools/tally/analysis/{{ review.analysis_run_id }}/download?client_id={{ selected_client.id }}&financial_year={{ date_from[:4] }}-{{ date_to[2:4] }}&format=xlsx" class="rounded-xl border border-emerald-300 bg-emerald-50 px-3 py-2 text-sm font-semibold text-emerald-800">Download Excel</a><a href="/tools/tally/analysis/{{ review.analysis_run_id }}/download?client_id={{ selected_client.id }}&financial_year={{ date_from[:4] }}-{{ date_to[2:4] }}&format=pdf" class="rounded-xl border px-3 py-2 text-sm font-semibold">Download PDF</a></div><div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-5">
{% for k,v in review.summary.items() %} {% for k,v in review.summary.items() %}
<div class="rounded-xl bg-white p-4 shadow-soft"> <div class="rounded-xl bg-white p-4 shadow-soft">
<div class="text-xs uppercase text-slate-500">{{ k|replace('_',' ') }}</div> <div class="text-xs uppercase text-slate-500">{{ k|replace('_',' ') }}</div>
+46 -7
View File
@@ -67,6 +67,23 @@ def _financial_year_for_date(value: date | None = None) -> str:
return f"{start_year}-{str(start_year + 1)[-2:]}" return f"{start_year}-{str(start_year + 1)[-2:]}"
def _financial_year_bounds(financial_year: str) -> tuple[date, date]:
match = re.fullmatch(r"(\d{4})-(\d{2})", str(financial_year or "").strip())
if not match:
raise ValueError("Invalid financial year.")
start_year = int(match.group(1))
return date(start_year, 4, 1), date(start_year + 1, 3, 31)
def _financial_year_options(reference_fy: str | None = None, years_back: int = 7) -> list[str]:
reference = str(reference_fy or _financial_year_for_date()).strip()
start, _ = _financial_year_bounds(reference)
return [
f"{year}-{str(year + 1)[-2:]}"
for year in range(start.year, start.year - max(1, int(years_back)), -1)
]
def _accounting_storage_payload(client, financial_year: str | None = None) -> dict: def _accounting_storage_payload(client, financial_year: str | None = None) -> dict:
"""Use the same FY + client-folder policy as existing Engagement storage.""" """Use the same FY + client-folder policy as existing Engagement storage."""
fy_value = str(financial_year or _financial_year_for_date()).strip() fy_value = str(financial_year or _financial_year_for_date()).strip()
@@ -202,6 +219,7 @@ def tally_tool(
synced: int = 0, synced: int = 0,
transactions_synced: int = 0, transactions_synced: int = 0,
export_started: int = 0, export_started: int = 0,
financial_year: str = "",
error: str = "", error: str = "",
): ):
db = CommonSessionLocal() db = CommonSessionLocal()
@@ -219,6 +237,13 @@ def tally_tool(
live_result = None live_result = None
analysis_history = [] analysis_history = []
command_error = error or "" command_error = error or ""
selected_financial_year = str(financial_year or _financial_year_for_date()).strip()
try:
selected_fy_start, selected_fy_end = _financial_year_bounds(selected_financial_year)
except Exception:
selected_financial_year = _financial_year_for_date()
selected_fy_start, selected_fy_end = _financial_year_bounds(selected_financial_year)
mirror_fy_options = _financial_year_options(_financial_year_for_date(), 7)
should_query_agent = bool(refresh or selected_client) should_query_agent = bool(refresh or selected_client)
if refresh: if refresh:
@@ -230,7 +255,7 @@ def tally_tool(
if should_query_agent and node and online: if should_query_agent and node and online:
payload = {} payload = {}
if selected_client: if selected_client:
payload = _accounting_storage_payload(selected_client) payload = _accounting_storage_payload(selected_client, selected_financial_year)
try: try:
response_data = request_agent_command( response_data = request_agent_command(
node.node_code, node.node_code,
@@ -258,9 +283,12 @@ def tally_tool(
command_error = str(exc) command_error = str(exc)
today = date.today() today = date.today()
fy_start_year = today.year if today.month >= 4 else today.year - 1 transaction_date_from = selected_fy_start.isoformat()
transaction_date_from = date(fy_start_year, 4, 1).isoformat() transaction_date_to = (
transaction_date_to = today.isoformat() min(today, selected_fy_end).isoformat()
if selected_financial_year == _financial_year_for_date(today)
else selected_fy_end.isoformat()
)
return _render( return _render(
request, request,
@@ -280,6 +308,8 @@ def tally_tool(
transactions_synced=bool(transactions_synced), transactions_synced=bool(transactions_synced),
transaction_date_from=transaction_date_from, transaction_date_from=transaction_date_from,
transaction_date_to=transaction_date_to, transaction_date_to=transaction_date_to,
selected_financial_year=selected_financial_year,
mirror_fy_options=mirror_fy_options,
command_error=command_error, command_error=command_error,
analysis_history=analysis_history, analysis_history=analysis_history,
) )
@@ -726,6 +756,7 @@ def start_full_accounting_export(
tally_guid: str = Form(...), tally_guid: str = Form(...),
date_from: str = Form(...), date_from: str = Form(...),
date_to: str = Form(...), date_to: str = Form(...),
financial_year: str = Form(""),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) validate_csrf(request, csrf_token)
@@ -742,6 +773,13 @@ def start_full_accounting_export(
end = date.fromisoformat(str(date_to or "")) end = date.fromisoformat(str(date_to or ""))
if start > end: if start > end:
raise ValueError("From Date cannot be after To Date.") raise ValueError("From Date cannot be after To Date.")
target_fy = str(financial_year or _financial_year_for_date(start)).strip()
fy_start, fy_end = _financial_year_bounds(target_fy)
if start < fy_start or end > fy_end:
raise ValueError(
f"Selected dates must fall within FY {target_fy} "
f"({fy_start.isoformat()} to {fy_end.isoformat()})."
)
except Exception as exc: except Exception as exc:
return RedirectResponse(url=f"/tools/tally?client_id={client.id}&error={quote(str(exc))}", status_code=303) return RedirectResponse(url=f"/tools/tally?client_id={client.id}&error={quote(str(exc))}", status_code=303)
node = get_active_storage_node_for_branch(db, scope.tenant_id, scope.branch_id) node = get_active_storage_node_for_branch(db, scope.tenant_id, scope.branch_id)
@@ -750,7 +788,7 @@ def start_full_accounting_export(
result = request_agent_command( result = request_agent_command(
node.node_code, "accounting_full_export_start", node.node_code, "accounting_full_export_start",
{ {
**_accounting_storage_payload(client, _financial_year_for_date(start)), **_accounting_storage_payload(client, target_fy),
"tally_guid": str(tally_guid or "").strip(), "tally_guid": str(tally_guid or "").strip(),
"date_from": start.isoformat(), "date_to": end.isoformat(), "date_from": start.isoformat(), "date_to": end.isoformat(),
"requested_by_user_id": int(user.id), "timeout_seconds": 1800, "requested_by_user_id": int(user.id), "timeout_seconds": 1800,
@@ -758,7 +796,7 @@ def start_full_accounting_export(
) )
if not result.get("ok"): if not result.get("ok"):
raise RuntimeError(str(result.get("error") or "Full Accounting Export could not be started.")) raise RuntimeError(str(result.get("error") or "Full Accounting Export could not be started."))
return RedirectResponse(url=f"/tools/tally?client_id={client.id}&refresh=1&export_started=1", status_code=303) return RedirectResponse(url=f"/tools/tally?client_id={client.id}&financial_year={quote(target_fy)}&refresh=1&export_started=1", status_code=303)
except Exception as exc: except Exception as exc:
return RedirectResponse(url=f"/tools/tally?client_id={client_id}&error={quote(str(exc))}", status_code=303) return RedirectResponse(url=f"/tools/tally?client_id={client_id}&error={quote(str(exc))}", status_code=303)
finally: finally:
@@ -779,7 +817,8 @@ def full_accounting_export_status(request: Request, client_id: int):
if not node or not _node_online(node): if not node or not _node_online(node):
return JSONResponse({"ok": False, "error": "ERP Local Agent is offline."}, status_code=503) return JSONResponse({"ok": False, "error": "ERP Local Agent is offline."}, status_code=503)
result = request_agent_command( result = request_agent_command(
node.node_code, "accounting_full_export_status", _accounting_storage_payload(client), timeout_seconds=10 node.node_code, "accounting_full_export_status",
{"client_id": int(client.id)}, timeout_seconds=10
) )
if not result.get("ok"): if not result.get("ok"):
return JSONResponse({"ok": False, "error": str(result.get("error") or "Progress lookup failed.")}, status_code=500) return JSONResponse({"ok": False, "error": str(result.get("error") or "Progress lookup failed.")}, status_code=500)
+1 -1
View File
@@ -4,7 +4,7 @@ import io
from pathlib import Path from pathlib import Path
import zipfile import zipfile
ERP_LOCAL_AGENT_VERSION = "1.25.6" ERP_LOCAL_AGENT_VERSION = "1.25.7"
ERP_LOCAL_AGENT_NAME = "ERP Local Agent" ERP_LOCAL_AGENT_NAME = "ERP Local Agent"
RUNTIME_ROOT = Path(__file__).resolve().parent / "local_agent_runtime" RUNTIME_ROOT = Path(__file__).resolve().parent / "local_agent_runtime"
_DETERMINISTIC_ZIP_TIMESTAMP = (2026, 1, 1, 0, 0, 0) _DETERMINISTIC_ZIP_TIMESTAMP = (2026, 1, 1, 0, 0, 0)
@@ -1,2 +1,2 @@
__version__ = "1.25.6" __version__ = "1.25.7"
AGENT_NAME = "ERP Local Agent" AGENT_NAME = "ERP Local Agent"
@@ -3174,16 +3174,17 @@ class AgentCommandProcessor:
} }
if value in exact: if value in exact:
return True return True
owner_prefix = ( owner_word = any(
value.startswith("partner ") or value.startswith("partners ") token in value.split()
or value.startswith("proprietor ") or value.startswith("proprietors ") for token in ("partner","partners","proprietor","proprietors","owner","owners")
or value.startswith("owner ") or value.startswith("owners ")
) )
current_account_like = ( current_account_like = (
" current account" in value or " current accounts" in value "current account" in value
or " current a c" in value or value.endswith(" current ac") or "current accounts" in value
or "current a c" in value
or value.endswith("current ac")
) )
if owner_prefix and current_account_like: if owner_word and current_account_like:
return True return True
return ( return (
value.startswith("capital account ") value.startswith("capital account ")
@@ -3211,6 +3212,14 @@ class AgentCommandProcessor:
parent_group = str(ledger_parent_by_name.get(lname.casefold()) or "").strip() parent_group = str(ledger_parent_by_name.get(lname.casefold()) or "").strip()
if _capital_account_parent(parent_group): if _capital_account_parent(parent_group):
continue continue
normalized_capital_context = re.sub(
r"[^a-z0-9]+", " ", f"{parent_group} {lname}".casefold()
).strip()
if (
any(x in normalized_capital_context.split() for x in ("partner","partners","proprietor","proprietors","owner","owners"))
and ("current account" in normalized_capital_context or "current a c" in normalized_capital_context)
):
continue
text = lname.casefold() text = lname.casefold()
matches=[] matches=[]
for r in rules: for r in rules: