Add Phase 18 automatic stock item voucher preparation

This commit is contained in:
A R R R Associates
2026-08-23 18:45:21 +05:30
parent 1111a52917
commit 67ebd587e9
13 changed files with 1705 additions and 31 deletions
@@ -0,0 +1,244 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<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">Accounting Engine · Phase 18</p>
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Automatic Accounting Preparation</h1>
<p class="mt-1 max-w-5xl text-sm text-slate-500">
Create native Tally voucher drafts automatically from existing Bank Analyzer and E-Way Bill source data. Purchase and Sales drafts retain stock-item level quantity, unit, rate, HSN and taxable value for native Tally inventory vouchers.
</p>
</div>
<div class="flex flex-wrap gap-2">
<a href="/tools/accounting/voucher-engine{% 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">Voucher Review Queue</a>
<a href="/tools/accounting/chart-of-accounts{% 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">Chart of Accounts</a>
</div>
</div>
{% if message %}<div class="rounded-xl border border-emerald-200 bg-emerald-50 p-3 text-sm text-emerald-800">{{ message }}</div>{% endif %}
{% if error %}<div class="rounded-xl border border-red-200 bg-red-50 p-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-6">
<label class="text-xs font-semibold xl:col-span-2">ERP Client
<select name="client_id" required class="mt-1 w-full rounded-lg border border-slate-300 bg-white px-3 py-2">
<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>
<label class="text-xs font-semibold xl:col-span-2">Tally Company / Chart of Accounts
<select name="tally_guid" class="mt-1 w-full rounded-lg border border-slate-300 bg-white px-3 py-2">
<option value="">Select company</option>
{% for c in company_options %}
<option value="{{ c.guid }}" {% if selected_tally_guid==c.guid %}selected{% endif %}>{{ c.name }}</option>
{% endfor %}
</select>
</label>
<label class="text-xs font-semibold">From
<input type="date" name="from_date" value="{{ from_date }}" class="mt-1 w-full rounded-lg border border-slate-300 px-3 py-2">
</label>
<label class="text-xs font-semibold">To
<input type="date" name="to_date" value="{{ to_date }}" class="mt-1 w-full rounded-lg border border-slate-300 px-3 py-2">
</label>
<div class="xl:col-span-6 flex justify-end">
<button class="rounded-xl bg-brand-600 px-5 py-2 text-sm font-semibold text-white">Load Automatic Sources</button>
</div>
</form>
</section>
{% if selected_client and selected_tally_guid %}
<section class="grid gap-4 lg:grid-cols-3">
<div class="rounded-2xl border border-emerald-200 bg-emerald-50/50 p-5">
<p class="text-xs font-semibold uppercase tracking-wider text-emerald-700">Bank Statements</p>
<h2 class="mt-1 font-semibold">Receipt / Payment / Contra</h2>
<p class="mt-1 text-sm text-slate-600">Bank Analyzer rows are converted into native Receipt, Payment or Contra voucher drafts. The bank ledger is chosen from the client's classified Chart of Accounts.</p>
</div>
<div class="rounded-2xl border border-blue-200 bg-blue-50/50 p-5">
<p class="text-xs font-semibold uppercase tracking-wider text-blue-700">E-Way Bill · Inward</p>
<h2 class="mt-1 font-semibold">Stock-item Purchase</h2>
<p class="mt-1 text-sm text-slate-600">Each invoice retains every item, HSN, quantity, unit, rate, taxable value and tax amounts and creates a native Purchase voucher draft.</p>
</div>
<div class="rounded-2xl border border-violet-200 bg-violet-50/50 p-5">
<p class="text-xs font-semibold uppercase tracking-wider text-violet-700">E-Way Bill · Outward</p>
<h2 class="mt-1 font-semibold">Stock-item Sales</h2>
<p class="mt-1 text-sm text-slate-600">Outward invoices create native Sales voucher drafts with item-wise inventory allocations and output-tax ledger lines.</p>
</div>
</section>
<section class="rounded-2xl bg-white shadow-soft">
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-slate-200 p-5">
<div>
<h2 class="font-semibold">Bank Analyzer Transactions</h2>
<p class="text-sm text-slate-500">One click creates the proposed Receipt / Payment / Contra draft. Posting remains subject to the common review and Tally preflight controls.</p>
</div>
<form method="get" class="flex flex-wrap gap-2">
<input type="hidden" name="client_id" value="{{ selected_client.id }}">
<input type="hidden" name="tally_guid" value="{{ selected_tally_guid }}">
<input type="hidden" name="from_date" value="{{ from_date }}">
<input type="hidden" name="to_date" value="{{ to_date }}">
<select name="bank_source" class="rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm">
{% for source in sources.bank %}
<option value="{{ source.table_name }}" {% if selected_bank_source==source.table_name %}selected{% endif %}>{{ source.table_name }}</option>
{% endfor %}
</select>
<button class="rounded-lg border border-slate-300 px-3 py-2 text-sm font-semibold">Change Source</button>
</form>
</div>
{% if not sources.bank %}
<div class="p-8 text-center text-slate-500">
No existing Bank Analyzer transaction table containing client/date/narration/amount fields was discovered in the current ERP schema. Existing Bank Analyzer data has not been changed.
</div>
{% elif not bank_rows %}
<div class="p-8 text-center text-slate-500">No Bank Analyzer transactions were found for the selected client/period.</div>
{% else %}
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead>
<tr class="bg-slate-50 text-left text-xs uppercase text-slate-500">
<th class="px-4 py-3">Date</th>
<th class="px-4 py-3">Narration / Party</th>
<th class="px-4 py-3">Direction</th>
<th class="px-4 py-3 text-right">Amount</th>
<th class="px-4 py-3">Bank/Cash Ledger</th>
<th class="px-4 py-3">Action</th>
</tr>
</thead>
<tbody>
{% for row in bank_rows %}
<tr class="border-t border-slate-100 align-top">
<td class="px-4 py-3 whitespace-nowrap">{{ row.date or '-' }}</td>
<td class="px-4 py-3">
<div class="font-semibold">{{ row.party or row.narration or '-' }}</div>
{% if row.party and row.narration %}<div class="mt-1 max-w-xl text-xs text-slate-500">{{ row.narration }}</div>{% endif %}
{% if row.category %}<div class="mt-1 text-xs text-slate-500">Category: {{ row.category }}</div>{% endif %}
{% if row.reference %}<div class="text-xs text-slate-500">Ref: {{ row.reference }}</div>{% endif %}
</td>
<td class="px-4 py-3"><span class="rounded-full px-2 py-1 text-xs font-semibold {% if row.direction=='DEBIT' %}bg-amber-100 text-amber-800{% else %}bg-emerald-100 text-emerald-800{% endif %}">{{ row.direction }}</span></td>
<td class="px-4 py-3 text-right font-semibold">₹{{ '%.2f'|format(row.amount) }}</td>
<td class="px-4 py-3">
<form method="post" action="/tools/accounting/voucher-engine/automatic/bank/draft" class="flex min-w-[320px] gap-2">
<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="source_table" value="{{ row.source_table }}">
<input type="hidden" name="source_id" value="{{ row.source_id }}">
<select name="bank_ledger_name" required class="min-w-[190px] flex-1 rounded-lg border border-slate-300 bg-white px-2 py-2 text-xs">
<option value="">Select Bank/Cash ledger</option>
{% for ledger in bank_ledgers %}
<option value="{{ ledger.name }}">{{ ledger.name }}</option>
{% endfor %}
</select>
<button class="rounded-lg bg-slate-900 px-3 py-2 text-xs font-semibold text-white">Create Draft</button>
</form>
</td>
<td class="px-4 py-3 text-xs text-slate-500">{% if row.contra_hint %}Contra hint detected{% else %}Auto ledger matching from party/category{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</section>
<section class="rounded-2xl bg-white shadow-soft">
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-slate-200 p-5">
<div>
<h2 class="font-semibold">E-Way Bill Purchase / Sales Invoices</h2>
<p class="text-sm text-slate-500">Invoices are grouped by E-Way Bill + invoice + party. Every underlying item remains separate in the voucher draft.</p>
</div>
<form method="get" class="flex flex-wrap gap-2">
<input type="hidden" name="client_id" value="{{ selected_client.id }}">
<input type="hidden" name="tally_guid" value="{{ selected_tally_guid }}">
<input type="hidden" name="from_date" value="{{ from_date }}">
<input type="hidden" name="to_date" value="{{ to_date }}">
<select name="eway_source" class="rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm">
{% for source in sources.eway %}
<option value="{{ source.table_name }}" {% if selected_eway_source==source.table_name %}selected{% endif %}>{{ source.table_name }}</option>
{% endfor %}
</select>
<select name="direction" class="rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm">
<option value="" {% if not direction %}selected{% endif %}>Purchase + Sales</option>
<option value="PURCHASE" {% if direction=='PURCHASE' %}selected{% endif %}>Purchase / Inward</option>
<option value="SALES" {% if direction=='SALES' %}selected{% endif %}>Sales / Outward</option>
</select>
<button class="rounded-lg border border-slate-300 px-3 py-2 text-sm font-semibold">Apply</button>
</form>
</div>
{% if not sources.eway %}
<div class="p-8 text-center text-slate-500">
No existing E-Way Bill item table containing E-Way Bill number, item description and taxable value was discovered in the current ERP schema. Existing E-Way Bill features have not been modified.
</div>
{% elif not eway_invoices %}
<div class="p-8 text-center text-slate-500">No E-Way Bill item invoices were found for this client/period/filter.</div>
{% else %}
<div class="divide-y divide-slate-100">
{% for inv in eway_invoices %}
<article class="p-5">
<div class="flex flex-wrap items-start justify-between gap-4">
<div>
<div class="flex flex-wrap gap-2">
<span class="rounded-full px-2 py-1 text-xs font-semibold {% if inv.direction=='PURCHASE' %}bg-blue-100 text-blue-800{% elif inv.direction=='SALES' %}bg-violet-100 text-violet-800{% else %}bg-slate-100{% endif %}">{{ inv.direction }}</span>
<span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold">{{ inv.items|length }} stock item(s)</span>
</div>
<h3 class="mt-2 font-semibold">{{ inv.party_name or 'Party not available' }}</h3>
<p class="text-sm text-slate-500">GSTIN {{ inv.party_gstin or '-' }} · Invoice {{ inv.invoice_no or '-' }} · {{ inv.invoice_date or '-' }} · EWB {{ inv.eway_bill_no or '-' }}</p>
</div>
<div class="text-right">
<div class="font-semibold">Taxable ₹{{ '%.2f'|format(inv.taxable_value) }}</div>
<div class="text-xs text-slate-500">CGST {{ '%.2f'|format(inv.cgst) }} · SGST {{ '%.2f'|format(inv.sgst) }} · IGST {{ '%.2f'|format(inv.igst) }} · Cess {{ '%.2f'|format(inv.cess) }}</div>
</div>
</div>
<div class="mt-4 overflow-x-auto rounded-xl border border-slate-200">
<table class="min-w-full text-xs">
<thead><tr class="bg-slate-50 text-left uppercase text-slate-500"><th class="px-3 py-2">Stock Item / Description</th><th class="px-3 py-2">HSN</th><th class="px-3 py-2 text-right">Qty</th><th class="px-3 py-2">Unit</th><th class="px-3 py-2 text-right">Rate</th><th class="px-3 py-2 text-right">Taxable</th></tr></thead>
<tbody>
{% for item in inv.items %}
<tr class="border-t border-slate-100">
<td class="px-3 py-2 font-semibold">{{ item.item_name or '-' }}</td>
<td class="px-3 py-2">{{ item.hsn or '-' }}</td>
<td class="px-3 py-2 text-right">{{ item.quantity }}</td>
<td class="px-3 py-2">{{ item.unit or '-' }}</td>
<td class="px-3 py-2 text-right">{{ '%.4f'|format(item.rate) }}</td>
<td class="px-3 py-2 text-right">₹{{ '%.2f'|format(item.taxable_value) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<form method="post" action="/tools/accounting/voucher-engine/automatic/eway/draft" class="mt-4 flex justify-end">
<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="source_table" value="{{ inv.source_table }}">
<input type="hidden" name="direction" value="{{ inv.direction }}">
<input type="hidden" name="eway_bill_no" value="{{ inv.eway_bill_no }}">
<input type="hidden" name="invoice_no" value="{{ inv.invoice_no }}">
<input type="hidden" name="invoice_date" value="{{ inv.invoice_date }}">
<input type="hidden" name="party_gstin" value="{{ inv.party_gstin }}">
<input type="hidden" name="party_name" value="{{ inv.party_name }}">
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Create Stock-item {{ inv.direction|title }} Draft</button>
</form>
</article>
{% endfor %}
</div>
{% endif %}
</section>
<section class="rounded-2xl border border-slate-200 bg-slate-50 p-5">
<h2 class="font-semibold">Manual entry remains only as a fallback</h2>
<p class="mt-1 text-sm text-slate-600">Manual / adjustment voucher creation has not been removed. It remains useful for provisions, accruals, depreciation, reclassification, audit adjustments and source transactions that cannot yet be resolved automatically.</p>
<a href="/tools/accounting/voucher-engine?client_id={{ selected_client.id }}" class="mt-3 inline-flex rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm font-semibold">Open Manual / Review Voucher Engine</a>
</section>
{% endif %}
</div>
{% endblock %}
@@ -10,6 +10,7 @@
</p>
</div>
<div class="flex flex-wrap gap-2">
<a href="/tools/accounting/voucher-engine/automatic{% 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">Automatic Preparation</a>
<a href="/tools/accounting/chart-of-accounts{% 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">Chart of Accounts</a>
<a href="/tools/tally{% 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">Back to Accounting</a>
</div>
@@ -57,8 +58,8 @@
<section class="rounded-2xl bg-white p-5 shadow-soft">
<div class="mb-4">
<h2 class="font-semibold text-slate-900">Create Voucher Draft</h2>
<p class="text-sm text-slate-500">Every ledger must already exist in the Phase 16 Chart of Accounts. Debit and Credit must balance before the draft is accepted.</p>
<h2 class="font-semibold text-slate-900">Manual / Adjustment Voucher Draft</h2>
<p class="text-sm text-slate-500">Automatic Bank/E-Way Bill preparation is available above. Use this manual form for adjustments, provisions, accruals, depreciation, reclassification, audit adjustments or unresolved source transactions. Every ledger must exist in the Chart of Accounts and Debit must equal Credit.</p>
</div>
<form method="post" action="/tools/accounting/voucher-engine/draft" id="voucherDraftForm" class="space-y-5">
@@ -201,6 +202,28 @@
</table>
</div>
{% if draft.items %}
<div class="mt-3 overflow-x-auto rounded-xl border border-blue-200">
<div class="border-b border-blue-100 bg-blue-50 px-3 py-2 text-xs font-semibold uppercase tracking-wider text-blue-800">Stock-item allocation</div>
<table class="min-w-full text-xs">
<thead><tr class="bg-slate-50 text-left uppercase text-slate-500"><th class="px-3 py-2">Stock Item</th><th class="px-3 py-2">HSN</th><th class="px-3 py-2 text-right">Qty</th><th class="px-3 py-2">Unit</th><th class="px-3 py-2 text-right">Rate</th><th class="px-3 py-2 text-right">Amount</th><th class="px-3 py-2">Accounting Ledger</th></tr></thead>
<tbody>
{% for item in draft.items %}
<tr class="border-t border-slate-100">
<td class="px-3 py-2 font-semibold">{{ item.stock_item_name }}</td>
<td class="px-3 py-2">{{ item.hsn_sac or '-' }}</td>
<td class="px-3 py-2 text-right">{{ item.quantity }}</td>
<td class="px-3 py-2">{{ item.unit or '-' }}</td>
<td class="px-3 py-2 text-right">{{ '%.4f'|format(item.rate) }}</td>
<td class="px-3 py-2 text-right">₹{{ '%.2f'|format(item.amount) }}</td>
<td class="px-3 py-2">{{ item.accounting_ledger_name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% if draft.last_error %}<div class="mt-3 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-800">{{ draft.last_error }}</div>{% endif %}
{% if draft.status == 'preflight_ready' %}<div class="mt-3 rounded-lg border border-emerald-200 bg-emerald-50 p-3 text-sm text-emerald-800">Tally preflight completed. Ledger existence, balancing and duplicate checks completed.</div>{% endif %}