From fe3ab9a38fb519ffde9ca078a9cdae98a5d3bff2 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Tue, 3 Mar 2026 22:09:00 +0400 Subject: [PATCH] fixed small bugs --- kapital_bank/bank_api.py | 335 +++++++++++++++++- kapital_bank/client/bank_transaction.js | 2 +- kapital_bank/client/payment_entry.js | 2 +- .../kapital_bank_account_mapping.json | 3 +- .../kapital_bank_purpose_mapping.json | 14 +- .../kapital_bank_settings.js | 220 ++++++++++++ .../kapital_bank_settings.json | 7 + .../kapital_bank_settings.py | 23 ++ 8 files changed, 597 insertions(+), 9 deletions(-) diff --git a/kapital_bank/bank_api.py b/kapital_bank/bank_api.py index 037122d..a895da1 100644 --- a/kapital_bank/bank_api.py +++ b/kapital_bank/bank_api.py @@ -743,6 +743,335 @@ def create_unmapped_suppliers(settings_name=None): return {"success": False, "message": str(e)} +# ═══════════════════════════════════════════════════════════════════════════════ +# ACCOUNT / CARD → BANK ACCOUNT MATCHING & CREATION +# ═══════════════════════════════════════════════════════════════════════════════ + +@frappe.whitelist() +def match_similar_accounts(): + """Auto-match account mappings to ERPNext Bank Accounts by IBAN.""" + try: + doc = frappe.get_doc("Kapital Bank Settings") + + erp_bank_accounts = frappe.get_all( + "Bank Account", + fields=["name", "bank_account_no", "iban"], + ) + + # Build lookup: iban/bank_account_no → (name, account) + iban_index = {} + for ba in erp_bank_accounts: + if ba.iban: + iban_index[ba.iban.strip()] = ba + if ba.bank_account_no: + iban_index[ba.bank_account_no.strip()] = ba + + matched = 0 + total_processed = 0 + + for row in doc.account_mappings: + if row.bank_account or not row.iban: + continue + total_processed += 1 + + match = iban_index.get(row.iban.strip()) + if match: + row.bank_account = match.name + # Fetch GL account from Bank Account + gl_account = frappe.db.get_value("Bank Account", match.name, "account") + if gl_account: + row.gl_account = gl_account + + frappe.db.set_value("Kapital Bank Account", row.iban, { + "status": "Mapped", + "bank_account": match.name, + }, update_modified=False) + matched += 1 + + if matched > 0: + doc.save(ignore_permissions=True) + + frappe.db.commit() + return {"success": True, "matched_count": matched, "total_processed": total_processed} + + except Exception as e: + frappe.log_error(f"match_similar_accounts: {e}\n{frappe.get_traceback()}", "Kapital Bank Account Matching") + return {"success": False, "message": str(e)} + + +@frappe.whitelist() +def create_unmapped_accounts(settings_name=None): + """Create Bank Account records for unmapped accounts in account_mappings.""" + try: + doc = frappe.get_doc("Kapital Bank Settings") + + unmapped = [] + for row in doc.account_mappings: + if not row.bank_account and row.iban: + unmapped.append(row) + + if not unmapped: + return {"success": True, "created_count": 0, "message": "No unmapped accounts to create"} + + default_bank = doc.default_bank + default_company = doc.default_company + + created = 0 + for row in unmapped: + try: + # Check if Bank Account already exists for this IBAN + existing = frappe.db.get_value("Bank Account", {"bank_account_no": row.iban}, "name") + if existing: + row.bank_account = existing + gl_account = frappe.db.get_value("Bank Account", existing, "account") + if gl_account: + row.gl_account = gl_account + frappe.db.set_value("Kapital Bank Account", row.iban, { + "status": "Mapped", + "bank_account": existing, + }, update_modified=False) + created += 1 + continue + + account_name = row.account_label or row.iban + + ba = frappe.new_doc("Bank Account") + ba.account_name = account_name + ba.bank_account_no = row.iban + if default_bank: + ba.bank = default_bank + if default_company: + ba.company = default_company + ba.insert(ignore_permissions=True) + + row.bank_account = ba.name + gl_account = frappe.db.get_value("Bank Account", ba.name, "account") + if gl_account: + row.gl_account = gl_account + + frappe.db.set_value("Kapital Bank Account", row.iban, { + "status": "Mapped", + "bank_account": ba.name, + }, update_modified=False) + created += 1 + + except Exception as e: + frappe.log_error(f"create_unmapped_accounts: error for {row.iban}: {e}", "Kapital Bank Account Creation") + + if created > 0: + doc.save(ignore_permissions=True) + + frappe.db.commit() + return {"success": True, "created_count": created} + + except Exception as e: + frappe.log_error(f"create_unmapped_accounts: {e}\n{frappe.get_traceback()}", "Kapital Bank Account Creation") + return {"success": False, "message": str(e)} + + +@frappe.whitelist() +def match_similar_cards(): + """Auto-match card mappings to ERPNext Bank Accounts by account_number.""" + try: + doc = frappe.get_doc("Kapital Bank Settings") + + erp_bank_accounts = frappe.get_all( + "Bank Account", + fields=["name", "bank_account_no"], + ) + + # Build lookup: bank_account_no → Bank Account + acno_index = {} + for ba in erp_bank_accounts: + if ba.bank_account_no: + acno_index[ba.bank_account_no.strip()] = ba + + matched = 0 + total_processed = 0 + + for row in doc.card_mappings: + if row.bank_account or not row.account_number: + continue + total_processed += 1 + + match = acno_index.get(row.account_number.strip()) + if match: + row.bank_account = match.name + gl_account = frappe.db.get_value("Bank Account", match.name, "account") + if gl_account: + row.gl_account = gl_account + + frappe.db.set_value("Kapital Bank Card", {"account_number": row.account_number}, { + "status": "Mapped", + "bank_account": match.name, + }, update_modified=False) + matched += 1 + + if matched > 0: + doc.save(ignore_permissions=True) + + frappe.db.commit() + return {"success": True, "matched_count": matched, "total_processed": total_processed} + + except Exception as e: + frappe.log_error(f"match_similar_cards: {e}\n{frappe.get_traceback()}", "Kapital Bank Card Matching") + return {"success": False, "message": str(e)} + + +@frappe.whitelist() +def create_unmapped_cards(settings_name=None): + """Create Bank Account records for unmapped cards in card_mappings.""" + try: + doc = frappe.get_doc("Kapital Bank Settings") + + unmapped = [] + for row in doc.card_mappings: + if not row.bank_account and row.account_number: + unmapped.append(row) + + if not unmapped: + return {"success": True, "created_count": 0, "message": "No unmapped cards to create"} + + default_bank = doc.default_bank + default_company = doc.default_company + + created = 0 + for row in unmapped: + try: + # Check if Bank Account already exists for this account_number + existing = frappe.db.get_value("Bank Account", {"bank_account_no": row.account_number}, "name") + if existing: + row.bank_account = existing + gl_account = frappe.db.get_value("Bank Account", existing, "account") + if gl_account: + row.gl_account = gl_account + frappe.db.set_value("Kapital Bank Card", {"account_number": row.account_number}, { + "status": "Mapped", + "bank_account": existing, + }, update_modified=False) + created += 1 + continue + + account_name = row.card_label or row.account_number + + ba = frappe.new_doc("Bank Account") + ba.account_name = account_name + ba.bank_account_no = row.account_number + if default_bank: + ba.bank = default_bank + if default_company: + ba.company = default_company + ba.insert(ignore_permissions=True) + + row.bank_account = ba.name + gl_account = frappe.db.get_value("Bank Account", ba.name, "account") + if gl_account: + row.gl_account = gl_account + + frappe.db.set_value("Kapital Bank Card", {"account_number": row.account_number}, { + "status": "Mapped", + "bank_account": ba.name, + }, update_modified=False) + created += 1 + + except Exception as e: + frappe.log_error(f"create_unmapped_cards: error for {row.account_number}: {e}", "Kapital Bank Card Creation") + + if created > 0: + doc.save(ignore_permissions=True) + + frappe.db.commit() + return {"success": True, "created_count": created} + + except Exception as e: + frappe.log_error(f"create_unmapped_cards: {e}\n{frappe.get_traceback()}", "Kapital Bank Card Creation") + return {"success": False, "message": str(e)} + + +# ═══════════════════════════════════════════════════════════════════════════════ +# PURPOSE LOADING FROM STATEMENTS +# ═══════════════════════════════════════════════════════════════════════════════ + +@frappe.whitelist() +def load_purposes_from_statements(from_date, to_date, login_name=None): + """Load unique purposes from account statements and add new ones to purpose_mappings.""" + try: + doc = frappe.get_doc("Kapital Bank Settings") + + # Collect existing purpose keywords (lowercased for comparison) + existing_keywords = { + row.purpose_keyword.lower() + for row in doc.purpose_mappings + if row.purpose_keyword + } + + # Get all registered accounts + accounts = frappe.get_all( + "Kapital Bank Account", + fields=["name", "cust_ac_no"], + ) + + if not accounts: + return {"success": True, "added_count": 0, "message": "No accounts in registry"} + + client = BIRBankClient(login_name) + all_purposes = set() + + for account in accounts: + if not account.cust_ac_no: + continue + try: + resp = client.get( + "/v2/statement/account", + params={ + "accountNumber": account.cust_ac_no, + "fromDate": _fmt_date(from_date), + "toDate": _fmt_date(to_date), + } + ) + if not resp.ok: + continue + + data = resp.json() + resp_code = data.get("response", {}).get("code", "?") + if resp_code != "0": + continue + + statement_list = ( + data.get("responseData", {}) + .get("operations", {}) + .get("statementList", []) + ) + + for txn in statement_list: + purpose = (txn.get("purpose") or "").strip() + if purpose: + all_purposes.add(purpose) + + except Exception: + continue + + # Add new purposes not already in the mapping table + added = 0 + for purpose in sorted(all_purposes): + if purpose.lower() not in existing_keywords: + doc.append("purpose_mappings", { + "purpose_keyword": purpose, + }) + existing_keywords.add(purpose.lower()) + added += 1 + + if added > 0: + doc.save(ignore_permissions=True) + frappe.db.commit() + + return {"success": True, "added_count": added, "total_found": len(all_purposes)} + + except Exception as e: + frappe.log_error(f"load_purposes_from_statements: {e}\n{frappe.get_traceback()}", "Kapital Bank Purpose Loading") + return {"success": False, "message": str(e)} + + # ═══════════════════════════════════════════════════════════════════════════════ # RAW API ACCESSORS (for reports / manual use) # ═══════════════════════════════════════════════════════════════════════════════ @@ -1032,7 +1361,7 @@ def _build_import_lookups(settings): purpose_rules = [] for row in settings.purpose_mappings: if row.purpose_keyword and row.paid_from and row.paid_to: - purpose_rules.append((row.purpose_keyword.lower(), row.paid_from, row.paid_to)) + purpose_rules.append((row.purpose_keyword.lower(), row.paid_from, row.paid_to, getattr(row, "payment_type", None) or "")) voen_to_party = {} name_to_party = {} @@ -1080,7 +1409,9 @@ def _import_one_transaction(txn_data, settings, purpose_rules, purpose_threshold paid_from = paid_to = None best_purpose_score = 0 - for keyword, pf, pt in purpose_rules: + for keyword, pf, pt, rule_payment_type in purpose_rules: + if rule_payment_type and rule_payment_type != payment_type: + continue if keyword in purpose_lower: paid_from = pf paid_to = pt diff --git a/kapital_bank/client/bank_transaction.js b/kapital_bank/client/bank_transaction.js index 1aa114f..3a8841b 100644 --- a/kapital_bank/client/bank_transaction.js +++ b/kapital_bank/client/bank_transaction.js @@ -512,7 +512,7 @@ KBBTImport.import = { showTransactionSelection: function(txns) { let txnTable = '
'; - txnTable += '' + + txnTable += '' + '' + '' + '' + diff --git a/kapital_bank/client/payment_entry.js b/kapital_bank/client/payment_entry.js index b6f7a9a..0c84831 100644 --- a/kapital_bank/client/payment_entry.js +++ b/kapital_bank/client/payment_entry.js @@ -470,7 +470,7 @@ KBImport.import = { showTransactionSelection: function(txns) { let txnTable = '
' + __('Ref No') + '' + __('Date') + '
'; - txnTable += '' + + txnTable += '' + '' + '' + '' + diff --git a/kapital_bank/kapital_bank/doctype/kapital_bank_account_mapping/kapital_bank_account_mapping.json b/kapital_bank/kapital_bank/doctype/kapital_bank_account_mapping/kapital_bank_account_mapping.json index d86920f..19a1fe0 100644 --- a/kapital_bank/kapital_bank/doctype/kapital_bank_account_mapping/kapital_bank_account_mapping.json +++ b/kapital_bank/kapital_bank/doctype/kapital_bank_account_mapping/kapital_bank_account_mapping.json @@ -13,9 +13,10 @@ "fields": [ { "fieldname": "iban", - "fieldtype": "Data", + "fieldtype": "Link", "in_list_view": 1, "label": "IBAN", + "options": "Kapital Bank Account", "reqd": 1 }, { diff --git a/kapital_bank/kapital_bank/doctype/kapital_bank_purpose_mapping/kapital_bank_purpose_mapping.json b/kapital_bank/kapital_bank/doctype/kapital_bank_purpose_mapping/kapital_bank_purpose_mapping.json index e784a1c..ff620a3 100644 --- a/kapital_bank/kapital_bank/doctype/kapital_bank_purpose_mapping/kapital_bank_purpose_mapping.json +++ b/kapital_bank/kapital_bank/doctype/kapital_bank_purpose_mapping/kapital_bank_purpose_mapping.json @@ -5,6 +5,7 @@ "engine": "InnoDB", "field_order": [ "purpose_keyword", + "payment_type", "column_break_1", "paid_from", "paid_to", @@ -18,6 +19,13 @@ "label": "Purpose Keyword", "reqd": 1 }, + { + "fieldname": "payment_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Payment Type", + "options": "\nPay\nReceive" + }, { "fieldname": "column_break_1", "fieldtype": "Column Break" @@ -27,16 +35,14 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Paid From (Account)", - "options": "Account", - "reqd": 1 + "options": "Account" }, { "fieldname": "paid_to", "fieldtype": "Link", "in_list_view": 1, "label": "Paid To (Account)", - "options": "Account", - "reqd": 1 + "options": "Account" }, { "fieldname": "notes", diff --git a/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.js b/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.js index d8c2c4d..6844807 100644 --- a/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.js +++ b/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.js @@ -6,6 +6,30 @@ frappe.ui.form.on('Kapital Bank Settings', { }, __('Kapital Bank')); // ── Accounts buttons ────────────────────────────────────────────────── + frm.add_custom_button(__('Match accounts to Bank Accounts'), () => { + if (frm.is_dirty()) { + frappe.confirm( + __('Document contains unsaved changes. Save before performing the operation?'), + () => frm.save().then(() => _match_accounts(frm)), + () => frappe.show_alert({ message: __('Operation canceled. Please save the document first.'), indicator: 'red' }, 5) + ); + } else { + _match_accounts(frm); + } + }, __('Accounts')); + + frm.add_custom_button(__('Create Bank Accounts'), () => { + if (frm.is_dirty()) { + frappe.confirm( + __('Document contains unsaved changes. Save before performing the operation?'), + () => frm.save().then(() => create_unmapped_bank_accounts(frm)), + () => frappe.show_alert({ message: __('Operation canceled. Please save the document first.'), indicator: 'red' }, 5) + ); + } else { + create_unmapped_bank_accounts(frm); + } + }, __('Accounts')); + frm.add_custom_button(__('Add unmapped accounts'), () => { if (frm.is_dirty()) { frm.save().then(() => add_unmapped_accounts(frm)); @@ -15,6 +39,30 @@ frappe.ui.form.on('Kapital Bank Settings', { }, __('Accounts')); // ── Cards buttons ──────────────────────────────────────────────────── + frm.add_custom_button(__('Match cards to Bank Accounts'), () => { + if (frm.is_dirty()) { + frappe.confirm( + __('Document contains unsaved changes. Save before performing the operation?'), + () => frm.save().then(() => _match_cards(frm)), + () => frappe.show_alert({ message: __('Operation canceled. Please save the document first.'), indicator: 'red' }, 5) + ); + } else { + _match_cards(frm); + } + }, __('Cards')); + + frm.add_custom_button(__('Create Bank Accounts for cards'), () => { + if (frm.is_dirty()) { + frappe.confirm( + __('Document contains unsaved changes. Save before performing the operation?'), + () => frm.save().then(() => create_unmapped_bank_accounts_for_cards(frm)), + () => frappe.show_alert({ message: __('Operation canceled. Please save the document first.'), indicator: 'red' }, 5) + ); + } else { + create_unmapped_bank_accounts_for_cards(frm); + } + }, __('Cards')); + frm.add_custom_button(__('Add unmapped cards'), () => { if (frm.is_dirty()) { frm.save().then(() => add_unmapped_cards(frm)); @@ -89,6 +137,19 @@ frappe.ui.form.on('Kapital Bank Settings', { } }, __('Suppliers')); + // ── Purpose buttons ─────────────────────────────────────────────────── + frm.add_custom_button(__('Load purposes from statements'), () => { + if (frm.is_dirty()) { + frappe.confirm( + __('Document contains unsaved changes. Save before performing the operation?'), + () => frm.save().then(() => _load_purposes(frm)), + () => frappe.show_alert({ message: __('Operation canceled. Please save the document first.'), indicator: 'red' }, 5) + ); + } else { + _load_purposes(frm); + } + }, __('Purpose')); + // ── Populate data tabs ──────────────────────────────────────────────── load_data_tabs(frm); } @@ -144,6 +205,165 @@ function _match_suppliers(frm) { ); } +// ═══════════════════════════════════════════════════════════════════════════════ +// MATCH / CREATE ACCOUNTS & CARDS +// ═══════════════════════════════════════════════════════════════════════════════ + +function _match_accounts(frm) { + frappe.confirm( + __('This will automatically match unmapped accounts with existing Bank Accounts by IBAN. Continue?'), + () => { + frappe.show_alert({ message: __('Matching accounts...'), indicator: 'blue' }); + frappe.call({ + method: 'kapital_bank.bank_api.match_similar_accounts', + callback(r) { + if (r.message && r.message.success) { + frappe.show_alert({ + message: __('Matched {0} of {1} accounts', [r.message.matched_count, r.message.total_processed]), + indicator: 'green' + }, 5); + frm.reload_doc(); + } else { + frappe.msgprint({ title: __('Error'), indicator: 'red', message: r.message ? r.message.message : __('Error matching accounts') }); + } + } + }); + } + ); +} + +function create_unmapped_bank_accounts(frm) { + frappe.confirm( + __('
WARNING! This will create new Bank Account records for all unmapped accounts!

