Fix opening balance scope and add async cash ledger loading

This commit is contained in:
A R R R Associates
2026-09-04 15:30:39 +05:30
parent 58752e30e1
commit 3ad20087be
9 changed files with 508 additions and 34 deletions
@@ -49,8 +49,13 @@
{% if selected_cash_ledger_name %}<option value="{{ selected_cash_ledger_name }}" selected>{{ selected_cash_ledger_name }}</option>{% endif %}
</select>
<button type="button" id="cash-load-ledgers" class="rounded-xl border border-slate-300 bg-white px-3 py-2 text-xs font-semibold text-slate-700">Load</button>
<button type="button" id="cash-refresh-ledgers" class="rounded-xl border border-slate-300 bg-white px-3 py-2 text-xs font-semibold text-slate-700">Refresh from Tally</button>
</div>
<p id="cash-ledger-help" class="mt-1 text-xs text-slate-500">Loads cached Tally ledgers instantly when available. Refresh from Tally runs safely in the Local Agent background and shows progress here and on the Local Agent dashboard.</p>
<div id="cash-ledger-progress" class="mt-2 hidden rounded-xl border border-sky-200 bg-sky-50 p-3">
<div class="flex items-center justify-between text-xs font-semibold text-sky-900"><span id="cash-ledger-progress-stage">Preparing ledger sync…</span><span id="cash-ledger-progress-pct">0%</span></div>
<div class="mt-2 h-2 overflow-hidden rounded-full bg-sky-100"><div id="cash-ledger-progress-bar" class="h-full rounded-full bg-sky-600 transition-all duration-500" style="width:0%"></div></div>
</div>
<p id="cash-ledger-help" class="mt-1 text-xs text-slate-500">Reads the Tally ledger master and lets you confirm the exact Cash ledger.</p>
</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>
@@ -200,27 +205,81 @@
const ledgerSelect = document.getElementById('cash-ledger-select');
const ledgerHelp = document.getElementById('cash-ledger-help');
async function loadCashLedgers() {
const ledgerRefreshButton = document.getElementById('cash-refresh-ledgers');
const ledgerProgress = document.getElementById('cash-ledger-progress');
const ledgerProgressBar = document.getElementById('cash-ledger-progress-bar');
const ledgerProgressPct = document.getElementById('cash-ledger-progress-pct');
const ledgerProgressStage = document.getElementById('cash-ledger-progress-stage');
let ledgerPollTimer = null;
function populateLedgers(data) {
const previous = ledgerSelect.value;
ledgerSelect.innerHTML = '<option value="">Confirm Cash ledger</option>';
(data.ledgers || []).forEach(function(row){
const opt=document.createElement('option');
opt.value=row.name;
opt.textContent=row.name + (row.parent ? ' — '+row.parent : '') + (row.is_cash_candidate ? ' • Cash candidate' : '');
if ((previous && previous===row.name) || (!previous && row.is_cash_candidate && !ledgerSelect.value)) opt.selected=true;
ledgerSelect.appendChild(opt);
});
if (!ledgerSelect.value && (data.cash_candidates||[]).length===1) ledgerSelect.value=data.cash_candidates[0].name;
ledgerHelp.textContent = String(data.ledger_count || 0)+' ledger(s) ready from '+(data.company_name || 'Tally')+(data.from_cache?' (local cache).':'.')+' Confirm the Cash ledger before extraction.';
}
function updateLedgerProgress(job) {
const p=Math.max(0,Math.min(100,Number(job.percent||0)));
ledgerProgress.classList.remove('hidden');
ledgerProgressBar.style.width=p+'%';
ledgerProgressPct.textContent=Math.round(p)+'%';
ledgerProgressStage.textContent=job.stage||job.status||'Working…';
}
async function pollLedgers(jobId, clientId, dateFrom) {
try {
const url=new URL('/tools/accounting/cash-payments/ledgers/progress',window.location.origin);
url.searchParams.set('client_id',clientId); url.searchParams.set('job_id',jobId); url.searchParams.set('date_from',dateFrom);
const response=await fetch(url.toString(),{headers:{'Accept':'application/json'},cache:'no-store'});
const data=await response.json();
if(!response.ok||!data.ok) throw new Error(data.error||'Could not read Tally ledger progress.');
const job=data.job||{}; updateLedgerProgress(job);
if(job.status==='completed'){
populateLedgers(data); ledgerButton.disabled=false; if(ledgerRefreshButton) ledgerRefreshButton.disabled=false;
window.setTimeout(()=>ledgerProgress.classList.add('hidden'),1800); return;
}
if(job.status==='failed') throw new Error(job.error||'Tally ledger sync failed.');
ledgerPollTimer=window.setTimeout(()=>pollLedgers(jobId,clientId,dateFrom),1500);
} catch(err) {
ledgerHelp.textContent=err&&err.message?err.message:String(err); ledgerButton.disabled=false;
if(ledgerRefreshButton) ledgerRefreshButton.disabled=false;
ledgerProgressStage.textContent=ledgerHelp.textContent;
}
}
async function loadCashLedgers(forceRefresh) {
const fd = new FormData(form);
if (!fd.get('client_id') || !fd.get('tally_guid')) { ledgerHelp.textContent = 'Select a client and Tally company first.'; return; }
fd.set('csrf_token', document.getElementById('cash-csrf-token').value);
ledgerButton.disabled = true; ledgerHelp.textContent = 'Reading Tally ledger master…';
if(forceRefresh) fd.set('force_refresh','1');
ledgerButton.disabled = true; if(ledgerRefreshButton) ledgerRefreshButton.disabled=true;
ledgerHelp.textContent = forceRefresh ? 'Starting background refresh from Tally…' : 'Loading cached ledgers or starting background Tally sync…';
try {
const response = await fetch('/tools/accounting/cash-payments/ledgers', {method:'POST', body:fd, headers:{'Accept':'application/json'}});
const data = await response.json();
if (!response.ok || !data.ok) throw new Error(data.error || 'Could not load Tally ledgers.');
const previous = ledgerSelect.value; ledgerSelect.innerHTML = '<option value="">Confirm Cash ledger</option>';
(data.ledgers || []).forEach(function(row){
const opt=document.createElement('option'); opt.value=row.name; opt.textContent=row.name + (row.parent ? ' — '+row.parent : '') + (row.is_cash_candidate ? ' • Cash candidate' : '');
if ((previous && previous===row.name) || (!previous && row.is_cash_candidate && !ledgerSelect.value)) opt.selected=true;
ledgerSelect.appendChild(opt);
});
if (!ledgerSelect.value && (data.cash_candidates||[]).length===1) ledgerSelect.value=data.cash_candidates[0].name;
ledgerHelp.textContent = String(data.ledger_count || 0)+' ledger(s) loaded from '+(data.company_name || 'Tally')+'. Confirm the Cash ledger before extraction.';
} catch(err) { ledgerHelp.textContent = err && err.message ? err.message : String(err); }
finally { ledgerButton.disabled=false; }
if ((data.ledgers||[]).length) {
populateLedgers(data); ledgerButton.disabled=false; if(ledgerRefreshButton) ledgerRefreshButton.disabled=false; return;
}
const job=data.job||{};
if(!job.job_id) throw new Error('Local Agent did not return a ledger-sync job id.');
updateLedgerProgress(job);
pollLedgers(job.job_id,String(fd.get('client_id')||''),String(fd.get('date_from')||''));
} catch(err) {
ledgerHelp.textContent = err && err.message ? err.message : String(err);
ledgerButton.disabled=false; if(ledgerRefreshButton) ledgerRefreshButton.disabled=false;
}
}
if (ledgerButton) ledgerButton.addEventListener('click', loadCashLedgers);
if (ledgerButton) ledgerButton.addEventListener('click', function(){loadCashLedgers(false);});
if (ledgerRefreshButton) ledgerRefreshButton.addEventListener('click', function(){loadCashLedgers(true);});
function show() {
overlay.classList.remove('hidden');
@@ -6,7 +6,7 @@
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-brand-600">Accounting · Phase 21A v2</p>
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Opening Balance Review & Correction</h1>
<p class="mt-1 max-w-5xl text-sm text-slate-500">
Summary-first review of previous-year closing balances against current-year openings. Matched and new zero-balance masters stay out of the main review; actionable differences are shown immediately and retain the existing controlled Tally correction workflow.
Summary-first review of previous-year closing balances against current-year openings. Only Balance Sheet ledgers and Stock Items are checked; revenue, expense, purchase and sales ledgers are excluded. Matched and new zero-balance masters stay out of the main review; actionable differences retain the existing controlled Tally correction workflow.
</p>
</div>
<div class="flex flex-wrap gap-2">
@@ -77,6 +77,10 @@
{% endif %}
{% if selected_run %}
<div class="mb-4 flex justify-end">
<a href="/tools/accounting/opening-balances/download/{{ selected_run.id }}?client_id={{ selected_client.id }}" class="rounded-xl border border-emerald-300 bg-emerald-50 px-4 py-2 text-sm font-semibold text-emerald-800">Download Excel Report</a>
</div>
<section class="rounded-2xl bg-white p-5 shadow-soft">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>