Fix Tally mirror reload loop and simplify company mapping

This commit is contained in:
A R R R Associates
2026-09-06 19:33:06 +05:30
parent b46a618b13
commit 1fd15d652b
@@ -166,13 +166,30 @@
</section> </section>
{% if accounting and accounting.exists and tally and tally.connected %} {% if accounting and accounting.exists and tally and tally.connected %}
<section class="rounded-2xl bg-white p-5 shadow-soft"> {% set active_mappings = (accounting.mappings or []) | selectattr('is_active') | list %}
<div> {% if active_mappings %}
<h2 class="font-semibold text-slate-900">Map ERP Scope to Tally Company</h2> {% set current_mapping = active_mappings[0] %}
<p class="mt-1 text-sm text-slate-500">The Tally GUID is stored as the permanent identifier. Company name and GSTIN are retained as descriptive snapshots.</p> <details class="rounded-2xl border border-slate-200 bg-white shadow-soft">
</div> <summary class="cursor-pointer list-none px-5 py-4">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h2 class="font-semibold text-slate-900">Tally Company Linked</h2>
<p class="mt-1 text-sm text-slate-500">
{{ selected_client.client_name }} → {{ current_mapping.company_name }}
{% if current_mapping.gstin %} · {{ current_mapping.gstin }}{% endif %}
</p>
</div>
<span class="rounded-full bg-emerald-50 px-3 py-1 text-xs font-semibold text-emerald-700">Mapped by GUID</span>
</div>
<p class="mt-2 text-xs text-slate-500">Mapping is a one-time setup. Open this panel only when you need to change the linked Tally company.</p>
</summary>
<div class="border-t border-slate-100 p-5">
<div>
<h3 class="font-semibold text-slate-900">Change ERP Scope to Tally Company Mapping</h3>
<p class="mt-1 text-sm text-slate-500">The Tally GUID remains the permanent identifier. Company name and GSTIN are descriptive snapshots.</p>
</div>
<form method="post" action="/tools/tally/map-company" class="mt-5 grid gap-4 lg:grid-cols-2"> <form method="post" action="/tools/tally/map-company" class="mt-5 grid gap-4 lg:grid-cols-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" /> <input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="client_id" value="{{ selected_client.id }}" /> <input type="hidden" name="client_id" value="{{ selected_client.id }}" />
@@ -211,12 +228,64 @@
</span> </span>
</label> </label>
<div class="lg:col-span-2">
<button type="submit" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Save Tally Mapping</button>
</div>
</form>
</div>
</details>
{% else %}
<section class="rounded-2xl bg-white p-5 shadow-soft">
<div>
<h2 class="font-semibold text-slate-900">Link Tally Company</h2>
<p class="mt-1 text-sm text-slate-500">One-time setup. The Tally GUID is stored as the permanent identifier; company name and GSTIN are descriptive snapshots.</p>
</div>
<form method="post" action="/tools/tally/map-company" class="mt-5 grid gap-4 lg:grid-cols-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="client_id" value="{{ selected_client.id }}" />
<label class="text-sm font-medium text-slate-700">
ERP Scope
<select name="registration_id" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm">
<option value="">Client level · {{ selected_client.client_name }}</option>
{% for reg, reg_type in registrations %}
<option value="{{ reg.id }}">
{{ reg_type.name }} · {{ reg.registration_number }}
{% if reg.trade_name %} · {{ reg.trade_name }}{% elif reg.legal_name %} · {{ reg.legal_name }}{% endif %}
</option>
{% endfor %}
</select>
<span class="mt-1 block text-xs font-normal text-slate-500">Choose a registration only when the Tally company belongs specifically to that registration; otherwise use client level.</span>
</label>
<label class="text-sm font-medium text-slate-700">
Currently Open in TallyPrime
<select name="tally_guid" required class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm">
<option value="">Select Tally company</option>
{% for company in tally.companies %}
{% if company.guid %}
<option value="{{ company.guid }}">{{ company.name }}{% if company.gstin %} · {{ company.gstin }}{% endif %}</option>
{% endif %}
{% endfor %}
</select>
</label>
<label class="flex items-start gap-2 lg:col-span-2">
<input type="checkbox" name="allow_gstin_mismatch" value="true" class="mt-1 rounded border-slate-300" />
<span class="text-sm text-slate-700">
Allow GSTIN mismatch after manual verification.
<span class="block text-xs text-slate-500">Use only when the ERP registration and the selected Tally company have intentionally different GSTIN snapshots.</span>
</span>
</label>
<div class="lg:col-span-2"> <div class="lg:col-span-2">
<button type="submit" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Save Tally Mapping</button> <button type="submit" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Save Tally Mapping</button>
</div> </div>
</form> </form>
</section> </section>
{% endif %} {% endif %}
{% endif %}
{% if accounting and accounting.exists %} {% if accounting and accounting.exists %}
<section class="rounded-2xl bg-white p-5 shadow-soft" id="full-accounting-export"> <section class="rounded-2xl bg-white p-5 shadow-soft" id="full-accounting-export">
@@ -474,7 +543,7 @@
const clientId = {{ selected_client.id if selected_client else 'null' }}; const clientId = {{ selected_client.id if selected_client else 'null' }};
if (!clientId) return; if (!clientId) return;
const box=document.getElementById('fullExportProgress'); if(!box) return; const box=document.getElementById('fullExportProgress'); if(!box) return;
let completedReloaded=false; let pollTimer=null;
function setText(id,v){const e=document.getElementById(id);if(e)e.textContent=String(v??'');} function setText(id,v){const e=document.getElementById(id);if(e)e.textContent=String(v??'');}
async function pollFullExport(){ async function pollFullExport(){
try{ try{
@@ -486,11 +555,15 @@
const rows=j.rows||((j.mirror||{}).counts)||{}; setText('fullExportLedgers',rows.ledger_master||0); setText('fullExportVouchers',rows.voucher||rows.voucher_header||0); setText('fullExportLines',rows.voucher_ledger_entry||rows.voucher_ledger||0); setText('fullExportInventory',rows.inventory_movement||rows.inventory||0); const rows=j.rows||((j.mirror||{}).counts)||{}; setText('fullExportLedgers',rows.ledger_master||0); setText('fullExportVouchers',rows.voucher||rows.voucher_header||0); setText('fullExportLines',rows.voucher_ledger_entry||rows.voucher_ledger||0); setText('fullExportInventory',rows.inventory_movement||rows.inventory||0);
const paths=[]; if(j.mirror_db_path)paths.push('Mirror: '+j.mirror_db_path); if(j.accounting_db_path)paths.push('Client DB: '+j.accounting_db_path); setText('fullExportPaths',paths.join(' • ')); const paths=[]; if(j.mirror_db_path)paths.push('Mirror: '+j.mirror_db_path); if(j.accounting_db_path)paths.push('Client DB: '+j.accounting_db_path); setText('fullExportPaths',paths.join(' • '));
const er=document.getElementById('fullExportError'); if(j.error){er.textContent=j.error;er.classList.remove('hidden')}else{er.classList.add('hidden')} const er=document.getElementById('fullExportError'); if(j.error){er.textContent=j.error;er.classList.remove('hidden')}else{er.classList.add('hidden')}
const btn=document.getElementById('fullExportButton'); if(btn)btn.disabled=['queued','running'].includes(String(j.status||'').toLowerCase()); const status=String(j.status||'').toLowerCase();
if(String(j.status||'').toLowerCase()==='completed'&&!completedReloaded){completedReloaded=true;setTimeout(()=>location.href='/tools/tally?client_id='+clientId+'&refresh=1',1800)} const btn=document.getElementById('fullExportButton'); if(btn)btn.disabled=['queued','running'].includes(status);
if(['completed','failed','cancelled','paused'].includes(status)){
if(pollTimer){clearInterval(pollTimer);pollTimer=null;}
}
}catch(e){} }catch(e){}
} }
pollFullExport(); setInterval(pollFullExport,1500); pollTimer=setInterval(pollFullExport,1500);
pollFullExport();
})(); })();
</script> </script>
{% endblock %} {% endblock %}