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

177 lines
21 KiB
HTML

{% extends "ui/templates/base/layout.html" %}
{% block content %}
{% set mirror = mirror_status.mirror if mirror_status and mirror_status.mirror else {} %}
{% set mirror_effective_ready = mirror.ready_for_financial_year or preview %}
<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</p>
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Depreciation (Income Tax)</h1>
<p class="mt-1 text-sm text-slate-500">Calculate Income-tax depreciation from the client SQLite Accounting Mirror. Approved drafts are stored locally and posted through the shared .NET batch writer with Tally read-back verification.</p>
</div>
<a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}&refresh=1{% endif %}" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Back to Tally</a>
</div>
{% if calculated %}<div class="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm font-medium text-emerald-800">Income-tax depreciation draft calculated and saved. It still requires explicit Partner approval before Tally write-back.</div>{% endif %}
{% if approved %}<div class="rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm font-medium text-amber-900">Depreciation draft approved. No Tally voucher has been created yet.</div>{% endif %}
{% if posted %}<div class="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm font-medium text-emerald-800">Approved depreciation Journal posted through the local SQLite queue and verified in TallyPrime.</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 %}
<section class="grid gap-4 md:grid-cols-3">
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">ERP Local Agent</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if agent_online else 'text-red-700' }}">{{ 'Connected' if agent_online else 'Offline' }}</div></div>
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">SQLite Mirror</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if mirror_effective_ready else 'text-amber-700' }}">{{ 'Ready' if mirror_effective_ready else 'Mirror required' }}</div>{% if mirror_effective_ready and mirror.company %}<div class="mt-1 text-xs text-slate-500">{{ mirror.company.company_name }} · {{ fy_start }} to {{ fy_end }}</div>{% elif mirror.financial_year_reason %}<div class="mt-1 text-xs text-amber-700">{{ mirror.financial_year_reason }}</div>{% endif %}</div>
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Write-back</div><div class="mt-2 text-lg font-semibold text-slate-700">Controlled</div><div class="mt-1 text-xs text-slate-500">Approved instructions are persisted in the local .act before .NET batch posting and read-back verification.</div></div>
</section>
<section class="rounded-2xl bg-white p-5 shadow-soft">
<form method="get" action="/tools/tally/depreciation" class="grid gap-4 lg:grid-cols-3 lg:items-end">
<div><label class="text-sm font-medium text-slate-700">ERP Client</label><select name="client_id" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" 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-sm font-medium text-slate-700">Financial Year</label><div class="mt-1 grid grid-cols-2 gap-2"><input type="date" name="fy_start" value="{{ fy_start }}" class="w-full rounded-xl border border-slate-300 px-3 py-2"><input type="date" name="fy_end" value="{{ fy_end }}" class="w-full rounded-xl border border-slate-300 px-3 py-2"></div></div>
<button type="submit" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Load Fixed Assets</button>
</form>
</section>
{% if preview %}
{% set coverage = preview.transaction_coverage %}
<div class="rounded-xl border {{ 'border-emerald-200 bg-emerald-50 text-emerald-800' if coverage.complete else 'border-amber-200 bg-amber-50 text-amber-900' }} px-4 py-3 text-sm">
{% if coverage.complete %}
SQLite mirror data covers the required period {{ coverage.required_from }} to {{ coverage.required_to }}{% if coverage.provisional %}. This is a provisional current-FY calculation; future dates are not required yet{% endif %}.
{% else %}
SQLite mirror data does not yet cover {{ coverage.required_from }} to {{ coverage.required_to }}. You may save a draft, but approval/write-back will remain blocked until the required FY is mirrored.
{% endif %}
</div>
<form method="post" action="/tools/tally/depreciation/calculate" class="space-y-5">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}"><input type="hidden" name="fy_start" value="{{ fy_start }}"><input type="hidden" name="fy_end" value="{{ fy_end }}">
<section class="rounded-2xl bg-white p-5 shadow-soft">
<div class="grid gap-4 md:grid-cols-2">
<div><label class="text-sm font-medium text-slate-700">Depreciation Debit Ledger</label><select id="depreciation-debit-ledger" name="depreciation_expense_ledger" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Depreciation</option>{% for name in preview.all_ledgers %}<option value="{{ name }}">{{ name }}</option>{% endfor %}</select><p class="mt-1 text-xs text-slate-500">Any existing ledger may be selected, including the asset ledger itself.</p></div>
<div><label class="text-sm font-medium text-slate-700">Depreciation Reserve / Credit Ledger</label><select name="depreciation_reserve_ledger" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Depreciation Reserve</option>{% for name in preview.all_ledgers %}<option value="{{ name }}">{{ name }}</option>{% endfor %}</select></div>
</div>
<p class="mt-3 text-xs text-slate-500">These ledger selections are stored with the draft. Phase 6 can post the Journal only after the saved draft is explicitly approved.</p>
</section>
<section class="overflow-hidden rounded-2xl bg-white shadow-soft">
<div class="border-b border-slate-200 px-5 py-4">
<h2 class="font-semibold text-slate-900">Fixed Asset Ledgers</h2>
<p class="mt-1 text-sm text-slate-500">Zero-opening ledgers with no current-period addition are omitted. Effective date is preferred for the 180-day test; voucher date is used when effective date is unavailable.</p>
<div class="mt-4 flex flex-wrap items-end gap-3 rounded-xl border border-slate-200 bg-slate-50 p-3">
<label class="flex items-center gap-2 text-sm font-medium text-slate-700"><input id="select-all-assets" type="checkbox" class="rounded border-slate-300"> Select all displayed ledgers</label>
<div><label class="block text-xs font-semibold uppercase tracking-wide text-slate-500">Bulk rate</label><select id="bulk-rate" class="mt-1 rounded-lg border border-slate-300 px-3 py-2 text-sm"><option value="">Select rate</option><option value="0">0%</option>{% for rate in [5,10,15,20,25,30,40,45,60,80,100] %}<option value="{{ rate }}">{{ rate }}%</option>{% endfor %}</select></div>
<button type="button" id="apply-bulk-rate" class="rounded-lg border border-brand-300 bg-white px-3 py-2 text-sm font-semibold text-brand-700 hover:bg-brand-50">Apply rate to selected</button>
</div>
</div>
<div class="overflow-x-auto"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500"><tr><th class="px-4 py-3 w-10">Select</th><th class="px-4 py-3">Ledger</th><th class="px-4 py-3 text-right">Opening WDV</th><th class="px-4 py-3 text-right">Additions ≥180 days</th><th class="px-4 py-3 text-right">Additions &lt;180 days</th><th class="px-4 py-3 text-right">Other Reductions</th><th class="px-4 py-3 text-right">Depreciation Already Recorded</th><th class="px-4 py-3">Rate</th><th class="px-4 py-3">Posting Ledger</th></tr></thead><tbody class="divide-y divide-slate-100">
{% for row in preview.fixed_asset_ledgers %}
<tr>
<td class="px-4 py-3"><input type="checkbox" class="asset-select rounded border-slate-300" data-ledger-key="{{ row.ledger_key }}"></td>
<td class="px-4 py-3"><div class="font-medium text-slate-900">{{ row.ledger_name }}</div><div class="text-xs text-slate-500">{{ row.parent_group }}</div></td>
<td class="px-4 py-3 text-right">{{ '%.2f'|format(row.opening_wdv) }}</td>
<td class="px-4 py-3 text-right">{{ '%.2f'|format(row.additions_full_rate) }}</td>
<td class="px-4 py-3 text-right">{{ '%.2f'|format(row.additions_half_rate) }}</td>
<td class="px-4 py-3 text-right">{{ '%.2f'|format(row.reductions) }}</td>
<td class="px-4 py-3 text-right">{% if row.depreciation_already_recorded %}<div class="font-semibold text-emerald-700">{{ '%.2f'|format(row.recorded_depreciation) }}</div><div class="text-[11px] text-slate-500">{{ row.recorded_depreciation_voucher_count }} voucher(s)</div>{% else %}<span class="text-slate-400">—</span>{% endif %}</td>
<td class="px-4 py-3"><select id="rate-{{ loop.index0 }}" data-ledger-key="{{ row.ledger_key }}" name="rate__{{ row.ledger_key }}" class="asset-rate rounded-lg border border-slate-300 px-2 py-1.5"><option value="0" {% if row.rate_percent == 0 %}selected{% endif %}>0%</option>{% for rate in [5,10,15,20,25,30,40,45,60,80,100] %}<option value="{{ rate }}" {% if row.rate_percent == rate %}selected{% endif %}>{{ rate }}%</option>{% endfor %}</select></td>
<td class="px-4 py-3"><button type="button" class="use-asset-as-debit text-xs font-semibold text-brand-700 underline underline-offset-2" data-ledger-name="{{ row.ledger_name }}">Use as debit ledger</button></td>
</tr>
{% else %}<tr><td colspan="9" class="px-4 py-8 text-center text-slate-500">No active Fixed Asset ledgers were found. Zero-opening ledgers without additions are intentionally omitted.</td></tr>{% endfor %}
</tbody></table></div>
<div class="flex justify-end border-t border-slate-200 px-5 py-4"><button type="submit" {% if not preview.fixed_asset_ledgers %}disabled{% endif %} class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-50">Calculate & Save Draft</button></div>
</section>
</form>
{% endif %}
{% if depreciation_run %}
{% if depreciation_run.analysis_run_id %}
<div class="flex justify-end gap-2">
<a href="/tools/tally/analysis/{{ depreciation_run.analysis_run_id }}/download?client_id={{ selected_client.id }}&financial_year={{ fy_start[:4] }}-{{ fy_end[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/{{ depreciation_run.analysis_run_id }}/download?client_id={{ selected_client.id }}&financial_year={{ fy_start[:4] }}-{{ fy_end[2:4] }}&format=pdf" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold">Download PDF</a>
</div>
{% endif %}
<section class="rounded-2xl bg-white p-5 shadow-soft"><div class="flex flex-wrap justify-between gap-4"><div><h2 class="font-semibold text-slate-900">Draft Calculation #{{ depreciation_run.id }}</h2><p class="mt-1 text-sm text-slate-500">{{ depreciation_run.company_name }} · {{ depreciation_run.fy_start }} to {{ depreciation_run.fy_end }}</p></div><div class="text-right"><div class="text-xs uppercase tracking-wide text-slate-500">Total Depreciation</div><div class="text-2xl font-semibold text-slate-900">₹ {{ '%.2f'|format(depreciation_run.total_depreciation) }}</div></div></div>
<div class="mt-4 rounded-xl border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-800">Journal: Debit <strong>{{ depreciation_run.depreciation_expense_ledger or 'Depreciation' }}</strong> and credit <strong>{{ depreciation_run.depreciation_reserve_ledger or 'Depreciation Reserve' }}</strong> for ₹ {{ '%.2f'|format(depreciation_run.total_depreciation) }}. Voucher date will be {{ depreciation_run.fy_end }} and reference ARRR-ITDEP-{{ depreciation_run.id }}.</div>
<div class="mt-4 grid gap-3 md:grid-cols-3">
<div class="rounded-xl border border-slate-200 p-3"><div class="text-xs uppercase tracking-wide text-slate-500">Draft Status</div><div class="mt-1 font-semibold text-slate-900">{{ depreciation_run.status|upper }}</div></div>
<div class="rounded-xl border border-slate-200 p-3"><div class="text-xs uppercase tracking-wide text-slate-500">Approved</div><div class="mt-1 font-semibold text-slate-900">{{ depreciation_run.approved_at_utc or 'Not yet approved' }}</div></div>
<div class="rounded-xl border border-slate-200 p-3"><div class="text-xs uppercase tracking-wide text-slate-500">Tally Posting</div><div class="mt-1 font-semibold {{ 'text-emerald-700' if depreciation_run.tally_posting_status == 'posted' else ('text-red-700' if depreciation_run.tally_posting_status == 'failed' else 'text-slate-900') }}">{{ depreciation_run.tally_posting_status|upper if depreciation_run.tally_posting_status else 'NOT POSTED' }}</div></div>
</div>
{% if depreciation_run.tally_posting_error %}<div class="mt-3 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">{{ depreciation_run.tally_posting_error }}</div>{% endif %}
<div class="mt-4 overflow-x-auto"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left text-xs uppercase text-slate-500"><tr><th class="px-3 py-2">Ledger</th><th class="px-3 py-2 text-right">Rate</th><th class="px-3 py-2 text-right">Calculated</th><th class="px-3 py-2 text-right">Already Recorded</th><th class="px-3 py-2 text-right">Unrecorded</th><th class="px-3 py-2 text-right">Closing WDV</th></tr></thead><tbody class="divide-y divide-slate-100">{% for row in depreciation_run.lines %}<tr><td class="px-3 py-2">{{ row.ledger_name }}</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.rate_percent) }}%</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.depreciation_amount) }}</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.recorded_depreciation or 0) }}</td><td class="px-3 py-2 text-right font-semibold">{{ '%.2f'|format(row.remaining_unrecorded_depreciation if row.remaining_unrecorded_depreciation is defined else row.depreciation_amount) }}</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.closing_wdv) }}</td></tr>{% endfor %}</tbody></table></div>
{% if depreciation_run.status == 'draft' %}
<form method="post" action="/tools/tally/depreciation/approve" class="mt-5 rounded-xl border border-amber-200 bg-amber-50 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="run_id" value="{{ depreciation_run.id }}"><input type="hidden" name="fy_start" value="{{ depreciation_run.fy_start }}">
<div class="font-semibold text-amber-900">Partner Approval Required</div>
<p class="mt-1 text-sm text-amber-800">Review the company, financial year, ledgers, rates and total before approving. Approval does not post anything to Tally.</p>
<textarea name="approval_note" rows="2" class="mt-3 w-full rounded-xl border border-amber-300 px-3 py-2" placeholder="Approval note (optional)"></textarea>
<label class="mt-3 flex items-start gap-2 text-sm text-amber-900"><input type="checkbox" name="confirm_approval" value="yes" class="mt-1"><span>I have reviewed this depreciation draft and approve it for Tally posting.</span></label>
<button type="submit" class="mt-3 rounded-xl bg-amber-700 px-4 py-2 text-sm font-semibold text-white hover:bg-amber-800">Approve Draft</button>
</form>
{% elif depreciation_run.status == 'approved' %}
<form method="post" action="/tools/tally/depreciation/post-to-tally" class="mt-5 rounded-xl border border-red-200 bg-red-50 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="run_id" value="{{ depreciation_run.id }}"><input type="hidden" name="fy_start" value="{{ depreciation_run.fy_start }}">
<div class="font-semibold text-red-900">Controlled Tally Write-back</div>
<p class="mt-1 text-sm text-red-800">This creates a real Journal voucher in the mapped company currently open in TallyPrime. The Local Agent re-checks the Tally GUID immediately before posting. Successful drafts cannot be posted twice.</p>
<label class="mt-3 flex items-start gap-2 text-sm text-red-900"><input type="checkbox" name="confirm_posting" value="yes" class="mt-1"><span>Create this Journal voucher in TallyPrime.</span></label>
<div class="mt-3 max-w-xs"><label class="text-sm font-medium text-red-900">Type POST to confirm</label><input name="post_confirmation" autocomplete="off" class="mt-1 w-full rounded-xl border border-red-300 px-3 py-2" placeholder="POST"></div>
<button type="submit" class="mt-3 rounded-xl bg-red-700 px-4 py-2 text-sm font-semibold text-white hover:bg-red-800">Post Approved Journal to Tally</button>
</form>
{% elif depreciation_run.status == 'posted' %}
<div class="mt-5 rounded-xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-800"><strong>Posted successfully.</strong> Posted at {{ depreciation_run.posted_at_utc }}{% if depreciation_run.posted_voucher_number %}; Tally voucher/reference ID {{ depreciation_run.posted_voucher_number }}{% endif %}. This draft is locked against duplicate posting.</div>
{% endif %}
{% if depreciation_run.writeback_attempts %}
<div class="mt-5"><h3 class="text-sm font-semibold text-slate-900">Write-back Audit Trail</h3><div class="mt-2 overflow-x-auto"><table class="min-w-full text-xs"><thead class="bg-slate-50 text-left uppercase text-slate-500"><tr><th class="px-3 py-2">Attempt</th><th class="px-3 py-2">Started</th><th class="px-3 py-2">Status</th><th class="px-3 py-2">Tally result</th><th class="px-3 py-2">Error</th></tr></thead><tbody class="divide-y divide-slate-100">{% for row in depreciation_run.writeback_attempts %}<tr><td class="px-3 py-2">#{{ row.id }}</td><td class="px-3 py-2">{{ row.started_at_utc }}</td><td class="px-3 py-2">{{ row.status|upper }}</td><td class="px-3 py-2">Created {{ row.tally_created }} · Errors {{ row.tally_errors }}{% if row.tally_last_voucher_id %} · ID {{ row.tally_last_voucher_id }}{% endif %}</td><td class="px-3 py-2 text-red-700">{{ row.error_message or '' }}</td></tr>{% endfor %}</tbody></table></div></div>
{% endif %}
</section>
{% endif %}
</div>
<script>
(() => {
const selectAll = document.getElementById('select-all-assets');
const bulkRate = document.getElementById('bulk-rate');
const applyButton = document.getElementById('apply-bulk-rate');
const selections = () => Array.from(document.querySelectorAll('.asset-select'));
if (selectAll) {
selectAll.addEventListener('change', () => {
selections().forEach(cb => { cb.checked = selectAll.checked; });
});
}
if (applyButton) {
applyButton.addEventListener('click', () => {
const value = bulkRate ? bulkRate.value : '';
if (value === '') return;
selections().filter(cb => cb.checked).forEach(cb => {
const key = cb.dataset.ledgerKey;
const rate = Array.from(document.querySelectorAll('.asset-rate')).find(el => el.dataset.ledgerKey === key);
if (rate) rate.value = value;
});
});
}
})();
</script>
{% if analysis_history %}
<section class="rounded-2xl bg-white p-5 shadow-soft">
<h2 class="font-semibold text-slate-900">Depreciation Analysis History</h2>
<div class="mt-3 overflow-x-auto"><table class="min-w-full text-sm"><thead><tr class="text-left text-xs uppercase text-slate-500"><th class="p-2">Run</th><th class="p-2">FY</th><th class="p-2">Period</th><th class="p-2">Completed</th><th class="p-2"></th></tr></thead><tbody>{% for row in analysis_history %}<tr class="border-t"><td class="p-2">#{{ row.id }}</td><td class="p-2">{{ row.financial_year }}</td><td class="p-2">{{ row.period_from }} → {{ row.period_to }}</td><td class="p-2">{{ row.completed_at_utc }}</td><td class="p-2 text-right"><a class="font-semibold text-brand-700" href="/tools/tally/analysis/{{ row.id }}/download?client_id={{ selected_client.id }}&financial_year={{ row.financial_year }}&format=xlsx">Excel</a> · <a class="font-semibold text-brand-700" href="/tools/tally/analysis/{{ row.id }}/download?client_id={{ selected_client.id }}&financial_year={{ row.financial_year }}&format=pdf">PDF</a></td></tr>{% endfor %}</tbody></table></div>
</section>
{% endif %}
<script>
document.addEventListener("DOMContentLoaded",function(){
const debit=document.getElementById("depreciation-debit-ledger");
document.querySelectorAll(".use-asset-as-debit").forEach(function(button){
button.addEventListener("click",function(){
if(!debit)return;
debit.value=button.dataset.ledgerName||"";
debit.scrollIntoView({behavior:"smooth",block:"center"});
debit.focus();
});
});
});
</script>
{% endblock %}