diff --git a/invoice_az/company_api.py b/invoice_az/company_api.py index 5f2d133..ff6a51c 100644 --- a/invoice_az/company_api.py +++ b/invoice_az/company_api.py @@ -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 " 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()