Materialize ERPNext Bank / Bank Account / GL Account from e-taxes load

load_company_bank_accounts now makes every non-closed e-taxes bank
record usable inside ERPNext, not just a read-only E-Taxes Bank Account
cache row:

- Bank (global, one per unique bankName) — idempotent insert.
- GL Account under parent "223 Bank hesablaşma hesabları", named
  "<CUR> <IBAN>", account_type=Bank, account_currency from e-taxes.
  Parent discovered by account_number=223 with a name-prefix fallback.
- Bank Account linking Bank + GL Account + Company, is_company_account=1,
  iban + bank_account_no = the AZ IBAN.

Closed accounts (status=C) are left at cache-row only; no native
records are created for them.

After the loop, unused AZ CoA placeholder accounts (names like
"AZN AZXXXXXXXXXXXXXXXXXXXXXXXXXX" under the bank group) are deleted
so the chart isn't cluttered with unmatched templates. Per-record
failures go to the "E-Taxes Bank Native Materialize" error log and
don't abort the overall load.
This commit is contained in:
Ali 2026-04-24 13:01:33 +00:00
parent f622a4ce2a
commit bb13e293c9
1 changed files with 131 additions and 0 deletions

View File

@ -471,6 +471,23 @@ def load_company_bank_accounts(company):
created += c
updated += u
# Phase 2: also materialise ERPNext-native Bank / GL Account / Bank Account
# for non-closed accounts so users can actually post transactions against them.
# Status "C" = closed — skip entirely (cache row above still written).
if (acc.get("status") or "").upper() != "C":
try:
_materialize_native_bank(company, acc)
except Exception as e:
frappe.log_error(
f"{acc.get('number')}: {e}\n{frappe.get_traceback()}",
"E-Taxes Bank Native Materialize",
)
# Clean up unused AZ CoA bank placeholders (entries named "<CUR> AZXX…" under
# parent 223). On a fresh setup they've never been referenced, so deletion
# is safe. Per-row failures logged but don't abort.
_delete_bank_placeholders(company)
frappe.db.commit()
return _summarize(created, updated, len(accounts), "Bank accounts loaded")
@ -482,6 +499,120 @@ def load_company_bank_accounts(company):
return {"success": False, "message": f"Error: {e}"}
def _find_bank_parent_account(company):
"""Resolve the 'Bank hesablaşma hesabları' (account_number=223) group under this
company. Falls back to any Asset group containing bank-typed children."""
parent = frappe.db.get_value(
"Account",
{"company": company, "account_number": "223", "is_group": 1},
"name",
)
if parent:
return parent
parent = frappe.db.get_value(
"Account",
{"company": company, "account_name": ("like", "Bank hesablaşma%"), "is_group": 1},
"name",
)
return parent
def _materialize_native_bank(company, acc):
"""Create Bank (global) + GL Account (per-company) + Bank Account (linking) for
a single e-taxes bank record. Idempotent safe to call repeatedly.
"""
bank_name = (acc.get("bankName") or "").strip()
iban = (acc.get("number") or "").strip()
currency = (acc.get("currency") or "").strip() or "AZN"
etx_account_type = (acc.get("type") or "").strip()
if not bank_name or not iban:
return # can't build records without these
# 1. Bank (global, unique on bank_name)
if not frappe.db.exists("Bank", bank_name):
bank_doc = frappe.get_doc({"doctype": "Bank", "bank_name": bank_name})
bank_doc.flags.ignore_permissions = True
bank_doc.flags.ignore_if_duplicate = True
bank_doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
# 2. GL Account under parent 223 (Bank hesablaşma hesabları)
parent = _find_bank_parent_account(company)
if not parent:
frappe.log_error(
f"No bank parent account found for company '{company}'; skipping native GL account for {iban}",
"E-Taxes Bank Native Materialize",
)
return
account_name = f"{currency} {iban}"
existing_gl = frappe.db.get_value(
"Account",
{"company": company, "account_name": account_name},
"name",
)
if existing_gl:
gl_name = existing_gl
else:
gl_doc = frappe.get_doc({
"doctype": "Account",
"account_name": account_name,
"parent_account": parent,
"company": company,
"account_type": "Bank",
"account_currency": currency,
"is_group": 0,
})
gl_doc.insert(ignore_permissions=True)
gl_name = gl_doc.name
# 3. Bank Account (links Bank + GL + Company)
ba_exists = frappe.db.exists(
"Bank Account",
{"bank": bank_name, "account_name": account_name, "company": company},
)
if not ba_exists:
ba_doc = frappe.get_doc({
"doctype": "Bank Account",
"account_name": account_name,
"bank": bank_name,
"account": gl_name,
"company": company,
"is_company_account": 1,
"bank_account_no": iban,
"iban": iban,
"account_type": etx_account_type or None,
})
ba_doc.insert(ignore_permissions=True)
def _delete_bank_placeholders(company):
"""AZ CoA seeds placeholders like 'AZN AZXXXXXXXXXXXXXXXXXXXXXXXXXX' under the
bank group. Remove any that remain after real accounts have been materialised.
"""
parent = _find_bank_parent_account(company)
if not parent:
return
placeholders = frappe.get_all(
"Account",
filters={
"company": company,
"parent_account": parent,
"account_name": ("like", "% AZXX%"),
"is_group": 0,
},
pluck="name",
)
for p in placeholders:
try:
frappe.delete_doc("Account", p, ignore_permissions=True, delete_permanently=True)
except Exception as e:
frappe.log_error(
f"Placeholder delete failed: {p}: {e}",
"E-Taxes Bank Native Materialize",
)
# ======= OBLIGATION PACTS (sub-contractor obl-pact-list) =======
@frappe.whitelist()