Fix silent bank materialize, set Stock Settings too, editable abbr
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.
This commit is contained in:
parent
45e9e54535
commit
4ed716637d
|
|
@ -1 +1 @@
|
|||
__version__ = "0.1.4"
|
||||
__version__ = "0.1.5"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
{"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 frappe.db.get_value(
|
||||
"Account",
|
||||
{"company": company, "account_name": ("like", "Bank hesablaşma%"), "is_group": 1},
|
||||
"name",
|
||||
)
|
||||
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 `<CUR> 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)",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
? `<div style="color:#888;font-size:12px;margin-top:6px">${__("VÖEN {0} (from Asan)", [frappe.utils.escape_html(this.data.asan_voen)])}</div>`
|
||||
: ""}
|
||||
</div>
|
||||
<div style="margin-bottom:12px">
|
||||
<label style="display:block;margin-bottom:4px">${__("Company abbreviation")}</label>
|
||||
<input type="text" class="jey-company-abbr form-control" style="max-width:140px;text-transform:uppercase" maxlength="10" />
|
||||
<div style="color:#888;font-size:12px;margin-top:6px">${__("Short code suffixed to account names. Auto-suggested from the company name — you can edit it.")}</div>
|
||||
</div>
|
||||
<h4 style="margin-top:20px;margin-bottom:10px">${__("Fiscal Year")}</h4>
|
||||
<div style="display:flex;gap:16px;max-width:420px">
|
||||
<div style="flex:1">
|
||||
|
|
@ -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 {
|
|||
<h3>${__("Confirm")}</h3>
|
||||
<ul style="line-height:1.8">
|
||||
<li><b>${__("Language")}:</b> ${LANG_NAME[this.data.language] || this.data.language}</li>
|
||||
<li><b>${__("Company")}:</b> ${frappe.utils.escape_html(this.data.company_name)}</li>
|
||||
<li><b>${__("Company")}:</b> ${frappe.utils.escape_html(this.data.company_name)} <span style="color:#888">(${frappe.utils.escape_html(this.data.company_abbr || "")})</span></li>
|
||||
${this.data.asan_voen
|
||||
? `<li><b>${__("VÖEN")}:</b> ${frappe.utils.escape_html(this.data.asan_voen)}</li>`
|
||||
: ""}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
|
@ -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,Оценка запасов
|
||||
|
|
|
|||
|
Loading…
Reference in New Issue