Use common SQLite master for cash payment workflow

This commit is contained in:
A R R R Associates
2026-09-04 22:23:17 +05:30
parent 7fadbb9f4b
commit 8536dc0457
5 changed files with 278 additions and 71 deletions
@@ -67,17 +67,23 @@
{% 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 Cash Ledgers</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</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 Master Data</button>
</div>
<p id="cash-ledger-help" class="mt-1 text-xs text-slate-500">Uses the same live Tally master-reading mechanism as Opening Balance Review. By default only ledgers under Cash-in-Hand are shown; choose “All Tally ledgers” only for manual selection when required.</p>
<p id="cash-ledger-help" class="mt-1 text-xs text-slate-500">Reads the common ledger master already stored in the client .act SQLite database. TallyPrime is contacted only when you click “Refresh Master Data” or when no local master snapshot exists yet.</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 Cash ledger list…</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>
<div class="mt-3 grid grid-cols-3 gap-2 text-[11px]">
<div id="cash-master-step-1" class="rounded-lg border border-slate-200 bg-slate-50 px-2 py-2 text-center"><div class="font-semibold">1. Local Master</div><div class="mt-1 text-slate-500">.act SQLite</div></div>
<div id="cash-master-step-2" class="rounded-lg border border-slate-200 bg-slate-50 px-2 py-2 text-center"><div class="font-semibold">2. Cash Filter</div><div class="mt-1 text-slate-500">Cash-in-Hand</div></div>
<div id="cash-master-step-3" class="rounded-lg border border-slate-200 bg-slate-50 px-2 py-2 text-center"><div class="font-semibold">3. Ledger Ready</div><div class="mt-1 text-slate-500">No Tally query</div></div>
</div>
<div id="cash-master-meta" class="mt-2 text-[11px] text-slate-500">Master source: local .act SQLite. Use Refresh Master Data only when Tally masters have changed.</div>
</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="md:col-span-2 xl:col-span-5"><button type="button" id="cash-review-button" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white" {% if not selected_client %}disabled{% endif %}>Review Cash Transactions</button><p class="mt-2 text-xs text-slate-500">Select the Tally company, choose a Cash-in-Hand ledger, and review its cash transactions for the selected period. The existing background extraction, threshold analysis and dual VPS/Local Agent progress are preserved.</p></div>
<div class="md:col-span-2 xl:col-span-5"><button type="button" id="cash-review-button" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white" {% if not selected_client %}disabled{% endif %}>Review Cash Transactions</button><p class="mt-2 text-xs text-slate-500">Select the Tally company and Cash ledger from the local .act master snapshot. Only the selected ledger’s relevant transactions are then extracted from Tally into SQLite; compliance analysis runs from SQLite. Existing threshold rules, allocation workflow and dual VPS/Local Agent progress are preserved.</p></div>
</form>
</div>
@@ -191,7 +197,7 @@
<div id="erp-cash-step-4" class="rounded-lg bg-slate-100 px-3 py-2">4. Analyse</div>
<div id="erp-cash-step-5" class="rounded-lg bg-slate-100 px-3 py-2">5. Complete</div>
</div>
<p class="mt-4 text-xs text-slate-500">The confirmed Cash ledger is used throughout this job. The Local Agent reads Tally in safe one-day batches and stores only vouchers containing that ledger in the cash-review cache. You can close this ERP page; the background job continues and remains visible on the Local Agent dashboard.</p>
<p class="mt-4 text-xs text-slate-500">The confirmed Cash ledger comes from the common local .act master snapshot. The Local Agent then reads Tally in safe one-day batches and stores only vouchers containing that ledger in SQLite. Analysis runs from SQLite. You can close this ERP page; the background job continues and remains visible on the Local Agent dashboard.</p>
<div id="cash-progress-error" class="mt-4 hidden rounded-xl border border-amber-200 bg-amber-50 p-3 text-sm text-amber-900"></div>
<div class="mt-4 flex justify-end gap-2">
<button type="button" id="cash-progress-cancel" class="hidden rounded-xl border border-amber-300 bg-amber-50 px-4 py-2 text-sm font-semibold text-amber-900">Cancel Extraction</button>
@@ -232,8 +238,21 @@
const ledgerProgressBar = document.getElementById('cash-ledger-progress-bar');
const ledgerProgressPct = document.getElementById('cash-ledger-progress-pct');
const ledgerProgressStage = document.getElementById('cash-ledger-progress-stage');
const masterStep1 = document.getElementById('cash-master-step-1');
const masterStep2 = document.getElementById('cash-master-step-2');
const masterStep3 = document.getElementById('cash-master-step-3');
const masterMeta = document.getElementById('cash-master-meta');
let ledgerPollTimer = null;
function paintMasterSteps(step) {
[masterStep1, masterStep2, masterStep3].forEach(function(el, idx) {
if (!el) return;
el.className = 'rounded-lg border px-2 py-2 text-center ' + (idx < step
? 'border-emerald-200 bg-emerald-50 text-emerald-900'
: 'border-slate-200 bg-slate-50 text-slate-700');
});
}
function renderLedgerOptions() {
const previous = ledgerSelect.value;
const rows = (ledgerScope && ledgerScope.value === 'all') ? allLedgerRows : cashLedgerRows;
@@ -267,6 +286,12 @@
from_cache: Boolean(data.from_cache)
};
renderLedgerOptions();
paintMasterSteps(3);
if (masterMeta) {
const synced = data.master_synced_at_utc ? new Date(data.master_synced_at_utc).toLocaleString() : 'time not available';
const source = data.master_refreshed ? 'TallyPrime → local .act SQLite (refreshed now)' : 'local .act SQLite';
masterMeta.textContent = 'Master source: ' + source + ' · Last master sync: ' + synced + ' · ' + String(data.total_ledgers_examined || data.ledger_count || 0) + ' ledger(s) in common snapshot.';
}
}
function updateLedgerProgress(job) {
@@ -275,6 +300,9 @@
ledgerProgressBar.style.width=p+'%';
ledgerProgressPct.textContent=Math.round(p)+'%';
ledgerProgressStage.textContent=job.stage||job.status||'Working…';
if (p >= 90) paintMasterSteps(3);
else if (p >= 60) paintMasterSteps(2);
else if (p >= 15) paintMasterSteps(1);
}
async function pollLedgers(jobId, clientId, dateFrom) {
@@ -310,9 +338,20 @@
fd.set('ledger_scope', selectedScope);
if(forceRefresh) fd.set('force_refresh','1');
ledgerButton.disabled = true; if(ledgerRefreshButton) ledgerRefreshButton.disabled=true;
ledgerHelp.textContent = selectedScope === 'all'
? 'Reading all Tally ledgers using the proven master snapshot…'
: 'Reading Tally masters and selecting Cash-in-Hand ledgers…';
paintMasterSteps(0);
ledgerProgress.classList.remove('hidden');
ledgerProgressBar.style.width='15%'; ledgerProgressPct.textContent='15%';
if (forceRefresh) {
ledgerProgressStage.textContent='Refreshing common accounting masters from TallyPrime…';
ledgerHelp.textContent='Refreshing the shared .act master snapshot once, then filtering the Cash-in-Hand hierarchy locally.';
if (masterMeta) masterMeta.textContent='Master source: refreshing TallyPrime → local .act SQLite.';
} else {
ledgerProgressStage.textContent='Reading common ledger master from local SQLite…';
ledgerHelp.textContent = selectedScope === 'all'
? 'Reading all ledgers from the common local .act master snapshot…'
: 'Reading local .act masters and selecting Cash-in-Hand ledgers…';
if (masterMeta) masterMeta.textContent='Master source: local .act SQLite. TallyPrime is not queried for this normal load.';
}
try {
const response = await fetch('/tools/accounting/cash-payments/ledgers', {method:'POST', body:fd, headers:{'Accept':'application/json'}});
const data = await response.json();
@@ -323,8 +362,9 @@
ledgerProgress.classList.remove('hidden');
ledgerProgressBar.style.width='100%'; ledgerProgressPct.textContent='100%';
ledgerProgressStage.textContent = data.cash_only
? String(data.ledger_count || 0)+' Cash-in-Hand ledger(s) ready'
: String(data.ledger_count || 0)+' ledger(s) ready';
? String(data.ledger_count || 0)+' Cash-in-Hand ledger(s) ready from SQLite'
: String(data.ledger_count || 0)+' ledger(s) ready from SQLite';
paintMasterSteps(3);
window.setTimeout(()=>ledgerProgress.classList.add('hidden'),1200);
return;
}