Add cash ledger extraction and dual progress UI
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
<section class="grid gap-4 lg:grid-cols-3">
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft lg:col-span-2">
|
||||
<h2 class="text-lg font-semibold text-slate-900">Cash Payment Compliance Review</h2>
|
||||
<form id="cash-review-form" method="get" action="/tools/accounting/cash-payments" class="mt-4 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<form id="cash-review-form" method="get" action="/tools/accounting/cash-payments" class="mt-4 grid gap-4 md:grid-cols-2 xl:grid-cols-5">
|
||||
<input type="hidden" id="cash-csrf-token" value="{{ csrf_token }}">
|
||||
<div>
|
||||
<label class="text-xs font-semibold uppercase text-slate-500">Client</label>
|
||||
@@ -41,9 +41,20 @@
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs font-semibold uppercase text-slate-500">Cash Ledger</label>
|
||||
<div class="mt-1 flex gap-2">
|
||||
<select id="cash-ledger-select" name="cash_ledger_name" class="min-w-0 flex-1 rounded-xl border-slate-300 text-sm" required>
|
||||
<option value="">Load and confirm cash ledger</option>
|
||||
{% 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>
|
||||
</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>
|
||||
<div class="md:col-span-2 xl:col-span-4"><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 %}>Extract / Refresh & Analyze</button><p class="mt-2 text-xs text-slate-500">Transactions are copied very slowly into the Local Agent SQLite accounting cache one day at a time. Analysis starts only after TallyPrime has been released.</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 %}>Extract / Refresh & Analyze</button><p class="mt-2 text-xs text-slate-500">Only vouchers containing the confirmed Cash ledger are stored in the cash-review cache. Extraction runs in the Local Agent background; the same graphical progress is visible here and on the Local Agent dashboard.</p></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -68,7 +79,7 @@
|
||||
<h3 class="font-semibold text-sky-950">Local Tally SQLite Cache</h3>
|
||||
<p class="mt-1 text-sm text-sky-800">Status: <strong>{{ cache_job.status }}</strong>{% if cache_job.stage %} · {{ cache_job.stage }}{% endif %}</p>
|
||||
</div>
|
||||
<div class="text-right text-sm text-sky-900"><div>{{ cache_job.completed_days }}/{{ cache_job.total_days }} day(s)</div><div>{{ cache_job.vouchers_cached }} voucher(s) cached</div></div>
|
||||
<div class="text-right text-sm text-sky-900"><div>{{ cache_job.completed_days }}/{{ cache_job.total_days }} day(s)</div><div>{{ cache_job.vouchers_cached }} Cash-ledger voucher(s)</div>{% if cache_job.cash_ledgers %}<div>Ledger: {{ cache_job.cash_ledgers|join(', ') }}</div>{% endif %}</div>
|
||||
</div>
|
||||
<div class="mt-3 h-2 overflow-hidden rounded-full bg-sky-100"><div class="h-full bg-sky-600" style="width:{{ cache_job.percent or 0 }}%"></div></div>
|
||||
{% if cache_job.error %}<p class="mt-3 text-sm font-medium text-amber-900">{{ cache_job.error }}</p>{% endif %}
|
||||
@@ -136,7 +147,7 @@
|
||||
<div class="w-full max-w-2xl rounded-2xl bg-white p-6 shadow-2xl">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-slate-900">Slow Tally Extraction to Local SQLite</h3>
|
||||
<h3 class="text-lg font-semibold text-slate-900">Cash Ledger Extraction & Compliance Progress</h3>
|
||||
<p id="cash-progress-text" class="mt-1 text-sm text-slate-600">Submitting a background extraction job to the ERP Local Agent…</p>
|
||||
</div>
|
||||
<div id="cash-progress-percent" class="text-lg font-semibold text-brand-700">0%</div>
|
||||
@@ -146,11 +157,18 @@
|
||||
</div>
|
||||
<div class="mt-4 grid grid-cols-2 gap-3 text-sm md:grid-cols-4">
|
||||
<div class="rounded-xl bg-slate-50 p-3"><div class="text-xs uppercase text-slate-500">Days</div><div id="cash-progress-days" class="mt-1 font-semibold">0 / 0</div></div>
|
||||
<div class="rounded-xl bg-slate-50 p-3"><div class="text-xs uppercase text-slate-500">Vouchers cached</div><div id="cash-progress-vouchers" class="mt-1 font-semibold">0</div></div>
|
||||
<div class="rounded-xl bg-slate-50 p-3"><div class="text-xs uppercase text-slate-500">Cash vouchers</div><div id="cash-progress-vouchers" class="mt-1 font-semibold">0</div></div>
|
||||
<div class="rounded-xl bg-slate-50 p-3"><div class="text-xs uppercase text-slate-500">Tally requests</div><div id="cash-progress-requests" class="mt-1 font-semibold">0</div></div>
|
||||
<div class="rounded-xl bg-slate-50 p-3"><div class="text-xs uppercase text-slate-500">Current date</div><div id="cash-progress-date" class="mt-1 font-semibold">—</div></div>
|
||||
</div>
|
||||
<p class="mt-4 text-xs text-slate-500">Only one day is requested from TallyPrime at a time. The agent waits about 3 seconds after every successful day. If Tally responds slowly, it cools for 20 seconds and retries only once. The tunnel heartbeat stays responsive because progress is read from SQLite, not from Tally.</p>
|
||||
<div class="mt-4 grid gap-2 text-xs md:grid-cols-5">
|
||||
<div id="erp-cash-step-1" class="rounded-lg bg-slate-100 px-3 py-2">1. Connect</div>
|
||||
<div id="erp-cash-step-2" class="rounded-lg bg-slate-100 px-3 py-2">2. Confirm ledger</div>
|
||||
<div id="erp-cash-step-3" class="rounded-lg bg-slate-100 px-3 py-2">3. Extract</div>
|
||||
<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>
|
||||
<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>
|
||||
@@ -178,6 +196,31 @@
|
||||
let activeJobId = '';
|
||||
let activeClientId = '';
|
||||
let activeDateFrom = '';
|
||||
const ledgerButton = document.getElementById('cash-load-ledgers');
|
||||
const ledgerSelect = document.getElementById('cash-ledger-select');
|
||||
const ledgerHelp = document.getElementById('cash-ledger-help');
|
||||
|
||||
async function loadCashLedgers() {
|
||||
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…';
|
||||
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 (ledgerButton) ledgerButton.addEventListener('click', loadCashLedgers);
|
||||
|
||||
function show() {
|
||||
overlay.classList.remove('hidden');
|
||||
@@ -208,6 +251,13 @@
|
||||
vouchers.textContent = String(job.vouchers_cached || 0);
|
||||
requests.textContent = String(job.tally_requests || 0);
|
||||
currentDate.textContent = job.current_date || '—';
|
||||
const stage = String(job.stage || '').toLowerCase();
|
||||
const status = String(job.status || '').toLowerCase();
|
||||
const steps = [1,2,3,4,5].map(n => document.getElementById('erp-cash-step-'+n));
|
||||
steps.forEach(e => { if(e) e.className='rounded-lg bg-slate-100 px-3 py-2'; });
|
||||
function done(n){ if(steps[n-1]) steps[n-1].className='rounded-lg bg-emerald-50 px-3 py-2 font-semibold text-emerald-700'; }
|
||||
function running(n){ if(steps[n-1]) steps[n-1].className='rounded-lg bg-sky-50 px-3 py-2 font-semibold text-sky-700 ring-1 ring-sky-200'; }
|
||||
if (value > 0) running(1); if (value >= 10) {done(1); running(2);} if (value >= 20) {done(2); running(3);} if (stage.includes('analys')) {done(3); running(4);} if (status === 'completed' && stage.includes('analysis complete')) {done(1);done(2);done(3);done(4);done(5);} else if (status === 'completed') {done(1);done(2);done(3);running(4);}
|
||||
if (job.can_cancel) cancelButton.classList.remove('hidden'); else cancelButton.classList.add('hidden');
|
||||
}
|
||||
async function poll(jobId, clientId, dateFrom, tallyGuid, dateTo) {
|
||||
@@ -230,6 +280,7 @@
|
||||
target.searchParams.set('date_from', dateFrom);
|
||||
target.searchParams.set('date_to', dateTo);
|
||||
target.searchParams.set('job_id', jobId);
|
||||
target.searchParams.set('cash_ledger_name', String(document.getElementById('cash-ledger-select').value || ''));
|
||||
target.searchParams.set('analyze', '1');
|
||||
window.location.assign(target.toString());
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user