Switch GST reconciliation to direct browser and manual return import

This commit is contained in:
A R R R Associates
2026-09-10 22:27:15 +05:30
parent 0ce2954674
commit 3b3b9c5ebe
2 changed files with 276 additions and 57 deletions
@@ -2,7 +2,7 @@
{% block content %}
<div class="mx-auto max-w-7xl space-y-5 p-4">
<div class="flex items-center justify-between gap-3">
<div><h1 class="text-2xl font-bold">GST Return Reconciliation</h1><p class="text-sm text-slate-600">Download GST returns on the workstation where you are using ERP, store them in the configured client local storage, and reconcile against Accounting Mirror.</p></div>
<div><h1 class="text-2xl font-bold">GST Return Reconciliation</h1><p class="text-sm text-slate-600">Open GST Portal directly in your browser, import downloaded return files into the configured client local storage, and reconcile against Accounting Mirror.</p></div>
<a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}{% endif %}" class="rounded-lg border px-3 py-2 text-sm">Back to Accounting</a>
</div>
{% if message %}<div class="rounded-lg border border-emerald-200 bg-emerald-50 p-3 text-emerald-800">{{ message }}</div>{% endif %}
@@ -19,37 +19,53 @@
{% if selected_client and selected_registration %}
<div class="grid gap-4 lg:grid-cols-2">
<form method="post" action="/tools/accounting/gst-reconciliation/download/start" class="rounded-xl border bg-white p-4 space-y-3">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}"><input type="hidden" name="registration_id" value="{{ selected_registration.id }}">
<input type="hidden" name="period" value="{{ period }}"><input type="hidden" name="financial_year" value="{{ financial_year }}"><input type="hidden" name="download_mode" value="{{ download_mode }}">
<h2 class="font-semibold">1. Download from GST Portal</h2>
<p class="text-xs text-slate-500">The visible GST browser opens on <b>this computer</b> through the ERP Local Agent at 127.0.0.1:8788. CAPTCHA/OTP remains visible here. After download, return data is transferred to the configured Local Storage Agent and stored under the client's FY/GST/GSTIN/period directory.</p>
<label class="text-sm">Credential Vault Entry<select name="credential_id" required class="mt-1 w-full rounded border p-2"><option value="">Select GST credential</option>{% for c in credentials %}<option value="{{ c.id }}" {% if credentials|length==1 %}selected{% endif %}>{{ c.title }}{% if c.reference_number %} — {{ c.reference_number }}{% endif %}</option>{% endfor %}</select></label>
{% if credentials|length==1 %}<div class="text-xs text-slate-500">The only eligible GST Portal credential has been selected automatically.</div>{% endif %}
<div class="rounded-lg border p-3">
<div class="mb-2 text-sm font-medium">Returns to download</div>
<div class="grid gap-2 sm:grid-cols-2"><label class="flex gap-2 text-sm"><input type="checkbox" name="gstr1" value="1" checked> GSTR-1</label><label class="flex gap-2 text-sm"><input type="checkbox" name="gstr2b" value="1" checked> GSTR-2B</label><label class="flex gap-2 text-sm"><input type="checkbox" name="gstr3b" value="1" checked> GSTR-3B</label><label class="flex gap-2 text-sm"><input type="checkbox" name="gstr2a" value="1"> GSTR-2A</label></div>
<div class="rounded-xl border bg-white p-4 space-y-4">
<div>
<h2 class="font-semibold">1. Download from GST Portal</h2>
<p class="mt-1 text-sm text-slate-600">Open the official GST Portal directly in this browser. No ERP Local Agent is required on this computer. Complete login, CAPTCHA and OTP normally, then download the required return JSON/ZIP files.</p>
</div>
<button {% if not node_online or not credentials %}disabled{% endif %} class="rounded bg-indigo-600 px-4 py-2 text-white disabled:opacity-50">Start GST Download</button>
<label class="text-sm">Credential Vault Entry
<select class="mt-1 w-full rounded border p-2" disabled>
{% if credentials %}{% for c in credentials %}<option {% if credentials|length==1 %}selected{% endif %}>{{ c.title }}{% if c.reference_number %} — {{ c.reference_number }}{% endif %}</option>{% endfor %}{% else %}<option>No GST Portal credential found</option>{% endif %}
</select>
</label>
<p class="text-xs text-slate-500">Credential Vault remains linked to this GSTIN for reference and future API-based workflows. The browser login is manual because the GST Portal is on a different website and ERP cannot read or autofill that page across browser security boundaries.</p>
<a href="{{ gst_login_url }}" target="_blank" rel="noopener noreferrer" class="inline-flex rounded bg-indigo-600 px-4 py-2 text-white">Open GST Portal</a>
<div class="rounded-lg border bg-slate-50 p-3 text-xs text-slate-600">
<b>For Full Financial Year:</b> download the selected GSTR-1, GSTR-2B and GSTR-3B JSON/ZIP files for all required months. You can select multiple files below in one import.
</div>
</div>
<form method="post" action="/tools/accounting/gst-reconciliation/import" enctype="multipart/form-data" class="rounded-xl border bg-white p-4 space-y-3">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="client_id" value="{{ selected_client.id }}">
<input type="hidden" name="registration_id" value="{{ selected_registration.id }}">
<input type="hidden" name="period" value="{{ period }}">
<input type="hidden" name="financial_year" value="{{ financial_year }}">
<input type="hidden" name="download_mode" value="{{ download_mode }}">
<div>
<h2 class="font-semibold">2. Import Downloaded Returns to Client Storage</h2>
<p class="mt-1 text-sm text-slate-600">Select the JSON or ZIP files downloaded from GST Portal. ERP identifies each return period, normalizes the data, and transfers it to the configured Local Storage Agent under the client's FY/GST/GSTIN/period directory.</p>
</div>
<label class="block text-sm">GSTR-1 files<input type="file" name="gstr1_files" accept=".json,.zip,application/json,application/zip" multiple class="mt-1 block w-full rounded border p-2"></label>
<label class="block text-sm">GSTR-2B files<input type="file" name="gstr2b_files" accept=".json,.zip,application/json,application/zip" multiple class="mt-1 block w-full rounded border p-2"></label>
<label class="block text-sm">GSTR-3B files<input type="file" name="gstr3b_files" accept=".json,.zip,application/json,application/zip" multiple class="mt-1 block w-full rounded border p-2"></label>
<label class="block text-sm">GSTR-2A files <span class="text-slate-400">(optional)</span><input type="file" name="gstr2a_files" accept=".json,.zip,application/json,application/zip" multiple class="mt-1 block w-full rounded border p-2"></label>
<button {% if not node_online %}disabled{% endif %} class="rounded bg-sky-700 px-4 py-2 text-white disabled:opacity-50">Import & Store GST Returns</button>
<div class="text-xs">Configured Storage Agent: <b>{{ 'Online' if node_online else 'Offline' }}</b></div>
<div class="text-xs text-slate-500">The operator workstation also needs ERP Local Agent 1.26.15+ running so the browser can open here.</div>
<div class="text-xs text-slate-500">No Local Agent is required on the computer where this browser is open.</div>
</form>
</div>
<form method="post" action="/tools/accounting/gst-reconciliation/analyze" class="rounded-xl border bg-white p-4 space-y-3">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}"><input type="hidden" name="registration_id" value="{{ selected_registration.id }}"><input type="hidden" name="period" value="{{ period }}"><input type="hidden" name="financial_year" value="{{ financial_year }}">
<h2 class="font-semibold">2. Reconcile Stored Data</h2>
<p class="text-sm text-slate-600">Sales: Accounting Mirror vs GSTR-1. Purchases: Accounting Mirror vs GSTR-2B. ITC: GSTR-2B vs GSTR-3B. Analysis reads only stored local return data after download.</p>
<label class="text-sm">Analysis scope<select name="analyze_mode" class="mt-1 w-full rounded border p-2"><option value="single">Single Month</option><option value="full_fy" {% if download_mode=='full_fy' %}selected{% endif %}>Full Financial Year</option></select></label>
<form method="post" action="/tools/accounting/gst-reconciliation/analyze" class="rounded-xl border bg-white p-4 space-y-3">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}"><input type="hidden" name="registration_id" value="{{ selected_registration.id }}"><input type="hidden" name="period" value="{{ period }}"><input type="hidden" name="financial_year" value="{{ financial_year }}">
<h2 class="font-semibold">3. Reconcile Stored Data</h2>
<p class="text-sm text-slate-600">Sales: Accounting Mirror vs GSTR-1. Purchases: Accounting Mirror vs GSTR-2B. ITC: GSTR-2B vs GSTR-3B. Analysis reads only return data already stored in the client's local-storage GST directory.</p>
<div class="flex flex-wrap items-end gap-3">
<label class="text-sm min-w-64">Analysis scope<select name="analyze_mode" class="mt-1 w-full rounded border p-2"><option value="single">Single Month</option><option value="full_fy" {% if download_mode=='full_fy' %}selected{% endif %}>Full Financial Year</option></select></label>
<button {% if not node_online %}disabled{% endif %} class="rounded bg-emerald-600 px-4 py-2 text-white disabled:opacity-50">Run Reconciliation</button>
</form>
</div>
<div id="operatorStatus" class="hidden rounded-xl border bg-white p-4">
<div class="flex items-center justify-between"><h2 class="font-semibold">Interactive GST Download</h2><span id="operatorPct" class="text-sm font-semibold"></span></div>
<div class="mt-2 h-2 overflow-hidden rounded bg-slate-100"><div id="operatorBar" class="h-2 bg-indigo-600" style="width:0%"></div></div>
<div id="operatorMessage" class="mt-3 text-sm text-slate-700"></div>
<div id="operatorDetail" class="mt-1 text-xs text-slate-500"></div>
</div>
</div>
</form>
{% set a=request.session.get('gst_reconciliation_result') or {} %}
{% if a %}
@@ -62,33 +78,5 @@
{% endif %}
</div>
{% if operator_job %}
<script>
(function(){
const token={{ operator_job.token|tojson }};
const jobId={{ operator_job.job_id|tojson }};
const box=document.getElementById('operatorStatus'), msg=document.getElementById('operatorMessage'), detail=document.getElementById('operatorDetail'), bar=document.getElementById('operatorBar'), pct=document.getElementById('operatorPct');
box.classList.remove('hidden');
function show(text,extra,p){msg.textContent=text||'';detail.textContent=extra||''; if(p!==undefined){const n=Math.max(0,Math.min(100,Number(p)||0));bar.style.width=n+'%';pct.textContent=Math.round(n)+'%';}}
async function poll(){
try{
const r=await fetch('http://127.0.0.1:8788/api/gst/operator/status?job_id='+encodeURIComponent(jobId),{cache:'no-store'}); const d=await r.json(); const j=d.job||{};
let p=0; if(j.period_total){p=Math.round(((Number(j.period_index||1)-1)/Number(j.period_total))*90)+5;} if(j.status==='completed'||j.status==='failed')p=100;
show(j.message||j.stage||j.status||'Working…',j.period?('Current period: '+j.period):'',p);
if(j.status==='completed'){detail.textContent='Completed. Return data has been transferred to the configured client local storage.';return;}
if(j.status==='failed'){msg.className='mt-3 text-sm text-red-700';return;}
}catch(e){show('Waiting for ERP Local Agent on this computer…','Ensure ERP Local Agent 1.26.15+ is running at http://127.0.0.1:8788.',2);}
setTimeout(poll,1500);
}
async function start(){
show('Connecting to ERP Local Agent on this computer…','The visible GST browser will open here.',2);
try{
const r=await fetch('http://127.0.0.1:8788/api/gst/operator/start',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token:token})}); const d=await r.json(); if(!r.ok||!d.ok)throw new Error(d.error||'Could not start local GST browser.');
poll();
}catch(e){msg.className='mt-3 text-sm text-red-700';show('Could not start GST browser on this computer.',e.message+' Ensure ERP Local Agent 1.26.15+ is running locally.',100);}
}
start();
})();
</script>
{% endif %}
{% endblock %}