fixed small bugs
This commit is contained in:
parent
89a372c490
commit
fe3ab9a38f
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>' +
|
||||||
|
|
|
||||||
|
|
@ -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>' +
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
// ═══════════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue