Files
arrr-erp/app/modules/accounting/templates/accounting/cash_payment_review.html
T
2026-09-06 22:45:18 +05:30

176 lines
17 KiB
HTML

{% 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 · SQLite Mirror</p>
<h1 class="mt-1 text-2xl font-semibold text-slate-900">{{ 'Cash Payment Entry Allocation' if page_mode == 'allocation' else 'Cash Payment Compliance Review' }}</h1>
{% if page_mode == 'allocation' %}
<p class="mt-1 max-w-4xl text-sm text-slate-600">Prepare and validate cash payment entry allocation using only actual source payment dates. The tool checks totals, the configured daily limit and the permitted source-date window; it never invents or changes dates.</p>
{% else %}
<p class="mt-1 max-w-4xl text-sm text-slate-600">Analyse cash-payment compliance directly from the client's local SQLite Accounting Mirror. TallyPrime is not contacted during this review.</p>
{% endif %}
</div>
<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 text-slate-700">Back to Tally Mirror</a>
</div>
{% if saved %}<div class="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-800">Cash payment rule settings saved.</div>{% endif %}
{% if command_error %}<div class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">{{ command_error }}</div>{% endif %}
{% if page_mode != 'allocation' %}
<section class="grid gap-4 lg:grid-cols-3">
<div class="rounded-2xl bg-white p-5 shadow-soft lg:col-span-2">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<h2 class="text-lg font-semibold text-slate-900">Review from Accounting Mirror</h2>
<p class="mt-1 text-sm text-slate-500">No Tally connection, company selection, extraction queue or day-by-day cache is used here.</p>
</div>
{% set mirror = (mirror_status.mirror if mirror_status and mirror_status.mirror else {}) %}
{% if mirror.ready %}
<span class="rounded-full bg-emerald-50 px-3 py-1 text-xs font-semibold text-emerald-700">SQLite Mirror Ready</span>
{% else %}
<span class="rounded-full bg-amber-50 px-3 py-1 text-xs font-semibold text-amber-800">Mirror Required</span>
{% endif %}
</div>
<form method="get" action="/tools/accounting/cash-payments" class="mt-4 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<div>
<label class="text-xs font-semibold uppercase text-slate-500">Client</label>
<select name="client_id" class="mt-1 w-full rounded-xl border-slate-300 text-sm" required onchange="this.form.submit()">
<option value="">Select client</option>
{% for client in clients %}<option value="{{ client.id }}" {% if selected_client and selected_client.id == client.id %}selected{% endif %}>{{ client.client_name }}</option>{% endfor %}
</select>
</div>
<div><label class="text-xs font-semibold uppercase text-slate-500">From</label><input type="date" name="date_from" value="{{ date_from }}" class="mt-1 w-full rounded-xl border-slate-300 text-sm" required></div>
<div><label class="text-xs font-semibold uppercase text-slate-500">To</label><input type="date" name="date_to" value="{{ date_to }}" class="mt-1 w-full rounded-xl border-slate-300 text-sm" required></div>
<div class="flex items-end">
<button type="submit" name="analyze" value="1" class="w-full rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-50" {% if not selected_client or not mirror.ready %}disabled{% endif %}>Review Cash Payments</button>
</div>
</form>
{% if selected_client %}
<div class="mt-5 grid gap-3 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-xl bg-slate-50 p-3">
<div class="text-xs font-semibold uppercase text-slate-500">Mirror Company</div>
<div class="mt-1 text-sm font-semibold text-slate-900">{{ mirror.company.company_name if mirror.ready and mirror.company else 'Not mirrored' }}</div>
</div>
<div class="rounded-xl bg-slate-50 p-3">
<div class="text-xs font-semibold uppercase text-slate-500">Mirror Period</div>
<div class="mt-1 text-sm font-semibold text-slate-900">{% if mirror.ready and mirror.voucher_period %}{{ mirror.voucher_period.from_date or '—' }} → {{ mirror.voucher_period.to_date or '—' }}{% else %}—{% endif %}</div>
</div>
<div class="rounded-xl bg-slate-50 p-3">
<div class="text-xs font-semibold uppercase text-slate-500">Vouchers Available</div>
<div class="mt-1 text-sm font-semibold text-slate-900">{{ mirror.counts.voucher if mirror.ready and mirror.counts else 0 }}</div>
</div>
<div class="rounded-xl bg-slate-50 p-3">
<div class="text-xs font-semibold uppercase text-slate-500">Voucher Lines</div>
<div class="mt-1 text-sm font-semibold text-slate-900">{{ mirror.counts.voucher_ledger_entry if mirror.ready and mirror.counts else 0 }}</div>
</div>
</div>
{% if mirror.ready %}
<p class="mt-3 break-all text-xs text-slate-500">Source: {{ mirror.path or mirror_status.mirror_db_path or '' }}</p>
{% else %}
<div class="mt-4 rounded-xl border border-amber-200 bg-amber-50 p-3 text-sm text-amber-900">Mirror this client first from <strong>Tally Accounting → Mirror Tally to SQLite</strong>. After that, Cash Payment Review works even if TallyPrime is closed.</div>
{% endif %}
{% endif %}
</div>
<div class="rounded-2xl bg-white p-5 shadow-soft">
<h2 class="text-lg font-semibold text-slate-900">Rule Settings</h2>
<form method="post" action="/tools/accounting/cash-payments/settings" class="mt-4 space-y-3">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id if selected_client else '' }}">
<div><label class="text-xs font-semibold uppercase text-slate-500">Cash payment limit</label><input type="number" min="1" step="0.01" name="cash_limit" value="{{ '%.2f'|format(setting.cash_limit) }}" class="mt-1 w-full rounded-xl border-slate-300 text-sm" required></div>
<div><label class="text-xs font-semibold uppercase text-slate-500">Effective from</label><input type="date" name="effective_from" value="{{ setting.effective_from.isoformat() }}" class="mt-1 w-full rounded-xl border-slate-300 text-sm" required></div>
<div><label class="text-xs font-semibold uppercase text-slate-500">Split review window (days)</label><input type="number" min="1" max="31" name="split_window_days" value="{{ setting.split_window_days }}" class="mt-1 w-full rounded-xl border-slate-300 text-sm" required></div>
<div><label class="text-xs font-semibold uppercase text-slate-500">Near-limit threshold (%)</label><input type="number" min="1" max="100" step="0.1" name="near_limit_percent" value="{{ setting.near_limit_percent }}" class="mt-1 w-full rounded-xl border-slate-300 text-sm" required></div>
<div><label class="text-xs font-semibold uppercase text-slate-500">Max source-date allocation window</label><input type="number" min="1" max="366" name="max_allocation_days" value="{{ setting.max_allocation_days }}" class="mt-1 w-full rounded-xl border-slate-300 text-sm" required></div>
<button class="w-full rounded-xl border border-brand-300 bg-brand-50 px-4 py-2 text-sm font-semibold text-brand-700">Save Settings</button>
</form>
</div>
</section>
{% if review %}
<section class="space-y-4">
<div class="flex flex-wrap 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-4 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 border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700">Download PDF</a>
</div>
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-5">
{% for label, value in [('Vouchers reviewed', review.summary.vouchers_reviewed), ('Cash payments', review.summary.cash_payment_vouchers), ('Single-voucher exceptions', review.summary.single_voucher_exceptions), ('Same-day exceptions', review.summary.same_day_exceptions), ('Possible split patterns', review.summary.possible_split_patterns)] %}
<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 text-slate-900">{{ value }}</div></div>
{% endfor %}
</div>
<div class="rounded-2xl bg-white p-5 shadow-soft overflow-x-auto">
<h3 class="font-semibold text-slate-900">Above-limit / same-day aggregate exceptions</h3>
<table class="mt-3 min-w-full text-sm"><thead class="text-left text-xs uppercase text-slate-500"><tr><th class="p-2">Date</th><th class="p-2">Party / ledger</th><th class="p-2">Amount</th><th class="p-2">Reason</th><th class="p-2">Voucher(s)</th></tr></thead><tbody>
{% for row in review.exceptions %}<tr class="border-t border-slate-100"><td class="p-2">{{ row.date }}</td><td class="p-2 font-medium">{{ row.party }}</td><td class="p-2">₹{{ '%.2f'|format(row.amount) }}</td><td class="p-2">{{ row.reason }}</td><td class="p-2">{{ row.voucher_numbers|join(', ') }}</td></tr>{% else %}<tr><td colspan="5" class="p-4 text-slate-500">No above-limit exception found for the selected period.</td></tr>{% endfor %}
</tbody></table>
</div>
<div class="rounded-2xl bg-white p-5 shadow-soft overflow-x-auto">
<h3 class="font-semibold text-slate-900">Possible split-payment patterns — review only</h3>
<p class="mt-1 text-xs text-slate-500">These rows are indicators for auditor review; they do not by themselves determine tax treatment.</p>
<table class="mt-3 min-w-full text-sm"><thead class="text-left text-xs uppercase text-slate-500"><tr><th class="p-2">Party / ledger</th><th class="p-2">Period</th><th class="p-2">Total</th><th class="p-2">Daily amounts</th></tr></thead><tbody>
{% for row in review.possible_split_payments %}<tr class="border-t border-slate-100"><td class="p-2 font-medium">{{ row.party }}</td><td class="p-2">{{ row.date_from }} → {{ row.date_to }}</td><td class="p-2">₹{{ '%.2f'|format(row.total_amount) }}</td><td class="p-2">{% for item in row.days %}{{ item.date }}: ₹{{ '%.2f'|format(item.amount) }}{% if not loop.last %}<br>{% endif %}{% endfor %}</td></tr>{% else %}<tr><td colspan="4" class="p-4 text-slate-500">No near-limit multi-day pattern found.</td></tr>{% endfor %}
</tbody></table>
</div>
</section>
{% endif %}
{% endif %}
{% if page_mode == 'allocation' %}
<section class="rounded-2xl bg-white p-5 shadow-soft">
<h2 class="text-lg font-semibold text-slate-900">Cash Payment Entry Allocation — Actual Source Dates</h2>
<p class="mt-1 text-sm text-slate-600">Use this when the source records establish that one accounting amount was actually paid on multiple dates. Enter the real dates and amounts. The tool validates totals, daily limits and the configured date window; it does not fabricate dates or automatically rewrite Tally vouchers.</p>
<form method="post" action="/tools/accounting/cash-payments/allocation-plan" class="mt-4 grid gap-4 lg:grid-cols-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div><label class="text-xs font-semibold uppercase text-slate-500">Client</label><select name="client_id" class="mt-1 w-full rounded-xl border-slate-300 text-sm" required>{% for client in clients %}<option value="{{ client.id }}" {% if selected_client and selected_client.id == client.id %}selected{% endif %}>{{ client.client_name }}</option>{% endfor %}</select></div>
<div><label class="text-xs font-semibold uppercase text-slate-500">Total accounting amount</label><input type="number" min="0.01" step="0.01" name="total_amount" value="{{ allocation_input.total_amount }}" class="mt-1 w-full rounded-xl border-slate-300 text-sm" required></div>
<div><label class="text-xs font-semibold uppercase text-slate-500">Party</label><input name="party_name" value="{{ allocation_input.party_name }}" class="mt-1 w-full rounded-xl border-slate-300 text-sm"></div>
<div><label class="text-xs font-semibold uppercase text-slate-500">Expense / purchase ledger</label><input name="expense_ledger" value="{{ allocation_input.expense_ledger }}" class="mt-1 w-full rounded-xl border-slate-300 text-sm"></div>
<div class="lg:col-span-2"><label class="text-xs font-semibold uppercase text-slate-500">Actual source payments — one per line: YYYY-MM-DD, amount</label><textarea name="source_payments" rows="7" class="mt-1 w-full rounded-xl border-slate-300 font-mono text-sm" placeholder="2026-09-01, 8500.00
2026-09-02, 9000.00">{{ allocation_input.source_payments }}</textarea></div>
<div class="lg:col-span-2"><button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white">Validate Source-Date Allocation</button></div>
</form>
{% if allocation_plan %}
<div class="mt-5 rounded-xl border {% if allocation_plan.status == 'ready' %}border-emerald-200 bg-emerald-50{% else %}border-amber-200 bg-amber-50{% endif %} p-4">
<div class="font-semibold {% if allocation_plan.status == 'ready' %}text-emerald-800{% else %}text-amber-900{% endif %}">{{ 'Ready for accounting review' if allocation_plan.status == 'ready' else 'Manual review required' }}</div>
{% if allocation_plan.errors %}<ul class="mt-2 list-disc pl-5 text-sm text-amber-900">{% for item in allocation_plan.errors %}<li>{{ item }}</li>{% endfor %}</ul>{% endif %}
<div class="mt-3 text-sm text-slate-700">Expected total: ₹{{ '%.2f'|format(allocation_plan.expected_total or 0) }} · Source total: ₹{{ '%.2f'|format(allocation_plan.supplied_total or 0) }} · Date span: {{ allocation_plan.span_days or 0 }} day(s) / {{ allocation_plan.max_allocation_days or setting.max_allocation_days }} allowed.</div>
{% if allocation_plan.entries %}<table class="mt-3 min-w-full text-sm"><thead><tr><th class="p-2 text-left">Actual Date</th><th class="p-2 text-left">Proposed Entry Amount</th></tr></thead><tbody>{% for row in allocation_plan.entries %}<tr class="border-t border-slate-200"><td class="p-2">{{ row.date }}</td><td class="p-2">₹{{ '%.2f'|format(row.amount) }}</td></tr>{% endfor %}</tbody></table>{% endif %}
<p class="mt-3 text-xs text-slate-600">{{ allocation_plan.note or 'No accounting dates are generated automatically.' }}</p>
{% if allocation_plan.status == 'ready' %}
<form method="post" action="/tools/accounting/cash-payments/allocation/post" class="mt-4 rounded-xl border border-slate-200 bg-white p-4">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="client_id" value="{{ selected_client.id }}">
<input type="hidden" name="total_amount" value="{{ allocation_input.total_amount }}">
<input type="hidden" name="party_name" value="{{ allocation_input.party_name }}">
<input type="hidden" name="expense_ledger" value="{{ allocation_input.expense_ledger }}">
<input type="hidden" name="source_payments" value="{{ allocation_input.source_payments }}">
<div class="grid gap-4 md:grid-cols-2">
<div><label class="text-xs font-semibold uppercase text-slate-500">Debit ledger</label><input value="{{ allocation_input.expense_ledger }}" disabled class="mt-1 w-full rounded-xl border-slate-300 bg-slate-50 text-sm"><p class="mt-1 text-xs text-slate-500">The reviewed expense / purchase ledger is debited.</p></div>
<div><label class="text-xs font-semibold uppercase text-slate-500">Cash ledger</label><input name="cash_ledger" value="{{ allocation_input.cash_ledger or 'Cash' }}" class="mt-1 w-full rounded-xl border-slate-300 text-sm" required></div>
</div>
<div class="mt-4 flex flex-wrap items-center gap-3">
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="confirm_posting" value="yes" required> I reviewed the actual source dates and amounts.</label>
<input name="confirm_text" placeholder="Type POST" class="rounded-lg border border-slate-300 px-3 py-2 text-sm" required>
<button class="rounded-lg bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Post Split Entries to Tally</button>
</div>
<p class="mt-2 text-xs text-slate-500">The entries are first stored in this client's local .act write queue, then transferred to Tally in .NET batches and read back for verification.</p>
</form>
{% endif %}
</div>
{% endif %}
{% if posting_result %}
<div class="mt-5 rounded-xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-900">
<div class="font-semibold">Cash payment split batch #{{ posting_result.batch_id }} posted and verified.</div>
<div class="mt-1">{{ posting_result.batch.batch.verified_count }} voucher(s) verified in TallyPrime.</div>
</div>
{% endif %}
</section>
{% endif %}
</div>
{% endblock %}