From d370d74e32b0201468f09ffc4305a01031229c46 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Wed, 10 Jun 2026 17:52:03 +0000 Subject: [PATCH] feat(property-tax-gov): rework property_tax_gov client script; add RU architecture doc Co-Authored-By: Claude Opus 4.8 (1M context) --- .../property_tax_gov/ARXITEKTURA_RU.md | 218 ++++++++++++++++++ .../property_tax_gov/property_tax_gov.js | 204 ++++++++++------ 2 files changed, 348 insertions(+), 74 deletions(-) create mode 100644 taxes_az/taxes_az/doctype/property_tax_gov/ARXITEKTURA_RU.md diff --git a/taxes_az/taxes_az/doctype/property_tax_gov/ARXITEKTURA_RU.md b/taxes_az/taxes_az/doctype/property_tax_gov/ARXITEKTURA_RU.md new file mode 100644 index 0000000..25704ce --- /dev/null +++ b/taxes_az/taxes_az/doctype/property_tax_gov/ARXITEKTURA_RU.md @@ -0,0 +1,218 @@ +# Property Tax Gov — полный разбор + +## Назначение + +Это доктайп для формирования **декларации по налогу на имущество** (Əmlak vergisinin +bəyannaməsi) для азербайджанской системы E-Taxes. Он берёт данные об основных средствах +(Asset) из ERPNext, агрегирует их, рассчитывает налог по формулам, извлечённым из самого +кабинета new.e-taxes.gov.az, и экспортирует результат в XML для подачи. + +Это часть пилота **Property Tax Gov** — нового семейства доктайпов с расчётом «по странице, +без авторизации», заменяющего старые `property_tax_return_ai`. + +## Файлы доктайпа + +| Файл | Роль | +|------|------| +| `property_tax_gov.json` | Определение полей: 3 таба, ~8 дочерних таблиц | +| `property_tax_gov.py` | Бэкенд: агрегация Asset → строки таблиц + мост к Node-калькулятору | +| `property_tax_gov.js` | Клиент: автозаполнение, расчёт формул, валидация, XML-экспорт | +| `gns_property_calc.bundle.js` | Извлечённый из кабинета ГНС `useCalculation` (эталон формул) | +| `gns_property_calc_cli.js` | stdin/stdout-обёртка Node для bundle | + +--- + +## 1. Структура формы (JSON) + +Три основных таба: + +1. **Ümumi məlumat** (Общая информация) — шапка декларации: налоговый орган + (`vergiorqanı` — большой Select со всеми территориальными управлениями), год (`il`), + ВОЕН, ФИО/название плательщика, тип плательщика (Hüquqi/Fiziki), вид деклараций и т.д. + +2. **Əmlak vergisinin hesablanması** (Расчёт налога на имущество) — 8 «cədvəl» + (расчётных таблиц): + - `vergi_hesab_1ci` / `vergi_hesab_1ci_1` — остаток на начало года (501) / на дату создания (503) + - `vergi_hesab_2ci` — остаток на конец года (504) + - `vergi_hesab_3cu` — сверхзастрахованные ОС (505) + - `vergi_hesab_4cu` — ставка налога (506), `vergi_hesab_4cu_1` — кол-во месяцев (507) + - `vergi_hesab_5ci` — рассчитанный налог (509/510) + - `vergi_hesab_6ci` — сумма к уплате в бюджет (511) + +3. **Əlavə 1** (`elave_1`) и **Əlavə 2** (`elave_2`) — два приложения, **источники данных**: + - **Əlavə 1** — имущество, **освобождённое** от налога, сгруппированное по статьям НК + (199.1, 199.4.1, 207.3 …). Ключевое поле строки — `azadolmasəbəbi` (основание освобождения). + - **Əlavə 2** — имущество, **подлежащее** налогообложению, по категориям (Binalar, + Mənzillər, Nəqliyyat vasitələri …). Ключевое поле — `vergiyəcəlbolunanəmlaklarınkateqoriyası`. + +### Ключевая развилка: два режима + +Два взаимоисключающих чекбокса в шапке управляют всей логикой: + +- **`tam_il`** (Tam İl) — компания работала **полный год**. +- **`il_erzinde`** (İl Ərzində) — компания **создана в течение года** (неполный период). + +Клиентский код (`setup_checkbox_exclusivity`, обработчики `tam_il`/`il_erzinde`) гарантирует, +что ровно один из них всегда включён. + +--- + +## 2. Автозаполнение из Asset (Python-бэкенд) + +Когда выбран год и режим, JS вызывает `reload_property_tax_data()` → серверный +`populate_property_tax_tables()`. + +**Поток данных:** + +``` +tabAsset (docstatus=1, company, purchase_date<=to_date) + │ + ├─ taxable_asset_type заполнен ──→ get_taxable_assets_aggregated() ──→ Əlavə 2 + └─ industrial/tax_exempt_tax_article ─→ get_tax_exempt_assets_aggregated() ─→ Əlavə 1 +``` + +Сердце расчёта — `get_asset_balances_using_standard_logic()`. Это **точная репликация +стандартного отчёта ERPNext «Asset Depreciations and Balances»**: + +- `get_asset_cost_details` — валовая стоимость (на начало периода, новые покупки, + проданные/списанные/капитализированные); +- `get_asset_depreciation_details` — накопленная амортизация из GL Entry + (через `tabAsset Category Account`); +- `get_asset_value_adjustments` — переоценки. + +Из них считается `net_asset_value_as_on_from_date/to_date` (остаточная стоимость). +Плюс `insurance_value` = (застрахованная сумма − валовая стоимость), если объект +застрахован выше стоимости. + +**Версионная совместимость:** код проверяет колонки таблицы Asset и сам выбирает +`gross_purchase_amount` / `net_purchase_amount` / `purchase_amount`, потому что ERPNext v15 +переименовал поле (`gross_purchase_amount` → `net_purchase_amount`). + +**Сопоставление со строками таблиц** — нечёткое (fuzzy): +- `extract_article_number()` достаёт «199.1» из «Vergi Məcəlləsinin 199.1-ci maddəsinə əsasən» + регуляркой; +- `find_matching_azadolma_sebeb()` ищет строку Əlavə 1, чей `azadolmasəbəbi` содержит этот номер; +- `find_matching_category()` сопоставляет `taxable_asset_type` со строкой Əlavə 2 по частичному + вхождению («Mənzillər» ↔ «1.5 Mənzillər»). + +**Бизнес-правило для İl Ərzində:** если у компании, созданной в течение года, нет новых покупок +(`value_of_new_purchase < 0.01`), все поля строки обнуляются. Также в зависимости от режима +данные кладутся в разные колонки: в `tam_il` — в «остаток на начало года» (501), +в `il_erzinde` — в «принято на учёт в течение года» (503). + +Сервер возвращает не строки, а **карты `{ключ_строки: {поле: значение}}`**, а уже клиент +применяет их к существующим строкам через `update_child_table_values()`. + +--- + +## 3. Расчётный движок (клиент) + +Здесь самое тонкое место. Файл содержит **два поколения кода расчёта**, и работает только второе. + +### Старый код (строки 269–919) +Ручные функции: `calculate_509_1`, `calculate_509_2`, `calculate_binalar_totals`, +`calculate_elave_*_totals`, `calculate_vergi_hesab_3cu_totals`. Они напрямую считали формулы: +- `509.1 = ((501.1 + 504.1) / 2) * 1% + 505.1 * 1%` (полный год); +- `509.2 = ((503.1 + 504.1) / 24 * 507) * 1% + 505.1 * 1%` (неполный год, 507 = число месяцев). + +### Новый код (строки 1042–1264) — **активный** + +В конце файла все шесть `calculate_*` **переопределены заново** как тонкие делегаторы: + +```js +function calculate_509_1(frm) { gns_property_recalc(frm); } +// ... и т.д. +``` + +Это работает за счёт **hoisting функций в JS: последнее объявление побеждает**. То есть все +старые триггеры (обработчики дочерних таблиц на строках 924–1040) теперь незаметно вызывают +новый единый движок `gns_property_recalc()`. Старые тела функций мертвы, но оставлены. + +**Как считает новый движок** (`_gns_property_recalc_now`): + +1. `ptgov_sum()` суммирует строки `elave_1` → объект `a1` (приложение 1, освобождённые), + `elave_2` → `a2` (облагаемые). При суммировании Əlavə 2 исключаются «итоговые» строки — + родитель «1. Binalar» и «CƏMİ» (`ptgov_is_excluded_e2`). +2. `full = !il_erzinde`, `months` берётся из строки 507. +3. `ptgov_calc_byArticle()` строит карту `{номер_статьи: значение}` — те же формулы, что и в + старом коде, но в одном месте, с округлением half-up до 2 знаков (`ptgov_round2`). Облагается + **только** Əlavə 2; 511 (к уплате) = 509. +4. Результаты разносятся по всем выходным cədvəl-таблицам: по номеру статьи (`ptgov_article` + парсит «509.1» из метки `göstəricilər`) в поле `manatla`; ставка 506 → `faizlə = 1`. + +**Защита от рекурсии:** флаг `frm.__ptgov_writing` ставится перед записью значений, чтобы наши же +`set_value` не зациклили обработчики `manatla`. Плюс debounce 250 мс (`_ptgov_timer`). + +### Про Node-мост (`compute_via_gns` + bundle) +По задумке предполагалось гонять **родной калькулятор кабинета** (`useCalculation`, извлечённый +в `gns_property_calc.bundle.js`) офлайн через Node-subprocess. Сейчас же, как явно сказано в +комментарии (строки 1103–1107), **формулы переписаны нативно в JS**, а bundle и +`compute_via_gns` остаются в репо как **эталонный источник**, из которого формулы выведены и +сверены. То есть в рантайме Node не вызывается — всё считается в браузере. + +--- + +## 4. Условная логика отображения + +`apply_conditional_table_logic()` показывает/прячет таблицы под режим: +- `tam_il` → видна `vergi_hesab_1ci` (501); строки 509.1/511.1 в 5-й/6-й таблицах; +- `il_erzinde` → видны `vergi_hesab_1ci_1` (503) и `vergi_hesab_4cu_1` (месяцы); строки + 509.2/511.2; колонки 501.x скрываются (`toggle_field_readonly`). + +Строки 5-й/6-й таблиц **полностью пересоздаются** под режим (`filter_vergi_hesab_5ci_rows`), +при этом старые значения сохраняются в `window.property_tax_saved_values` и восстанавливаются. + +`calculate_months_for_vergi_hesab_4cu_1()` (Python) находит самый ранний Asset по +`available_for_use_date` в году и считает число месяцев до конца года для формулы 509.2. + +--- + +## 5. Валидация + +`validate_minimum_asset_value()` — только в режиме `il_erzinde`: стоимость ОС в колонках +503.1/503.2 не может быть **< 500 манат** (но > 0). При нарушении: +- блокирует сохранение (`frm.validated = false`); +- подсвечивает ячейки красным (`highlight_invalid_cells`); +- показывает HTML-предупреждение и ставит 🔴 в заголовок таба (`update_tab_title`). + +--- + +## 6. Шаблонные строки и кнопки + +- `ptgov_ensure_rows()` — при `refresh` заполняет пустые дочерние таблицы каноническими наборами + строк (`PTGOV_E1_ROWS` — 23 статьи освобождения, `PTGOV_E2_ROWS` — 12 категорий, cədvəl-таблицы + — из `options` поля `göstəricilər`). +- `ptgov_set_readonly()` — делает вычисляемые колонки (`manatla`, `faizlə`) read-only. +- Кнопки: **«Şablon sətirləri yarat»** (создать шаблонные строки), **«ГНС ilə hesabla»** + (пересчитать вручную), **«Export XML»** (через `xml_mapping_tool.generate_xml`). + +--- + +## Сводный поток работы + +``` +Пользователь: выбирает год + режим (Tam İl / İl Ərzində) + ↓ +JS reload_property_tax_data → Python populate_property_tax_tables + ↓ +tabAsset → агрегация (ERPNext balance-логика) → карты {строка: значения} + ↓ +JS раскладывает по Əlavə 1 / Əlavə 2 + ↓ +любое изменение строки → gns_property_recalc (debounce 250ms) + ↓ +ptgov_sum → ptgov_calc_byArticle → разнос по cədvəl (501→511) + ↓ +валидация (min 500 ман.) → Export XML → подача в E-Taxes +``` + +--- + +## Главное при правках этого файла + +1. Активен только **второй** блок расчёта (делегаторы внизу). Старые `calculate_509_*` — + мёртвый код, переопределён hoisting'ом. Правь `ptgov_calc_byArticle`, а не их. +2. Сопоставление Asset ↔ строки таблиц — **нечёткое по подстроке**, легко ломается при + изменении меток строк. +3. Поля имеют азербайджанские имена с диакритикой (`göstəricilər`, `manatla`) — копируй точно. +4. Node-мост (`compute_via_gns`) сейчас не в рантайме, а как эталон формул. diff --git a/taxes_az/taxes_az/doctype/property_tax_gov/property_tax_gov.js b/taxes_az/taxes_az/doctype/property_tax_gov/property_tax_gov.js index 0824734..c6c0b8b 100644 --- a/taxes_az/taxes_az/doctype/property_tax_gov/property_tax_gov.js +++ b/taxes_az/taxes_az/doctype/property_tax_gov/property_tax_gov.js @@ -1097,64 +1097,76 @@ function gns_property_recalc(frm) { _ptgov_timer = setTimeout(function () { _gns_property_recalc_now(frm); }, 250); } +// half-up rounding to 2 decimals (matches the cabinet's decimal rounding) +function ptgov_round2(x) { return Math.round((flt(x) + Number.EPSILON) * 100) / 100; } + +// LOCAL implementation of the property-tax formulas. The formulas/logic were extracted & +// verified from the GNS cabinet (new.e-taxes.gov.az) — see .etaxes_notes/scratch/calc_extract. +// Only the +,-,*,/ run here (locally, offline). The cabinet is the SOURCE of the formulas, +// not a runtime dependency; compute_via_gns + the extracted bundle stay in the repo as the +// reference from which these were derived. +// a1 = appendix1 (taxFree) totals, a2 = appendix2 (taxable) totals, full = Tam İl +function ptgov_calc_byArticle(a1, a2, full, months) { + const RATE = 0.01; // 506 taxDegree = 1% for legal entities + const by = {}; + if (full) { + // 501 prePeriod (beginning of year): total = taxable + tax-exempt + by['501.1'] = a2.periodStart; // taxable residual + by['501.2'] = a1.periodStart; // tax-exempt residual + by['501'] = a2.periodStart + a1.periodStart; + } else { + // 503 createDate (newly established during the year) + by['503.1'] = a2.onCreate; + by['503.2'] = a1.created; + by['503'] = a2.onCreate + a1.created; + } + // 504 endYear + by['504.1'] = a2.periodEnd; + by['504.2'] = a1.endYear; + by['504'] = a2.periodEnd + a1.endYear; + // 505 excess-insured + by['505.1'] = a2.excess; + by['505.2'] = a1.over; + by['505'] = a2.excess + a1.over; + // 509 calculated tax — only taxable (appendix2) is taxed; 511 shouldBePaid mirrors it + let tax; + if (full) { + tax = ptgov_round2((a2.periodStart + a2.periodEnd) / 2 * RATE + a2.excess * RATE); + by['509.1'] = tax; by['511.1'] = tax; + } else { + tax = ptgov_round2((a2.onCreate + a2.periodEnd) / 24 * flt(months) * RATE + a2.excess * RATE); + by['509.2'] = tax; by['511.2'] = tax; + } + return by; +} + function _gns_property_recalc_now(frm) { - const e1 = ptgov_sum(frm.doc.elave_1, PTGOV_E1); - const e2 = ptgov_sum(frm.doc.elave_2, PTGOV_E2, ptgov_is_excluded_e2); - - const activity = frm.doc.il_erzinde ? 'OBJECT_ESTABLISHED' : 'OBJECT_FULL_ACTIVITY'; - - // months (507): read from the 4cu cedvel 1 (ayla) row, used only for OBJECT_ESTABLISHED - let months = null; + const a1 = ptgov_sum(frm.doc.elave_1, PTGOV_E1); + const a2 = ptgov_sum(frm.doc.elave_2, PTGOV_E2, ptgov_is_excluded_e2); + const full = !frm.doc.il_erzinde; + let months = 0; (frm.doc.vergi_hesab_4cu_1 || []).forEach(function (r) { if (ptgov_article(r.göstəricilər) === '507') months = flt(r.ayla); }); + const byArt = ptgov_calc_byArticle(a1, a2, full, months); - const payload = { - objectActivity: activity, - calculation: { - monthCountFromStartPeriod: activity === 'OBJECT_ESTABLISHED' ? months : null, - monthCountToLiquidation: null, - }, - appendix1: { - periodStartTotal: e1.periodStart, createdTotal: e1.created, - removedTotal: e1.removed, endYearTotal: e1.endYear, overTotal: e1.over, - }, - appendix2: { - periodStartTotal: e2.periodStart, onCreateDateTotal: e2.onCreate, - onCancelDateTotal: e2.onCancel, periodEndTotal: e2.periodEnd, - excessInsuredTotal: e2.excess, - }, - }; - - frappe.call({ - method: 'taxes_az.taxes_az.doctype.property_tax_gov.property_tax_gov.compute_via_gns', - args: { payload: JSON.stringify(payload) }, - callback: function (r) { - if (!r.message || !r.message.byArticle) return; - const byArt = r.message.byArticle; - frm.__ptgov_writing = true; // guard: our set_value writes retrigger manatla handlers - try { - // write computed values into the calculation cedvel rows (manatla) by article number - PTGOV_OUT_TABLES.forEach(function (tbl) { - (frm.doc[tbl] || []).forEach(function (row) { - const art = ptgov_article(row.göstəricilər); - if (art != null && byArt[art] != null) { - frappe.model.set_value(row.doctype, row.name, 'manatla', flt(byArt[art])); - } - }); - }); - // tax rate (506) is the constant 1% for legal entities - (frm.doc.vergi_hesab_4cu || []).forEach(function (row) { - if (ptgov_article(row.göstəricilər) === '506') { - frappe.model.set_value(row.doctype, row.name, 'faizlə', 1); - } - }); - } finally { - PTGOV_OUT_TABLES.concat(['vergi_hesab_4cu']).forEach(function (t) { frm.refresh_field(t); }); - setTimeout(function () { frm.__ptgov_writing = false; }, 0); // release after sync triggers flush + frm.__ptgov_writing = true; // guard: our set_value writes retrigger manatla handlers + try { + PTGOV_OUT_TABLES.forEach(function (tbl) { + (frm.doc[tbl] || []).forEach(function (row) { + const art = ptgov_article(row.göstəricilər); + frappe.model.set_value(row.doctype, row.name, 'manatla', flt(byArt[art] || 0)); + }); + }); + (frm.doc.vergi_hesab_4cu || []).forEach(function (row) { + if (ptgov_article(row.göstəricilər) === '506') { + frappe.model.set_value(row.doctype, row.name, 'faizlə', 1); } - }, - }); + }); + } finally { + PTGOV_OUT_TABLES.concat(['vergi_hesab_4cu']).forEach(function (t) { frm.refresh_field(t); }); + setTimeout(function () { frm.__ptgov_writing = false; }, 0); // release after sync triggers flush + } } // ---- delegators: override the old hand-coded calc (hoisting: these win) ---- @@ -1165,41 +1177,85 @@ function calculate_vergi_hesab_3cu_totals(frm) { gns_property_recalc(frm); } function calculate_509_1(frm) { gns_property_recalc(frm); } function calculate_509_2(frm) { gns_property_recalc(frm); } -// ---- template rows: create one row per Select option for empty tables ---- -// (the form shipped without default rows for elave_1/2 and most cedvel tables; -// autofill matches asset data to these rows and the calc writes results into them) -const PTGOV_ROW_TEMPLATES = [ - ['elave_1', 'azadolmasəbəbi'], - ['elave_2', 'vergiyəcəlbolunanəmlaklarınkateqoriyası'], - ['vergi_hesab_1ci', 'göstəricilər'], - ['vergi_hesab_1ci_1', 'göstəricilər'], - ['vergi_hesab_2ci', 'göstəricilər'], - ['vergi_hesab_3cu', 'göstəricilər'], - ['vergi_hesab_4cu', 'göstəricilər'], - ['vergi_hesab_4cu_1', 'göstəricilər'], - ['vergi_hesab_5ci', 'göstəricilər'], - ['vergi_hesab_6ci', 'göstəricilər'], +// ---- template rows: identical to the original doctype's Default Client Scripts ---- +// elave_1/elave_2 use the exact curated row sets; cedvel tables use their göstərici options. +const PTGOV_E1_ROWS = [ + "Vergi Məcəlləsinin 199.1-ci maddəsinə əsasən azaldılmalı əmlak vergisinin məbləği", + "Vergi Məcəlləsinin 199.4.1-ci maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.4.2-ci maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.4.3-cü maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.4.4-cü maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.4.1-1-ci maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.5-ci maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.7-ci maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.8-ci maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.9-cu maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.11-cu maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.14-cu maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.15-cu maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.16-cı maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.17-ci maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.18-ci maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.19-cu maddəsinə əsasən:", + "Vergi Məcəlləsinin 199.20-ci maddəsinə əsasən:", + "Vergi Məcəlləsinin 227.1-ci maddəsinə əsasən:", + "Qanunla təsdiq olunmuş hasilatın pay bölgüsü haqqında, əsas boru kəməri haqqında və digər bu qəbildən olan sazişlərdə və ya qanunlarda, o cümlədən neft və qaz haqqında, ixrac məqsədli neft-qaz fəaliyyəti üzrə:", + "Xüsusi iqtisadi zonalar haqqında qanunlarda nəzərdə tutulan əmlaklar üzrə:", + "Azərbaycan Respublikasının tərəfdar çıxdığı beynəlxalq müqavilələr üzrə:", + "Vergidən azad olunan əmlakın CƏMİ dəyəri:", ]; +const PTGOV_E2_ROWS = [ + "1. Binalar, tikililər və qurğular", + "1.1 Çoxmərtəbəli (çoxmənzilli) yaşayış binaları", + "1.2 Qeyri-yaşayış binaları (sahələri)", + "1.3 Əmlak kompleksi kimi müəssisələr", + "1.4 Qurğular", + "1.5 Mənzillər", + "1.6 Fərdi yaşayış və bağ evləri", + "2 Maşınlar və avadanlıqlar", + "3 Yüksək texnologiyalar məhsulu olan hesablama texnikası", + "4 Nəqliyyat vasitələri", + "5 Digər əsas vəsaitlər", + "Vergiyə cəlb olunan əmlakın CƏMİ dəyəri:", +]; +const PTGOV_CEDVEL_TABLES = ['vergi_hesab_1ci', 'vergi_hesab_1ci_1', 'vergi_hesab_2ci', 'vergi_hesab_3cu', + 'vergi_hesab_4cu', 'vergi_hesab_4cu_1', 'vergi_hesab_5ci', 'vergi_hesab_6ci']; + function ptgov_ensure_rows(frm) { let added = false; - PTGOV_ROW_TEMPLATES.forEach(function (pair) { - const tbl = pair[0], fld = pair[1]; - if ((frm.doc[tbl] || []).length) return; // already has rows -> skip + function fill(tbl, fld, vals) { + if ((frm.doc[tbl] || []).length) return; // already has rows -> skip + vals.forEach(function (v) { frm.add_child(tbl)[fld] = v; }); + if (vals.length) { frm.refresh_field(tbl); added = true; } + } + fill('elave_1', 'azadolmasəbəbi', PTGOV_E1_ROWS); + fill('elave_2', 'vergiyəcəlbolunanəmlaklarınkateqoriyası', PTGOV_E2_ROWS); + PTGOV_CEDVEL_TABLES.forEach(function (tbl) { const field = frm.get_field(tbl); if (!field) return; - const childDt = field.df.options; - const df = frappe.meta.get_docfield(childDt, fld); + const df = frappe.meta.get_docfield(field.df.options, 'göstəricilər'); const opts = ((df && df.options) || '').split('\n').filter(function (x) { return x.trim(); }); - opts.forEach(function (opt) { frm.add_child(tbl)[fld] = opt; }); - if (opts.length) { frm.refresh_field(tbl); added = true; } + fill(tbl, 'göstəricilər', opts); }); return added; } +// make computed output columns read-only (the cedvel manatla/faizlə are calc results) +function ptgov_set_readonly(frm) { + const cols = [['vergi_hesab_1ci', 'manatla'], ['vergi_hesab_1ci_1', 'manatla'], ['vergi_hesab_2ci', 'manatla'], + ['vergi_hesab_3cu', 'manatla'], ['vergi_hesab_5ci', 'manatla'], ['vergi_hesab_6ci', 'manatla'], + ['vergi_hesab_4cu', 'faizlə']]; + cols.forEach(function (pair) { + const f = frm.get_field(pair[0]); + if (f && f.grid) f.grid.update_docfield_property(pair[1], 'read_only', 1); + }); +} + // manual recalc button (autofill runs automatically on İl / Tam İl / İl Ərzində change) frappe.ui.form.on('Property Tax Gov', { refresh: function (frm) { - ptgov_ensure_rows(frm); // populate empty child tables with their template rows + ptgov_ensure_rows(frm); // populate empty child tables with their template rows + ptgov_set_readonly(frm); // lock computed cedvel columns frm.add_custom_button('Şablon sətirləri yarat', function () { if (ptgov_ensure_rows(frm)) frappe.show_alert({ message: 'Şablon sətirləri yaradıldı', indicator: 'green' }); });