Add client financial year Accounting Mirror registry
This commit is contained in:
@@ -21,14 +21,14 @@
|
||||
</select>
|
||||
</label>
|
||||
<label class="space-y-1 text-sm"><span class="font-semibold text-slate-700">Analysis FY</span>
|
||||
<select name="financial_year" data-creditor-analysis-fy class="w-full rounded-xl border border-slate-300 px-3 py-2">
|
||||
<select id="creditorAnalysisFY" name="financial_year" class="w-full rounded-xl border border-slate-300 px-3 py-2">
|
||||
{% for fy in fy_options %}<option value="{{ fy }}" {% if fy == selected_fy %}selected{% endif %}>{{ fy }}</option>{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label class="space-y-1 text-sm"><span class="font-semibold text-slate-700">Follow-up FY (optional)</span>
|
||||
<select name="follow_up_fy" data-creditor-follow-up-fy class="w-full rounded-xl border border-slate-300 px-3 py-2">
|
||||
<select id="creditorFollowUpFY" name="follow_up_fy" class="w-full rounded-xl border border-slate-300 px-3 py-2">
|
||||
<option value="">Do not trace later payments</option>
|
||||
{% for fy in fy_options %}{% if fy > selected_fy %}<option value="{{ fy }}" {% if fy == follow_up_fy %}selected{% endif %}>{{ fy }}</option>{% endif %}{% endfor %}
|
||||
{% for fy in follow_up_options %}<option value="{{ fy }}" {% if fy == follow_up_fy %}selected{% endif %}>{{ fy }}</option>{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<div class="flex items-end"><button class="w-full rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Analyse Creditors</button></div>
|
||||
@@ -141,69 +141,30 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const analysis=document.getElementById('creditorAnalysisFY');
|
||||
const follow=document.getElementById('creditorFollowUpFY');
|
||||
const registered={{ (fy_options or [])|tojson }};
|
||||
if(!analysis||!follow)return;
|
||||
function startYear(fy){const m=/^(\d{4})-\d{2}$/.exec(fy||'');return m?parseInt(m[1],10):-1;}
|
||||
function rebuild(){
|
||||
const chosen=follow.value;
|
||||
const base=startYear(analysis.value);
|
||||
follow.innerHTML='';
|
||||
const empty=document.createElement('option'); empty.value=''; empty.textContent='Do not trace later payments'; follow.appendChild(empty);
|
||||
registered.slice().sort((a,b)=>startYear(a)-startYear(b)).forEach(function(fy){
|
||||
if(startYear(fy)<=base)return;
|
||||
const opt=document.createElement('option'); opt.value=fy; opt.textContent=fy; if(fy===chosen)opt.selected=true; follow.appendChild(opt);
|
||||
});
|
||||
}
|
||||
analysis.addEventListener('change',rebuild);
|
||||
rebuild();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
// Keep Follow-up FY synchronized with the Analysis FY on the client side.
|
||||
// Previously the server rendered follow-up options only for the FY that was
|
||||
// active when the page loaded. If the user changed Analysis FY in the form
|
||||
// (for example 2026-27 -> 2021-22), the Follow-up FY control still showed
|
||||
// 2027-28 until the page was submitted. Rebuild it immediately from the FY
|
||||
// values already supplied by the server, without changing any accounting or
|
||||
// FIFO calculation logic.
|
||||
var analysisFySelect = document.querySelector('[data-creditor-analysis-fy]');
|
||||
var followUpFySelect = document.querySelector('[data-creditor-follow-up-fy]');
|
||||
|
||||
function fyStartYear(value) {
|
||||
var match = String(value || '').match(/^(\d{4})-\d{2}$/);
|
||||
return match ? parseInt(match[1], 10) : null;
|
||||
}
|
||||
|
||||
if (analysisFySelect && followUpFySelect) {
|
||||
var fyUniverse = Array.from(analysisFySelect.options)
|
||||
.map(function (option) { return String(option.value || '').trim(); })
|
||||
.filter(function (value) { return fyStartYear(value) !== null; });
|
||||
|
||||
function rebuildFollowUpYears() {
|
||||
var selectedFy = String(analysisFySelect.value || '').trim();
|
||||
var selectedStart = fyStartYear(selectedFy);
|
||||
var previousFollowUp = String(followUpFySelect.value || '').trim();
|
||||
|
||||
while (followUpFySelect.options.length) {
|
||||
followUpFySelect.remove(0);
|
||||
}
|
||||
|
||||
var emptyOption = document.createElement('option');
|
||||
emptyOption.value = '';
|
||||
emptyOption.textContent = 'Do not trace later payments';
|
||||
followUpFySelect.appendChild(emptyOption);
|
||||
|
||||
if (selectedStart === null) return;
|
||||
|
||||
var laterYears = fyUniverse
|
||||
.filter(function (fy) {
|
||||
var start = fyStartYear(fy);
|
||||
return start !== null && start > selectedStart;
|
||||
})
|
||||
.sort(function (a, b) { return fyStartYear(a) - fyStartYear(b); });
|
||||
|
||||
laterYears.forEach(function (fy) {
|
||||
var option = document.createElement('option');
|
||||
option.value = fy;
|
||||
option.textContent = fy;
|
||||
followUpFySelect.appendChild(option);
|
||||
});
|
||||
|
||||
if (laterYears.indexOf(previousFollowUp) !== -1) {
|
||||
followUpFySelect.value = previousFollowUp;
|
||||
} else {
|
||||
followUpFySelect.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
analysisFySelect.addEventListener('change', rebuildFollowUpYears);
|
||||
rebuildFollowUpYears();
|
||||
}
|
||||
|
||||
function applyBucket(detailRow, bucket) {
|
||||
if (!detailRow) return;
|
||||
detailRow.querySelectorAll('[data-creditor-lot]').forEach(function (lot) {
|
||||
|
||||
@@ -290,9 +290,17 @@
|
||||
<span class="rounded-full bg-emerald-50 px-3 py-1 text-xs font-semibold text-emerald-700">Read-only export</span>
|
||||
</div>
|
||||
<div class="mt-4 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900"><strong>Before export:</strong> open the mapped company in TallyPrime and set Tally's active period (Alt+F2) to cover the full period you want exported. The ODBC mirror reads Tally's active period; the ERP does not change Tally's period automatically.</div>
|
||||
<div id="mirrorRegistryStatus" class="mt-3 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-700">
|
||||
{% if selected_mirror_registration %}
|
||||
<strong>Mirror registered:</strong> FY {{ selected_mirror_registration.financial_year }}{% if selected_mirror_registration.company_name %} · {{ selected_mirror_registration.company_name }}{% endif %}{% if selected_mirror_registration.last_synced_at_utc %} · last synced {{ selected_mirror_registration.last_synced_at_utc.strftime('%d-%m-%Y %H:%M') }}{% endif %}. Creating this FY again will ask for replacement confirmation.
|
||||
{% else %}
|
||||
<strong>No registered mirror for FY {{ selected_financial_year }}.</strong> Creating the mirror will register it to this client and financial year for Accounting/Audit tools.
|
||||
{% endif %}
|
||||
</div>
|
||||
<form method="post" action="/tools/tally/full-export" class="mt-5 grid gap-4 lg:grid-cols-5 lg:items-end" id="mirrorExportForm">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<input type="hidden" name="client_id" value="{{ selected_client.id }}" />
|
||||
<input type="hidden" name="replace_existing" id="mirrorReplaceExisting" value="" />
|
||||
<label class="text-sm font-medium text-slate-700">Target Financial Year
|
||||
<select id="mirrorFinancialYear" name="financial_year" required class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm">
|
||||
{% for fy in mirror_fy_options %}
|
||||
@@ -553,7 +561,7 @@
|
||||
function setText(id,v){const e=document.getElementById(id);if(e)e.textContent=String(v??'');}
|
||||
async function pollFullExport(){
|
||||
try{
|
||||
const r=await fetch('/tools/tally/full-export/status?client_id='+encodeURIComponent(clientId),{cache:'no-store'});
|
||||
const fyEl=document.getElementById('mirrorFinancialYear'); const fyValue=fyEl?fyEl.value:''; const r=await fetch('/tools/tally/full-export/status?client_id='+encodeURIComponent(clientId)+'&financial_year='+encodeURIComponent(fyValue),{cache:'no-store'});
|
||||
const d=await r.json(); if(!r.ok||!d.ok||!d.job)return;
|
||||
const j=d.job||{}, p=Math.max(0,Math.min(100,Number(j.percent)||0)); box.classList.remove('hidden');
|
||||
setText('fullExportStage',j.stage||j.status||'Working…'); setText('fullExportMessage',j.message||''); setText('fullExportPct',Math.round(p)+'%');
|
||||
@@ -609,6 +617,49 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
applyFY(fy.value);
|
||||
}
|
||||
});
|
||||
|
||||
const mirrorForm=document.getElementById("mirrorExportForm");
|
||||
const replaceField=document.getElementById("mirrorReplaceExisting");
|
||||
const registryStatus=document.getElementById("mirrorRegistryStatus");
|
||||
let confirmingMirror=false;
|
||||
async function refreshMirrorRegistrationStatus(){
|
||||
if(!fy||!clientId)return;
|
||||
try{
|
||||
const r=await fetch('/tools/tally/mirror-registration/status?client_id='+encodeURIComponent(clientId)+'&financial_year='+encodeURIComponent(fy.value),{cache:'no-store'});
|
||||
const d=await r.json();
|
||||
if(!r.ok||!d.ok)return;
|
||||
if(registryStatus){
|
||||
registryStatus.innerHTML=d.exists
|
||||
? '<strong>Mirror registered:</strong> FY '+fy.value+(d.company_name?' · '+d.company_name:'')+(d.last_synced_at?' · last synced '+new Date(d.last_synced_at).toLocaleString():'')+'. Creating this FY again will ask for replacement confirmation.'
|
||||
: '<strong>No registered mirror for FY '+fy.value+'.</strong> Creating the mirror will register it to this client and financial year for Accounting/Audit tools.';
|
||||
}
|
||||
}catch(e){}
|
||||
}
|
||||
if(fy)fy.addEventListener("change",refreshMirrorRegistrationStatus);
|
||||
refreshMirrorRegistrationStatus();
|
||||
|
||||
if(mirrorForm){
|
||||
mirrorForm.addEventListener("submit",async function(event){
|
||||
if(confirmingMirror || (replaceField&&replaceField.value==='1'))return;
|
||||
event.preventDefault();
|
||||
try{
|
||||
const r=await fetch('/tools/tally/mirror-registration/status?client_id='+encodeURIComponent(clientId)+'&financial_year='+encodeURIComponent(fy.value),{cache:'no-store'});
|
||||
const d=await r.json();
|
||||
if(!r.ok||!d.ok){ mirrorForm.submit(); return; }
|
||||
if(d.exists){
|
||||
const label=(d.client_name||'this client')+' - FY '+fy.value;
|
||||
const ok=window.confirm('An Accounting Mirror already exists for '+label+'.\n\nReplace the existing mirror?\n\nThe current mirror will remain active if the new export fails.');
|
||||
if(!ok)return;
|
||||
if(replaceField)replaceField.value='1';
|
||||
}
|
||||
confirmingMirror=true;
|
||||
mirrorForm.submit();
|
||||
}catch(e){
|
||||
confirmingMirror=true;
|
||||
mirrorForm.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user