From afd65ce3025f154e048d4431cd2dd53e2e877075 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Thu, 9 Jul 2026 12:10:08 +0000 Subject: [PATCH] =?UTF-8?q?fix(fno):=20run=20declaration=20calculations=20?= =?UTF-8?q?once=20per=20D=C3=B6vr=20pick?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- taxes_az/public/js/dovr_period.js | 49 +++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/taxes_az/public/js/dovr_period.js b/taxes_az/public/js/dovr_period.js index 362da24..c63af22 100644 --- a/taxes_az/public/js/dovr_period.js +++ b/taxes_az/public/js/dovr_period.js @@ -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() {