fixed small bugs

This commit is contained in:
Ali 2026-03-03 22:09:00 +04:00
parent 89a372c490
commit fe3ab9a38f
8 changed files with 597 additions and 9 deletions

View File

@ -743,6 +743,335 @@ def create_unmapped_suppliers(settings_name=None):
return {"success": False, "message": str(e)} 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) # RAW API ACCESSORS (for reports / manual use)
# ═══════════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════════
@ -1032,7 +1361,7 @@ def _build_import_lookups(settings):
purpose_rules = [] purpose_rules = []
for row in settings.purpose_mappings: for row in settings.purpose_mappings:
if row.purpose_keyword and row.paid_from and row.paid_to: 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 = {} voen_to_party = {}
name_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 paid_from = paid_to = None
best_purpose_score = 0 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: if keyword in purpose_lower:
paid_from = pf paid_from = pf
paid_to = pt paid_to = pt

View File

@ -512,7 +512,7 @@ KBBTImport.import = {
showTransactionSelection: function(txns) { showTransactionSelection: function(txns) {
let txnTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered kb-bt-txn-table" style="width: 100%; table-layout: fixed;">'; let txnTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered kb-bt-txn-table" style="width: 100%; table-layout: fixed;">';
txnTable += '<thead><tr>' + txnTable += '<thead style="position: sticky; top: 0; z-index: 1; background: var(--bg-color, #fff);"><tr>' +
'<th style="width: 4%;"><input type="checkbox" class="kb-bt-select-all-txns"></th>' + '<th style="width: 4%;"><input type="checkbox" class="kb-bt-select-all-txns"></th>' +
'<th style="width: 14%;">' + __('Ref No') + '</th>' + '<th style="width: 14%;">' + __('Ref No') + '</th>' +
'<th style="width: 10%;">' + __('Date') + '</th>' + '<th style="width: 10%;">' + __('Date') + '</th>' +

View File

@ -470,7 +470,7 @@ KBImport.import = {
showTransactionSelection: function(txns) { showTransactionSelection: function(txns) {
let txnTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered kb-txn-table" style="width: 100%; table-layout: fixed;">'; let txnTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered kb-txn-table" style="width: 100%; table-layout: fixed;">';
txnTable += '<thead><tr>' + txnTable += '<thead style="position: sticky; top: 0; z-index: 1; background: var(--bg-color, #fff);"><tr>' +
'<th style="width: 4%;"><input type="checkbox" class="kb-select-all-txns"></th>' + '<th style="width: 4%;"><input type="checkbox" class="kb-select-all-txns"></th>' +
'<th style="width: 14%;">' + __('Ref No') + '</th>' + '<th style="width: 14%;">' + __('Ref No') + '</th>' +
'<th style="width: 10%;">' + __('Date') + '</th>' + '<th style="width: 10%;">' + __('Date') + '</th>' +

View File

@ -13,9 +13,10 @@
"fields": [ "fields": [
{ {
"fieldname": "iban", "fieldname": "iban",
"fieldtype": "Data", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "IBAN", "label": "IBAN",
"options": "Kapital Bank Account",
"reqd": 1 "reqd": 1
}, },
{ {

View File

@ -5,6 +5,7 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"purpose_keyword", "purpose_keyword",
"payment_type",
"column_break_1", "column_break_1",
"paid_from", "paid_from",
"paid_to", "paid_to",
@ -18,6 +19,13 @@
"label": "Purpose Keyword", "label": "Purpose Keyword",
"reqd": 1 "reqd": 1
}, },
{
"fieldname": "payment_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Payment Type",
"options": "\nPay\nReceive"
},
{ {
"fieldname": "column_break_1", "fieldname": "column_break_1",
"fieldtype": "Column Break" "fieldtype": "Column Break"
@ -27,16 +35,14 @@
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Paid From (Account)", "label": "Paid From (Account)",
"options": "Account", "options": "Account"
"reqd": 1
}, },
{ {
"fieldname": "paid_to", "fieldname": "paid_to",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Paid To (Account)", "label": "Paid To (Account)",
"options": "Account", "options": "Account"
"reqd": 1
}, },
{ {
"fieldname": "notes", "fieldname": "notes",

View File

@ -6,6 +6,30 @@ frappe.ui.form.on('Kapital Bank Settings', {
}, __('Kapital Bank')); }, __('Kapital Bank'));
// ── Accounts buttons ────────────────────────────────────────────────── // ── 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'), () => { frm.add_custom_button(__('Add unmapped accounts'), () => {
if (frm.is_dirty()) { if (frm.is_dirty()) {
frm.save().then(() => add_unmapped_accounts(frm)); frm.save().then(() => add_unmapped_accounts(frm));
@ -15,6 +39,30 @@ frappe.ui.form.on('Kapital Bank Settings', {
}, __('Accounts')); }, __('Accounts'));
// ── Cards buttons ──────────────────────────────────────────────────── // ── 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'), () => { frm.add_custom_button(__('Add unmapped cards'), () => {
if (frm.is_dirty()) { if (frm.is_dirty()) {
frm.save().then(() => add_unmapped_cards(frm)); frm.save().then(() => add_unmapped_cards(frm));
@ -89,6 +137,19 @@ frappe.ui.form.on('Kapital Bank Settings', {
} }
}, __('Suppliers')); }, __('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 ──────────────────────────────────────────────── // ── Populate data tabs ────────────────────────────────────────────────
load_data_tabs(frm); 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(
__('<div style="color:red;font-weight:bold;">WARNING! This will create new Bank Account records for all unmapped accounts!</div><p>This action is irreversible. Are you sure?</p>'),
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(
__('<div style="color:red;font-weight:bold;">WARNING! This will create new Bank Account records for all unmapped cards!</div><p>This action is irreversible. Are you sure?</p>'),
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 // LOAD DATA DIALOG
// ═══════════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════════

View File

@ -9,6 +9,7 @@
"default_login", "default_login",
"default_company", "default_company",
"company_code", "company_code",
"default_bank",
"general_section", "general_section",
"similarity_threshold_customers", "similarity_threshold_customers",
"similarity_threshold_suppliers", "similarity_threshold_suppliers",
@ -77,6 +78,12 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Company Code" "label": "Company Code"
}, },
{
"fieldname": "default_bank",
"fieldtype": "Link",
"label": "Default Bank",
"options": "Bank"
},
{ {
"fieldname": "general_section", "fieldname": "general_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",

View File

@ -4,10 +4,33 @@ from frappe.model.document import Document
class KapitalBankSettings(Document): class KapitalBankSettings(Document):
def on_update(self): def on_update(self):
self._sync_account_statuses()
self._sync_customer_statuses() self._sync_customer_statuses()
self._sync_supplier_statuses() self._sync_supplier_statuses()
self._sync_card_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): def _sync_customer_statuses(self):
"""Reset Kapital Bank Customer status to 'New' for customers removed from customer_mappings.""" """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} mapped_names = {row.kb_customer_name for row in self.customer_mappings if row.kb_customer_name}