This action is irreversible. Are you sure?

'), + function() { + frappe.confirm( + __('Are you really sure? This cannot be undone.'), + function() { + frappe.call({ + method: 'kapital_bank.bank_api.create_unmapped_accounts', + args: { 'settings_name': frm.doc.name }, + callback: function(r) { + if (r.message && r.message.success) { + frappe.show_alert({ + message: __('Created {0} new Bank Accounts', [r.message.created_count]), + indicator: 'green' + }, 6); + frm.reload_doc(); + } else { + frappe.msgprint({ + title: __('Error'), + indicator: 'red', + message: r.message ? r.message.message : __('Error creating Bank Accounts') + }); + } + } + }); + } + ); + } + ); +} + +function _match_cards(frm) { + frappe.confirm( + __('This will automatically match unmapped cards with existing Bank Accounts by account number. Continue?'), + () => { + frappe.show_alert({ message: __('Matching cards...'), indicator: 'blue' }); + frappe.call({ + method: 'kapital_bank.bank_api.match_similar_cards', + callback(r) { + if (r.message && r.message.success) { + frappe.show_alert({ + message: __('Matched {0} of {1} cards', [r.message.matched_count, r.message.total_processed]), + indicator: 'green' + }, 5); + frm.reload_doc(); + } else { + frappe.msgprint({ title: __('Error'), indicator: 'red', message: r.message ? r.message.message : __('Error matching cards') }); + } + } + }); + } + ); +} + +function create_unmapped_bank_accounts_for_cards(frm) { + frappe.confirm( + __('
WARNING! This will create new Bank Account records for all unmapped cards!

