fix(fno): run declaration calculations once per Dövr pick

Picking a period fired every recalculation twice. `frm.set_value()` schedules the
field's handlers asynchronously (frappe.model.set_value -> run_serially), so by
the time the first trigger ran, both `ay`/`rüb` and `il` were already set in
frm.doc — and each field's handler then did a full pass. In VAT that also meant
`populate_vat_tables` was called twice per pick.

Five declarations were affected, all of them via Formula_Editor_Script_* which
bind a handler to every field a formula depends on: Declaration of value added
tax, Declaration of mining tax, Road tax 2, Tax return withheld at source of
payment, Single declaration related to salaried and non-salaried work.

Those scripts are generated, so they can't be patched — the fix has to live in
the picker. Instead of two set_value() calls, write the fields directly, do what
set_value would have done (refresh_field, refresh_dependency, dirty) and fire the
handlers exactly once.

Firing only `il` is safe: for every affected declaration the period handler and
the `il` handler have byte-identical bodies once the trigger-field argument is
normalized (VAT 2750 lines each, single declaration 1202, mining tax 484, road
tax 2 128, tax withheld 4 — differing only in a comment), and
calculate_table_row_field() never reads its triggerField argument. A runtime
fallback triggers the period field instead when no `il` handler is bound.

Note this cannot be done with set_value("il", year) alone: frappe.model.set_value
skips both the trigger and dirty() when the value is unchanged, so changing only
the month within the same year would silently recalculate nothing.

Verified headless: one call per pick on both VAT (month) and Single declaration
(quarter), including a second pick within the same year.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-07-09 12:10:08 +00:00
parent fd389d7b37
commit afd65ce302
1 changed files with 40 additions and 9 deletions

View File

@ -102,19 +102,50 @@ frappe.provide("taxes_az.dovr");
return 0;
}
// Seçilmiş tarixi ay/il/rüb + dövr-a yazır.
// Seçilmiş tarixi ay/il/rüb + dövr-a yazır və hesablamaları BİR DƏFƏ işə salır.
//
// Niyə `frm.set_value()` yox:
// set_value hər sahə üçün ayrıca trigger qoyur, triggerlər isə asinxron
// işləyir (frappe.model.set_value -> run_serially). Yəni `ay` və `il`
// dəyərləri hər ikisi frm.doc-a düşəndən sonra HƏR İKİ handler işə düşür və
// bütün hesablama iki dəfə aparılır (VAT-da həm də populate_vat_tables iki
// dəfə çağırılır).
// Üstəlik set_value dəyər dəyişməyibsə heç nə etmir (`doc[key] !== value`),
// ona görə yalnız ayı dəyişəndə (il eyni qalanda) `il`-ə arxalanmaq olmaz —
// nə trigger, nə də dirty olardı.
//
// Ona görə set_value-nun etdiklərini əl ilə təkrarlayırıq və trigger-i bir dəfə
// özümüz çağırırıq. Bu təhlükəsizdir: bütün ФНО-larda period handler-i ilə `il`
// handler-inin gövdəsi eynidir (formula editor hər asılı sahə üçün eyni bloku
// generasiya edir), `calculate_table_row_field`-in triggerField arqumenti isə
// heç yerdə istifadə olunmur.
function apply_selection(frm, mode, date) {
const year = date.getFullYear();
const pf = period_field(mode); // "ay" | "rüb" | null (annual)
let period = null;
if (mode === "month") {
period = MONTHS[date.getMonth()];
frm.set_value("ay", period);
} else if (is_quarter(mode)) {
period = QUARTERS[Math.floor(date.getMonth() / 3)];
frm.set_value(period_field(mode), period);
if (pf) {
period = (mode === "month")
? MONTHS[date.getMonth()]
: QUARTERS[Math.floor(date.getMonth() / 3)];
frm.doc[pf] = period;
}
frm.doc.il = year;
frm.doc["dövr"] = build_label(mode, period, year);
if (pf) frm.refresh_field(pf);
frm.refresh_field("il");
frm.refresh_field("dövr");
frm.layout.refresh_dependency();
frm.dirty();
// Hesablamaları bir dəfə işə salırıq. Adətən `il` handler-i var; yoxdursa
// period sahəsinin handler-inə geri düşürük.
const bound = frappe.ui.form.handlers[frm.doctype] || {};
const on = (bound["il"] || []).length ? "il" : pf;
if (on) {
frm.script_manager.trigger(on, frm.doctype, frm.docname);
}
frm.set_value("il", year);
frm.set_value("dövr", build_label(mode, period, year));
}
function inject_style() {