Add Phase 9 purchase review board
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<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 · Accounting Intelligence</p><h1 class="mt-1 text-2xl font-semibold text-slate-900">Purchase Review Board</h1><p class="mt-1 max-w-4xl text-sm text-slate-500">Unified Phase 9 review queue for GSTR-2B purchases and linked E-Invoice/E-Way Bill enrichment. Review, correct or bulk-confirm high-confidence suggestions. Nothing is posted to Tally.</p></div>
|
||||
<div class="flex flex-wrap gap-2"><a href="/tools/accounting/gstr2b{% if selected_client %}?client_id={{ selected_client.id }}{% endif %}" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700">GSTR-2B</a><a href="/tools/accounting/purchase-enrichment{% if selected_client %}?client_id={{ selected_client.id }}{% endif %}" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700">E-Invoice / E-Way Bill</a><a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}{% endif %}" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Back to Tally</a></div>
|
||||
</div>
|
||||
{% if message %}<div class="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm font-medium text-emerald-800">{{ message }}</div>{% endif %}
|
||||
{% if error %}<div class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">{{ error }}</div>{% endif %}
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft"><form method="get" class="grid gap-4 md:grid-cols-2 xl:grid-cols-8">
|
||||
<label class="text-sm font-medium text-slate-700 xl:col-span-2">ERP Client<select name="client_id" required class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm"><option value="">Select client</option>{% for c in clients %}<option value="{{ c.id }}" {% if selected_client and selected_client.id==c.id %}selected{% endif %}>{{ c.client_name }}</option>{% endfor %}</select></label>
|
||||
{% if selected_client %}
|
||||
<label class="text-sm font-medium text-slate-700 xl:col-span-2">Tally Company<select name="tally_guid" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm"><option value="">All companies</option>{% for guid,company in tally_companies %}<option value="{{ guid }}" {% if filters.tally_guid==guid %}selected{% endif %}>{{ company or guid }} · {{ guid }}</option>{% endfor %}</select></label>
|
||||
<label class="text-sm font-medium text-slate-700">Status<select name="status" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm"><option value="all">All</option><option value="suggested" {% if filters.status=='suggested' %}selected{% endif %}>Suggested</option><option value="review_required" {% if filters.status=='review_required' %}selected{% endif %}>Review Required</option><option value="reviewed" {% if filters.status=='reviewed' %}selected{% endif %}>Reviewed</option><option value="pending_analysis" {% if filters.status=='pending_analysis' %}selected{% endif %}>Pending Analysis</option></select></label>
|
||||
<label class="text-sm font-medium text-slate-700">Confidence<select name="confidence" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm"><option value="all">All</option><option value="high" {% if filters.confidence=='high' %}selected{% endif %}>High ≥85%</option><option value="medium" {% if filters.confidence=='medium' %}selected{% endif %}>60–84%</option><option value="low" {% if filters.confidence=='low' %}selected{% endif %}>Low <60%</option></select></label>
|
||||
<label class="text-sm font-medium text-slate-700">Source<select name="source" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm"><option value="all">All</option><option value="enriched" {% if filters.source=='enriched' %}selected{% endif %}>Enriched</option><option value="gstr2b_only" {% if filters.source=='gstr2b_only' %}selected{% endif %}>GSTR-2B only</option></select></label>
|
||||
<label class="text-sm font-medium text-slate-700">Period<select name="return_period" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm"><option value="">All</option>{% for p in return_periods %}<option value="{{ p }}" {% if filters.return_period==p %}selected{% endif %}>{{ p }}</option>{% endfor %}</select></label>
|
||||
<label class="text-sm font-medium text-slate-700 xl:col-span-2">Supplier / GSTIN / Invoice<input name="supplier" value="{{ filters.supplier }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm"></label>
|
||||
<label class="text-sm font-medium text-slate-700">Rows<select name="per_page" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm">{% for n in [10,25,50,100] %}<option value="{{ n }}" {% if result and result.per_page==n %}selected{% endif %}>{{ n }}</option>{% endfor %}</select></label>
|
||||
{% endif %}<div class="flex items-end"><button class="w-full rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Apply</button></div>
|
||||
</form></section>
|
||||
{% if selected_client and counts %}
|
||||
<section class="grid gap-4 sm:grid-cols-2 xl:grid-cols-6">{% for label,value,klass in [('Total',counts.total,''),('Reviewed',counts.reviewed,'text-emerald-700'),('Pending',counts.pending,''),('High Confidence',counts.high,'text-blue-700'),('Exceptions',counts.exceptions,'text-amber-700'),('Enriched',counts.enriched,'')] %}<div class="rounded-2xl bg-white p-4 shadow-soft"><div class="text-xs font-semibold uppercase text-slate-500">{{ label }}</div><div class="mt-2 text-2xl font-semibold {{ klass }}">{{ value }}</div></div>{% endfor %}</section>
|
||||
{% if result %}
|
||||
<form id="bulk-form" method="post" action="/tools/accounting/purchase-review/bulk-confirm" class="hidden">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}">{% for k in ['tally_guid','status','confidence','source','supplier','return_period','page','per_page'] %}<input type="hidden" name="{{ k }}" value="{{ filters[k] }}">{% endfor %}
|
||||
</form>
|
||||
<section class="rounded-2xl bg-white shadow-soft"><div class="flex flex-wrap items-center justify-between gap-3 border-b border-slate-200 px-5 py-4"><div><h2 class="font-semibold text-slate-900">Purchase Queue</h2><p class="mt-1 text-sm text-slate-500">{{ result.total }} records · page {{ result.page }} of {{ result.pages }}</p></div><button type="submit" form="bulk-form" class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white">Confirm Selected High-Confidence</button></div>
|
||||
<div class="overflow-x-auto"><table class="min-w-full text-sm"><thead><tr class="border-b border-slate-200 bg-slate-50 text-left text-xs uppercase text-slate-500"><th class="px-3 py-3"><input id="select-high" type="checkbox"></th><th class="px-3 py-3">Supplier / Invoice</th><th class="px-3 py-3">Source</th><th class="px-3 py-3 text-right">Taxable</th><th class="px-3 py-3">Suggestion</th><th class="px-3 py-3">Final Treatment</th><th class="px-3 py-3">Review</th></tr></thead><tbody>
|
||||
{% for pair in result.rows %}{% set row=pair[0] %}{% set enrich=pair[1] %}{% set suggested=nature_by_id.get(row.suggested_nature_id) if row.suggested_nature_id else None %}{% set final=nature_by_id.get(row.final_nature_id) if row.final_nature_id else None %}
|
||||
<tr class="border-b border-slate-100 align-top"><td class="px-3 py-4">{% if row.review_status!='reviewed' and suggested and row.suggested_confidence>=85 %}<input class="high-select" type="checkbox" name="purchase_ids" value="{{ row.id }}" form="bulk-form">{% endif %}</td>
|
||||
<td class="px-3 py-4 min-w-[220px]"><div class="font-semibold text-slate-900">{{ row.supplier_name or row.supplier_gstin }}</div><div class="text-xs text-slate-500">{{ row.supplier_gstin or '-' }}</div><div class="mt-2">{{ row.invoice_number }} · {{ row.invoice_date }}</div></td>
|
||||
<td class="px-3 py-4"><span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold">GSTR-2B</span>{% if enrich %}<span class="ml-1 rounded-full bg-indigo-50 px-2 py-1 text-xs font-semibold text-indigo-700">+{{ enrich }} enrichment</span>{% endif %}</td>
|
||||
<td class="px-3 py-4 text-right whitespace-nowrap"><div class="font-semibold">₹{{ '%.2f'|format(row.taxable_value) }}</div><div class="text-xs text-slate-500">Invoice ₹{{ '%.2f'|format(row.invoice_value) }}</div></td>
|
||||
<td class="px-3 py-4 min-w-[250px]">{% if suggested %}<div class="flex gap-2"><span class="font-semibold">{{ suggested.name }}</span><span class="rounded-full px-2 py-1 text-xs font-semibold {% if row.suggested_confidence>=85 %}bg-emerald-50 text-emerald-700{% elif row.suggested_confidence>=60 %}bg-blue-50 text-blue-700{% else %}bg-amber-50 text-amber-700{% endif %}">{{ row.suggested_confidence }}%</span></div><div class="text-sm text-slate-600">{{ row.suggested_ledger_name or 'No mapped Tally ledger' }}</div>{% if explanations.get(row.id) %}<details class="mt-2"><summary class="cursor-pointer text-xs font-semibold text-brand-600">Why?</summary><ul class="mt-1 text-xs text-slate-500">{% for x in explanations.get(row.id)[:5] %}<li>• {{ x }}</li>{% endfor %}</ul></details>{% endif %}{% else %}<span class="rounded-full bg-amber-50 px-2 py-1 text-xs font-semibold text-amber-700">Review required</span>{% endif %}</td>
|
||||
<td class="px-3 py-4 min-w-[220px]">{% if row.review_status=='reviewed' %}<div class="rounded-xl border border-emerald-200 bg-emerald-50 p-3"><div class="text-xs font-semibold uppercase text-emerald-700">Reviewed</div><div class="font-semibold">{{ final.name if final else '-' }}</div><div class="text-sm text-slate-600">{{ row.final_ledger_name or 'No ledger' }}</div></div>{% else %}<span class="text-xs text-slate-500">Awaiting review</span>{% endif %}</td>
|
||||
<td class="px-3 py-4 min-w-[330px]"><form method="post" action="/tools/accounting/purchase-review/purchase/{{ row.id }}/review" class="space-y-2"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}">{% for k in ['tally_guid','status','confidence','source','supplier','return_period','page','per_page'] %}<input type="hidden" name="{{ k }}" value="{{ filters[k] }}">{% endfor %}<select name="final_nature_id" required class="w-full rounded-lg border border-slate-300 bg-white px-2 py-2 text-sm">{% for nature in natures %}<option value="{{ nature.id }}" {% if (final and final.id==nature.id) or (not final and suggested and suggested.id==nature.id) %}selected{% endif %}>{{ nature.name }}</option>{% endfor %}</select><select name="final_ledger_name" class="w-full rounded-lg border border-slate-300 bg-white px-2 py-2 text-sm"><option value="">No mapped ledger yet</option>{% for nature_id, maps in mappings_by_nature.items() %}{% for m in maps %}<option value="{{ m.ledger_name }}" {% if row.final_ledger_name==m.ledger_name or (not row.final_ledger_name and row.suggested_ledger_name==m.ledger_name) %}selected{% endif %}>{{ m.ledger_name }}</option>{% endfor %}{% endfor %}</select><button class="w-full rounded-lg bg-brand-600 px-3 py-2 text-sm font-semibold text-white">{{ 'Update Review' if row.review_status=='reviewed' else 'Confirm / Correct' }}</button></form></td></tr>
|
||||
{% else %}<tr><td colspan="7" class="px-4 py-10 text-center text-slate-500">No purchases match the selected filters.</td></tr>{% endfor %}</tbody></table></div>
|
||||
{% if result.pages>1 %}<div class="flex items-center justify-between border-t border-slate-200 px-5 py-4"><span class="text-sm text-slate-500">Page {{ result.page }} of {{ result.pages }}</span><div class="flex gap-2">{% if prev_url %}<a href="{{ prev_url }}" class="rounded-lg border border-slate-300 px-3 py-2 text-sm font-semibold">Previous</a>{% endif %}{% if next_url %}<a href="{{ next_url }}" class="rounded-lg border border-slate-300 px-3 py-2 text-sm font-semibold">Next</a>{% endif %}</div></div>{% endif %}</section>
|
||||
</form>
|
||||
<script>(function(){const a=document.getElementById('select-high');if(!a)return;a.addEventListener('change',function(){document.querySelectorAll('.high-select').forEach(function(b){b.checked=a.checked;});});})();</script>
|
||||
{% endif %}{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -18,6 +18,7 @@
|
||||
{% if selected_client %}<a href="/tools/accounting/ledger-learning?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">Ledger Learning</a>{% endif %}
|
||||
{% if selected_client %}<a href="/tools/accounting/gstr2b?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">GSTR-2B Intelligence</a>{% endif %}
|
||||
{% if selected_client %}<a href="/tools/accounting/purchase-enrichment?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">E-Invoice / E-Way Bill</a>{% endif %}
|
||||
{% if selected_client %}<a href="/tools/accounting/purchase-review?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">Purchase Review</a>{% endif %}
|
||||
{% 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>
|
||||
|
||||
Reference in New Issue
Block a user