This action is irreversible. Are you sure?

'), + function() { + frappe.confirm( + __('Are you really sure? This cannot be undone.'), + function() { + frappe.call({ + method: 'kapital_bank.bank_api.create_unmapped_cards', + args: { 'settings_name': frm.doc.name }, + callback: function(r) { + if (r.message && r.message.success) { + frappe.show_alert({ + message: __('Created {0} new Bank Accounts for cards', [r.message.created_count]), + indicator: 'green' + }, 6); + frm.reload_doc(); + } else { + frappe.msgprint({ + title: __('Error'), + indicator: 'red', + message: r.message ? r.message.message : __('Error creating Bank Accounts for cards') + }); + } + } + }); + } + ); + } + ); +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// LOAD PURPOSES FROM STATEMENTS +// ═══════════════════════════════════════════════════════════════════════════════ + +function _load_purposes(frm) { + const today = frappe.datetime.get_today(); + const first_day = frappe.datetime.month_start(today); + + const d = new frappe.ui.Dialog({ + title: __('Load Purposes from Statements'), + fields: [ + { fieldname: 'date_from', fieldtype: 'Date', label: __('Date From'), reqd: 1, default: first_day }, + { fieldname: 'col_break', fieldtype: 'Column Break' }, + { fieldname: 'date_to', fieldtype: 'Date', label: __('Date To'), reqd: 1, default: today }, + ], + primary_action_label: __('Load'), + primary_action(values) { + d.hide(); + frappe.show_alert({ message: __('Loading purposes from statements...'), indicator: 'blue' }); + frappe.call({ + method: 'kapital_bank.bank_api.load_purposes_from_statements', + args: { from_date: values.date_from, to_date: values.date_to }, + callback(r) { + if (r.message && r.message.success) { + frappe.show_alert({ + message: __('Added {0} new purposes (found {1} total)', [r.message.added_count, r.message.total_found]), + indicator: 'green' + }, 6); + if (r.message.added_count > 0) { + frm.reload_doc(); + } + } else { + frappe.msgprint({ + title: __('Error'), + indicator: 'red', + message: r.message ? r.message.message : __('Error loading purposes') + }); + } + } + }); + } + }); + d.show(); +} + // ═══════════════════════════════════════════════════════════════════════════════ // LOAD DATA DIALOG // ═══════════════════════════════════════════════════════════════════════════════ diff --git a/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.json b/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.json index 9890667..db8ce80 100644 --- a/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.json +++ b/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.json @@ -9,6 +9,7 @@ "default_login", "default_company", "company_code", + "default_bank", "general_section", "similarity_threshold_customers", "similarity_threshold_suppliers", @@ -77,6 +78,12 @@ "fieldtype": "Data", "label": "Company Code" }, + { + "fieldname": "default_bank", + "fieldtype": "Link", + "label": "Default Bank", + "options": "Bank" + }, { "fieldname": "general_section", "fieldtype": "Section Break", diff --git a/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.py b/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.py index fc3902e..1721da4 100644 --- a/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.py +++ b/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.py @@ -4,10 +4,33 @@ from frappe.model.document import Document class KapitalBankSettings(Document): def on_update(self): + self._sync_account_statuses() self._sync_customer_statuses() self._sync_supplier_statuses() self._sync_card_statuses() + def _sync_account_statuses(self): + """Reset Kapital Bank Account status to 'New' for accounts removed from account_mappings.""" + mapped_ibans = {row.iban for row in self.account_mappings if row.iban} + + orphaned = frappe.get_all( + "Kapital Bank Account", + filters={"status": "Mapped"}, + fields=["name"], + ) + + for account in orphaned: + if account.name not in mapped_ibans: + frappe.db.set_value( + "Kapital Bank Account", + account.name, + { + "status": "New", + "bank_account": None, + }, + update_modified=False, + ) + def _sync_customer_statuses(self): """Reset Kapital Bank Customer status to 'New' for customers removed from customer_mappings.""" mapped_names = {row.kb_customer_name for row in self.customer_mappings if row.kb_customer_name}
' + __('Ref No') + '' + __('Date') + '