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
|
return
|
||||||
|
|
||||||
# Apply inventory valuation method before any stock loaders touch the Company.
|
# Apply inventory valuation method. Company.valuation_method wins over Stock
|
||||||
# Company-level wins over Stock Settings in erpnext.stock.utils.get_valuation_method.
|
# 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"
|
chosen_vm = (args or {}).get("valuation_method") or "FIFO"
|
||||||
if chosen_vm in ("FIFO", "Moving Average"):
|
if chosen_vm in ("FIFO", "Moving Average"):
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
"Company", company_name, "valuation_method", chosen_vm, update_modified=False
|
"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
|
from invoice_az import company_api
|
||||||
|
|
||||||
|
|
@ -250,20 +259,25 @@ def _materialize_native_banks(company):
|
||||||
|
|
||||||
|
|
||||||
def _find_bank_parent_account(company):
|
def _find_bank_parent_account(company):
|
||||||
"""'Bank hesablaşma hesabları' group (account_number=223) under this company.
|
"""Find the CoA group to place bank accounts under. In the AZ chart 223
|
||||||
Falls back to any group with the AZ name prefix."""
|
'Bank hesablaşma hesabları' is a leaf (not a group) and its numbered
|
||||||
parent = frappe.db.get_value(
|
siblings 223.1/223.2/... all live under 22 'Pul vəsaitləri və onların
|
||||||
"Account",
|
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},
|
{"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},
|
||||||
if parent:
|
):
|
||||||
return parent
|
parent = frappe.db.get_value("Account", filters, "name")
|
||||||
return frappe.db.get_value(
|
if parent:
|
||||||
"Account",
|
return parent
|
||||||
{"company": company, "account_name": ("like", "Bank hesablaşma%"), "is_group": 1},
|
return None
|
||||||
"name",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _materialize_one_bank(company, parent_account, row):
|
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):
|
def _delete_bank_placeholders(company, parent_account):
|
||||||
"""Remove the stock 'AZN AZXXX…', 'USD AZXXX…' template accounts from AZ CoA
|
"""Remove the stock template accounts from AZ CoA once real banks have been
|
||||||
once real banks have been materialised. Fresh-setup only — nothing references
|
materialised. Covers both `<CUR> AZXX…` and `Kart XXXX…` patterns — anything
|
||||||
these placeholders yet, so deletion is safe. Per-row failures logged.
|
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",
|
"Account",
|
||||||
filters={
|
filters={
|
||||||
"company": company,
|
"company": company,
|
||||||
"parent_account": parent_account,
|
"parent_account": parent_account,
|
||||||
"account_name": ("like", "% AZXX%"),
|
"account_type": "Bank",
|
||||||
"is_group": 0,
|
"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:
|
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:
|
except Exception as exc:
|
||||||
frappe.log_error(
|
frappe.log_error(
|
||||||
f"Placeholder delete failed: {name}: {exc}",
|
f"Placeholder delete failed: {row.name}: {exc}",
|
||||||
"Jey Wizard materialize (bank placeholder)",
|
"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
|
// 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
|
// 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.
|
// 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
|
// 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.
|
// 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_login_name: "",
|
||||||
asan_voen: "",
|
asan_voen: "",
|
||||||
company_name: "",
|
company_name: "",
|
||||||
|
company_abbr: "",
|
||||||
|
company_abbr_user_edited: false,
|
||||||
fy_start_date: `${year}-01-01`,
|
fy_start_date: `${year}-01-01`,
|
||||||
fy_end_date: `${year}-12-31`,
|
fy_end_date: `${year}-12-31`,
|
||||||
valuation_method: "FIFO",
|
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 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>
|
||||||
|
<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>
|
<h4 style="margin-top:20px;margin-bottom:10px">${__("Fiscal Year")}</h4>
|
||||||
<div style="display:flex;gap:16px;max-width:420px">
|
<div style="display:flex;gap:16px;max-width:420px">
|
||||||
<div style="flex:1">
|
<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-start").val(this.data.fy_start_date);
|
||||||
$body.find(".jey-fy-end").val(this.data.fy_end_date);
|
$body.find(".jey-fy-end").val(this.data.fy_end_date);
|
||||||
$body.find(".jey-valuation-method").val(this.data.valuation_method || "FIFO");
|
$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) {
|
render_confirm($body) {
|
||||||
|
|
@ -428,7 +476,7 @@ frappe.setup.SetupWizard = class JeySetupWizard {
|
||||||
<h3>${__("Confirm")}</h3>
|
<h3>${__("Confirm")}</h3>
|
||||||
<ul style="line-height:1.8">
|
<ul style="line-height:1.8">
|
||||||
<li><b>${__("Language")}:</b> ${LANG_NAME[this.data.language] || this.data.language}</li>
|
<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
|
${this.data.asan_voen
|
||||||
? `<li><b>${__("VÖEN")}:</b> ${frappe.utils.escape_html(this.data.asan_voen)}</li>`
|
? `<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;
|
return false;
|
||||||
}
|
}
|
||||||
this.data.company_name = name;
|
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 fyStart = (this.$wrap.find(".jey-fy-start").val() || "").trim();
|
||||||
const fyEnd = (this.$wrap.find(".jey-fy-end").val() || "").trim();
|
const fyEnd = (this.$wrap.find(".jey-fy-end").val() || "").trim();
|
||||||
if (!fyStart || !fyEnd) {
|
if (!fyStart || !fyEnd) {
|
||||||
|
|
@ -1043,12 +1102,13 @@ frappe.setup.SetupWizard = class JeySetupWizard {
|
||||||
|
|
||||||
build_setup_args() {
|
build_setup_args() {
|
||||||
const company = (this.data.company_name || "").trim() || "Test Company AZ";
|
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 = (
|
const abbr = (
|
||||||
parts.length >= 2
|
(this.data.company_abbr || "").toUpperCase().replace(/\s+/g, "") ||
|
||||||
? parts.slice(0, 3).map((p) => p[0]).join("")
|
this.derive_abbr(company) ||
|
||||||
: (parts[0] || "CO").slice(0, 3)
|
"CO"
|
||||||
).toUpperCase().slice(0, 10);
|
).slice(0, 10);
|
||||||
const admin = frappe.boot.user || {};
|
const admin = frappe.boot.user || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,10 @@ In System,Sistemdə
|
||||||
Employees selected,İşçilər seçildi
|
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."
|
"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 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)
|
VÖEN {0} (from Asan),VÖEN {0} (Asan-dan)
|
||||||
Fiscal Year,Maliyyə ili
|
Fiscal Year,Maliyyə ili
|
||||||
Stock Valuation,Ehtiyatların qiymətləndirilməsi
|
Stock Valuation,Ehtiyatların qiymətləndirilməsi
|
||||||
|
|
|
||||||
|
|
|
@ -79,6 +79,10 @@ In System,В системе
|
||||||
Employees selected,Сотрудники выбраны
|
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 после завершения мастера."
|
"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 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)
|
VÖEN {0} (from Asan),ВÖЕН {0} (из Asan)
|
||||||
Fiscal Year,Финансовый год
|
Fiscal Year,Финансовый год
|
||||||
Stock Valuation,Оценка запасов
|
Stock Valuation,Оценка запасов
|
||||||
|
|
|
||||||
|
Loading…
Reference in New Issue