From 4ed716637db907d9832310457bf799268c409eb5 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Fri, 24 Apr 2026 14:09:35 +0000 Subject: [PATCH] Fix silent bank materialize, set Stock Settings too, editable abbr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bank materialize was finding no parent account and skipping everything: the AZ CoA keeps "Bank hesablaşma hesabları" (223) as a LEAF and the 223.1/223.2/... currency placeholders as siblings under the real group "Pul vəsaitləri və onların ekvivalentləri" (22). The finder now walks 223 → 22 → name-prefix and lands on 22, which is where real bank GL accounts need to go. Placeholder sweep widened from "% AZXX%" to any leaf under the bank parent whose name contains XXXX — catches the "Kart XXXX…AZN" template too. Valuation was only being written to Company.valuation_method, which wins in erpnext.stock.utils.get_valuation_method but is invisible on the Stock Settings page users actually open to verify. Now we write both via frappe.db.set_single_value. Company abbreviation is editable on the company step with a live- suggested default. Deriver strips every quote variant and collapses the AZ legal-form boilerplate (MƏHDUD MƏSULİYYƏTLİ CƏMİYYƏTİ → MMC, AÇIQ/QAPALI SƏHMDAR CƏMİYYƏTİ → ASC/QSC, SƏHMDAR CƏMİYYƏTİ → SC, KOOPERATİV → K) before tokenising, so "XYZ MMC" produces XM instead of the old deriver's M-with-a-stray-quote. Manual edits stick. Validated A-Z0-9, 1–10 chars. --- jey_wizard/__init__.py | 2 +- jey_wizard/etaxes.py | 68 ++++++++++++++++++---------- jey_wizard/public/js/jey_setup.js | 74 ++++++++++++++++++++++++++++--- jey_wizard/translations/az.csv | 4 ++ jey_wizard/translations/ru.csv | 4 ++ 5 files changed, 120 insertions(+), 32 deletions(-) diff --git a/jey_wizard/__init__.py b/jey_wizard/__init__.py index bbab024..1276d02 100644 --- a/jey_wizard/__init__.py +++ b/jey_wizard/__init__.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" diff --git a/jey_wizard/etaxes.py b/jey_wizard/etaxes.py index dd21982..fe280af 100644 --- a/jey_wizard/etaxes.py +++ b/jey_wizard/etaxes.py @@ -168,13 +168,22 @@ def materialize_after_setup(args): ) return - # Apply inventory valuation method before any stock loaders touch the Company. - # Company-level wins over Stock Settings in erpnext.stock.utils.get_valuation_method. + # Apply inventory valuation method. Company.valuation_method wins over Stock + # Settings in erpnext.stock.utils.get_valuation_method, but users typically + # inspect the Stock Settings page to verify setup, so we set both to avoid the + # "I picked Moving Average but Stock Settings still shows FIFO" surprise. chosen_vm = (args or {}).get("valuation_method") or "FIFO" if chosen_vm in ("FIFO", "Moving Average"): frappe.db.set_value( "Company", company_name, "valuation_method", chosen_vm, update_modified=False ) + try: + frappe.db.set_single_value("Stock Settings", "valuation_method", chosen_vm) + except Exception as exc: + frappe.log_error( + f"Stock Settings valuation_method set failed: {exc}", + "Jey Wizard materialize", + ) from invoice_az import company_api @@ -250,20 +259,25 @@ def _materialize_native_banks(company): def _find_bank_parent_account(company): - """'Bank hesablaşma hesabları' group (account_number=223) under this company. - Falls back to any group with the AZ name prefix.""" - parent = frappe.db.get_value( - "Account", + """Find the CoA group to place bank accounts under. In the AZ chart 223 + 'Bank hesablaşma hesabları' is a leaf (not a group) and its numbered + siblings 223.1/223.2/... all live under 22 'Pul vəsaitləri və onların + ekvivalentləri' — which is a group. Real bank accounts go there too. + + Preference order: + 1. 223 if it's ever been converted to a group. + 2. 22 (Pul vəsaitləri…), the actual AZ CoA group. + 3. Any group with name starting with 'Pul vəsaitləri'. + """ + for filters in ( {"company": company, "account_number": "223", "is_group": 1}, - "name", - ) - if parent: - return parent - return frappe.db.get_value( - "Account", - {"company": company, "account_name": ("like", "Bank hesablaşma%"), "is_group": 1}, - "name", - ) + {"company": company, "account_number": "22", "is_group": 1}, + {"company": company, "account_name": ("like", "Pul vəsaitləri%"), "is_group": 1}, + ): + parent = frappe.db.get_value("Account", filters, "name") + if parent: + return parent + return None def _materialize_one_bank(company, parent_account, row): @@ -323,26 +337,32 @@ def _materialize_one_bank(company, parent_account, row): def _delete_bank_placeholders(company, parent_account): - """Remove the stock 'AZN AZXXX…', 'USD AZXXX…' template accounts from AZ CoA - once real banks have been materialised. Fresh-setup only — nothing references - these placeholders yet, so deletion is safe. Per-row failures logged. + """Remove the stock template accounts from AZ CoA once real banks have been + materialised. Covers both ` AZXX…` and `Kart XXXX…` patterns — anything + with a run of four or more X characters in the name, under the bank parent. + Fresh-setup only — nothing references these placeholders yet, so deletion + is safe. Per-row failures logged. """ - placeholders = frappe.get_all( + candidates = frappe.get_all( "Account", filters={ "company": company, "parent_account": parent_account, - "account_name": ("like", "% AZXX%"), + "account_type": "Bank", "is_group": 0, }, - pluck="name", + fields=["name", "account_name"], ) - for name in placeholders: + for row in candidates: + if "XXXX" not in (row.account_name or ""): + continue try: - frappe.delete_doc("Account", name, ignore_permissions=True, delete_permanently=True) + frappe.delete_doc( + "Account", row.name, ignore_permissions=True, delete_permanently=True + ) except Exception as exc: frappe.log_error( - f"Placeholder delete failed: {name}: {exc}", + f"Placeholder delete failed: {row.name}: {exc}", "Jey Wizard materialize (bank placeholder)", ) diff --git a/jey_wizard/public/js/jey_setup.js b/jey_wizard/public/js/jey_setup.js index 925a0ea..d51fc9d 100644 --- a/jey_wizard/public/js/jey_setup.js +++ b/jey_wizard/public/js/jey_setup.js @@ -10,7 +10,7 @@ frappe.provide("jey_wizard"); // Bump this string in every commit that changes wizard code. Displayed in the badge so // we can tell at a glance which version is actually running on a given machine. Kept in // sync with __version__ in jey_wizard/__init__.py. -const JEY_WIZARD_VERSION = "0.1.4"; +const JEY_WIZARD_VERSION = "0.1.5"; // Wipe Frappe + ERPNext default slides so their `before_load`/`after_load` listeners // don't try to mutate a wizard that isn't slide-based anymore. @@ -48,6 +48,8 @@ frappe.setup.SetupWizard = class JeySetupWizard { asan_login_name: "", asan_voen: "", company_name: "", + company_abbr: "", + company_abbr_user_edited: false, fy_start_date: `${year}-01-01`, fy_end_date: `${year}-12-31`, valuation_method: "FIFO", @@ -391,6 +393,11 @@ frappe.setup.SetupWizard = class JeySetupWizard { ? `
${__("VÖEN {0} (from Asan)", [frappe.utils.escape_html(this.data.asan_voen)])}
` : ""} +
+ + +
${__("Short code suffixed to account names. Auto-suggested from the company name — you can edit it.")}
+

${__("Fiscal Year")}

@@ -417,6 +424,47 @@ frappe.setup.SetupWizard = class JeySetupWizard { $body.find(".jey-fy-start").val(this.data.fy_start_date); $body.find(".jey-fy-end").val(this.data.fy_end_date); $body.find(".jey-valuation-method").val(this.data.valuation_method || "FIFO"); + + // If user has never touched the abbr field, keep it in sync with the name. + // Once they edit it manually we stop auto-suggesting so their choice sticks. + const $nameEl = $body.find(".jey-company-name"); + const $abbrEl = $body.find(".jey-company-abbr"); + const fillAbbrDefault = () => { + if (!this.data.company_abbr_user_edited) { + const suggested = this.derive_abbr($nameEl.val() || ""); + this.data.company_abbr = suggested; + $abbrEl.val(suggested); + } + }; + $abbrEl.val(this.data.company_abbr || this.derive_abbr(this.data.company_name || "")); + $nameEl.on("input", fillAbbrDefault); + $abbrEl.on("input", (e) => { + this.data.company_abbr_user_edited = true; + this.data.company_abbr = ($(e.currentTarget).val() || "").toUpperCase().slice(0, 10); + $(e.currentTarget).val(this.data.company_abbr); + }); + } + + derive_abbr(rawName) { + // Normalise Azerbaijani company-form boilerplate before tokenising so the + // auto-suggest doesn't spell out each word of "Məhdud Məsuliyyətli Cəmiyyəti" + // as separate initials. Also strip every quote character — the stock + // ERPNext deriver chokes on them. + if (!rawName) return ""; + let name = String(rawName).replace(/["“”„‟«»‘’‚‛'`´]/g, " "); + name = name.replace(/[ƏE]/gi, (ch) => (ch === ch.toUpperCase() ? "E" : "e")); + const rules = [ + [/\bMEHDUD\s+MESULIYYETLI\s+CEMIYYETI\b/gi, "MMC"], + [/\bA[CÇ]IQ\s+SEHMDAR\s+CEMIYYETI\b/gi, "ASC"], + [/\bQAPALI\s+SEHMDAR\s+CEMIYYETI\b/gi, "QSC"], + [/\bSEHMDAR\s+CEMIYYETI\b/gi, "SC"], + [/\bKOOPERATIV\b/gi, "K"], + ]; + for (const [re, rep] of rules) name = name.replace(re, rep); + const parts = name.split(/\s+/).filter(Boolean); + if (parts.length === 0) return ""; + if (parts.length === 1) return parts[0].slice(0, 3).toUpperCase(); + return parts.slice(0, 4).map((p) => p[0]).join("").toUpperCase().slice(0, 10); } render_confirm($body) { @@ -428,7 +476,7 @@ frappe.setup.SetupWizard = class JeySetupWizard {

${__("Confirm")}

  • ${__("Language")}: ${LANG_NAME[this.data.language] || this.data.language}
  • -
  • ${__("Company")}: ${frappe.utils.escape_html(this.data.company_name)}
  • +
  • ${__("Company")}: ${frappe.utils.escape_html(this.data.company_name)} (${frappe.utils.escape_html(this.data.company_abbr || "")})
  • ${this.data.asan_voen ? `
  • ${__("VÖEN")}: ${frappe.utils.escape_html(this.data.asan_voen)}
  • ` : ""} @@ -493,6 +541,17 @@ frappe.setup.SetupWizard = class JeySetupWizard { return false; } this.data.company_name = name; + const abbrRaw = (this.$wrap.find(".jey-company-abbr").val() || "").toUpperCase().replace(/\s+/g, ""); + const abbr = abbrRaw || this.derive_abbr(name); + if (!abbr) { + this.$wrap.find(".jey-status").text(__("Company abbreviation is required")); + return false; + } + if (!/^[A-Z0-9]{1,10}$/.test(abbr)) { + this.$wrap.find(".jey-status").text(__("Abbreviation must be 1–10 letters/digits (A–Z, 0–9).")); + return false; + } + this.data.company_abbr = abbr; const fyStart = (this.$wrap.find(".jey-fy-start").val() || "").trim(); const fyEnd = (this.$wrap.find(".jey-fy-end").val() || "").trim(); if (!fyStart || !fyEnd) { @@ -1043,12 +1102,13 @@ frappe.setup.SetupWizard = class JeySetupWizard { build_setup_args() { const company = (this.data.company_name || "").trim() || "Test Company AZ"; - const parts = company.split(/\s+/).filter(Boolean); + // Prefer the user's (possibly edited) abbr captured in capture_current; + // fall back to the deriver if something bypassed validation. const abbr = ( - parts.length >= 2 - ? parts.slice(0, 3).map((p) => p[0]).join("") - : (parts[0] || "CO").slice(0, 3) - ).toUpperCase().slice(0, 10); + (this.data.company_abbr || "").toUpperCase().replace(/\s+/g, "") || + this.derive_abbr(company) || + "CO" + ).slice(0, 10); const admin = frappe.boot.user || {}; return { diff --git a/jey_wizard/translations/az.csv b/jey_wizard/translations/az.csv index 113a4b1..3039996 100644 --- a/jey_wizard/translations/az.csv +++ b/jey_wizard/translations/az.csv @@ -79,6 +79,10 @@ In System,Sistemdə Employees selected,İşçilər seçildi "Selected {0} employee(s). They will be imported into ƏMAS Employees and the standard Employee doctype after the wizard finishes.","Seçilən işçi: {0}. Sihirbaz tamamlandıqdan sonra ƏMAS Employees və standart Employee doctype-ə idxal ediləcək." Company name,Şirkət adı +Company abbreviation,Şirkətin qısaltması +"Short code suffixed to account names. Auto-suggested from the company name — you can edit it.","Hesab adlarına əlavə olunan qısa kod. Şirkət adından avtomatik təklif edilir — dəyişə bilərsiniz." +Company abbreviation is required,Şirkətin qısaltması tələb olunur +"Abbreviation must be 1–10 letters/digits (A–Z, 0–9).","Qısaltma 1–10 simvol olmalıdır (A–Z, 0–9)." VÖEN {0} (from Asan),VÖEN {0} (Asan-dan) Fiscal Year,Maliyyə ili Stock Valuation,Ehtiyatların qiymətləndirilməsi diff --git a/jey_wizard/translations/ru.csv b/jey_wizard/translations/ru.csv index 9525912..866baa0 100644 --- a/jey_wizard/translations/ru.csv +++ b/jey_wizard/translations/ru.csv @@ -79,6 +79,10 @@ In System,В системе Employees selected,Сотрудники выбраны "Selected {0} employee(s). They will be imported into ƏMAS Employees and the standard Employee doctype after the wizard finishes.","Выбрано сотрудников: {0}. Они будут импортированы в ƏMAS Employees и стандартный Employee после завершения мастера." Company name,Название компании +Company abbreviation,Сокращённое название +"Short code suffixed to account names. Auto-suggested from the company name — you can edit it.","Короткий код, добавляемый к названиям счетов. Подставляется автоматически — можно исправить." +Company abbreviation is required,Сокращённое название обязательно +"Abbreviation must be 1–10 letters/digits (A–Z, 0–9).","Сокращение должно быть 1–10 символов (A–Z, 0–9)." VÖEN {0} (from Asan),ВÖЕН {0} (из Asan) Fiscal Year,Финансовый год Stock Valuation,Оценка запасов