Add operator workstation GST browser and full FY return download
This commit is contained in:
@@ -1,49 +1,56 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% 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 portal data through Credential Vault, store it in 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>
|
||||
<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>
|
||||
<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 %}
|
||||
{% if error %}<div class="rounded-lg border border-red-200 bg-red-50 p-3 text-red-800">{{ error }}</div>{% endif %}
|
||||
<form method="get" class="grid gap-3 rounded-xl border bg-white p-4 md:grid-cols-4">
|
||||
|
||||
<form method="get" class="grid gap-3 rounded-xl border bg-white p-4 md:grid-cols-5">
|
||||
<label class="text-sm">Client<select name="client_id" class="mt-1 w-full rounded border p-2" onchange="this.form.submit()"><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">GSTIN<select name="registration_id" class="mt-1 w-full rounded border p-2" onchange="this.form.submit()"><option value="">Select GSTIN</option>{% for r,t in registrations %}<option value="{{ r.id }}" {% if selected_registration and r.id==selected_registration.id %}selected{% endif %}>{{ r.registration_number }}{% if r.trade_name %} — {{ r.trade_name }}{% endif %}</option>{% endfor %}</select></label>
|
||||
<label class="text-sm">Return Period (MMYYYY)<input name="period" value="{{ period }}" pattern="[0-9]{6}" placeholder="042026" class="mt-1 w-full rounded border p-2"></label>
|
||||
<label class="text-sm">Financial Year<input name="financial_year" value="{{ financial_year }}" pattern="[0-9]{4}-[0-9]{2}" placeholder="2025-26" class="mt-1 w-full rounded border p-2"></label>
|
||||
<label class="text-sm">Mode<select name="download_mode" class="mt-1 w-full rounded border p-2"><option value="single" {% if download_mode=='single' %}selected{% endif %}>Single Month</option><option value="full_fy" {% if download_mode=='full_fy' %}selected{% endif %}>Full Financial Year</option></select></label>
|
||||
<div class="flex items-end"><button class="w-full rounded bg-slate-800 px-3 py-2 text-white">Load</button></div>
|
||||
<label class="text-sm md:col-span-2">Single Month Period (MMYYYY)<input name="period" value="{{ period }}" pattern="[0-9]{6}" placeholder="042025" class="mt-1 w-full rounded border p-2"><span class="text-xs text-slate-500">Used only when Mode = Single Month.</span></label>
|
||||
</form>
|
||||
|
||||
{% 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="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 Local Storage Agent opens GST portal on the workstation. Username/password are taken from Credential Vault; captcha/OTP remains interactive. Raw return data is saved 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 not credentials %}
|
||||
<div class="rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm text-amber-900">
|
||||
No usable GST Portal credential is linked to this client/GSTIN.
|
||||
<a class="font-semibold underline" href="/credential-vault/new?client_id={{ selected_client.id }}®istration_id={{ selected_registration.id }}">Add GST Portal credential</a>.
|
||||
<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>
|
||||
{% elif credentials|length == 1 %}
|
||||
<div class="text-xs text-slate-500">The only eligible GST Portal credential has been selected automatically.</div>
|
||||
{% endif %}
|
||||
<label class="flex gap-2 text-sm"><input type="checkbox" name="include_2a" value="1"> Also download GSTR-2A</label>
|
||||
<button {% if not period or 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>
|
||||
<div class="text-xs">Storage Agent: <b>{{ 'Online' if node_online else 'Offline' }}</b></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>
|
||||
<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>
|
||||
</form>
|
||||
|
||||
<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="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. Reconciliation JSON is retained in the same local client GST directory.</p>
|
||||
<button {% if not period or not node_online %}disabled{% endif %} class="rounded bg-emerald-600 px-4 py-2 text-white disabled:opacity-50">Run Reconciliation</button>
|
||||
<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>
|
||||
<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>
|
||||
{% if status %}<div class="rounded-xl border bg-white p-4"><h2 class="font-semibold">Download Status</h2><div class="mt-2 grid gap-2 text-sm md:grid-cols-3"><div>Status: <b>{{ status.status or '-' }}</b></div><div>Stage: {{ status.stage or '-' }}</div><div>{{ status.message or '' }}</div></div></div>{% endif %}
|
||||
|
||||
<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>
|
||||
|
||||
{% set a=request.session.get('gst_reconciliation_result') or {} %}
|
||||
{% if a %}
|
||||
{% set s=a.get('sales_reconciliation',{}).get('counts',{}) %}{% set p=a.get('purchase_reconciliation',{}).get('counts',{}) %}
|
||||
@@ -54,4 +61,34 @@
|
||||
{% endif %}
|
||||
{% 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 %}
|
||||
|
||||
Reference in New Issue
Block a user