From 68c3d7d8a9c7f09c06b6d1d3c716fedefc8a8b20 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Fri, 13 Mar 2026 21:04:16 +0400 Subject: [PATCH] bug fix --- kapital_bank/mapping.py | 125 ++++++++++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 19 deletions(-) diff --git a/kapital_bank/mapping.py b/kapital_bank/mapping.py index a316aa6..b84f3ea 100644 --- a/kapital_bank/mapping.py +++ b/kapital_bank/mapping.py @@ -1,6 +1,18 @@ import json import frappe +from frappe.utils import cint, flt + + +def _extract_messages(message_log): + """Extract plain text from frappe.local.message_log entries.""" + parts = [] + for m in message_log: + if isinstance(m, dict): + parts.append(m.get("message") or m.get("msg") or "") + else: + parts.append(str(m)) + return " | ".join(p for p in parts if p) @frappe.whitelist() @@ -291,6 +303,8 @@ def _create_and_reconcile_doc(txn, paid_from, paid_to, document_type, multi_curr return {"success": True, "doc_name": doc_name, "reconciled": False, "error": str(re)} except Exception as e: + frappe.db.rollback() + frappe.local.message_log = [] frappe.log_error( f"_create_and_reconcile_doc failed for {bank_transaction_name}: {e}\n{frappe.get_traceback()}", "Kapital Bank Mapping", @@ -320,11 +334,22 @@ def _create_payment_entry_for_brt(txn, paid_from, paid_to, bank_txn): if party_type and party: pe.party_type = party_type pe.party = party + + frappe.local.message_log = [] pe.insert(ignore_permissions=True) pe.submit() + + if frappe.local.message_log: + msgs = _extract_messages(frappe.local.message_log) + frappe.local.message_log = [] + frappe.db.rollback() + return {"success": False, "error": msgs} + return {"success": True, "doc_name": pe.name} except Exception as e: + frappe.db.rollback() + frappe.local.message_log = [] frappe.log_error( f"_create_payment_entry_for_brt failed: {e}\n{frappe.get_traceback()}", "Kapital Bank Mapping", @@ -335,6 +360,9 @@ def _create_payment_entry_for_brt(txn, paid_from, paid_to, bank_txn): def _create_journal_entry_for_brt(txn, paid_from, paid_to, bank_txn, multi_currency=False): """Create and submit a Journal Entry for a BRT transaction.""" try: + import erpnext + from erpnext.setup.utils import get_exchange_rate + amount = abs(float(txn.get("amount") or 0)) party_type = (txn.get("party_type") or "").strip() party = (txn.get("party") or "").strip() @@ -342,9 +370,47 @@ def _create_journal_entry_for_brt(txn, paid_from, paid_to, bank_txn, multi_curre posting_date = bank_txn.date or txn.get("date") ref_no = bank_txn.reference_number or txn.get("reference_number") or "" - # Determine account types to assign party to the right row - paid_from_type = frappe.db.get_value("Account", paid_from, "account_type") or "" - paid_to_type = frappe.db.get_value("Account", paid_to, "account_type") or "" + company_currency = erpnext.get_company_currency(bank_txn.company) + + # Get account currencies and types + from_account = frappe.get_cached_value("Account", paid_from, ["account_type", "account_currency"], as_dict=True) or {} + to_account = frappe.get_cached_value("Account", paid_to, ["account_type", "account_currency"], as_dict=True) or {} + paid_from_type = from_account.get("account_type") or "" + paid_to_type = to_account.get("account_type") or "" + from_currency = from_account.get("account_currency") or company_currency + to_currency = to_account.get("account_currency") or company_currency + + # Auto-enable multi_currency if any account is in a foreign currency + is_multi = from_currency != company_currency or to_currency != company_currency + + # Frappe rounds debit_in_account_currency to field precision BEFORE multiplying + # by exchange_rate to get debit (company currency). + # Strategy: keep the original amount on the company-currency row, then compute + # an exact exchange rate for the foreign row so that: + # flt(foreign_amount * exact_rate, P) == flt(amount, P) + # This mirrors what Frappe UI does when the user types the AZN amount first. + currency_precision = cint(frappe.db.get_single_value("System Settings", "currency_precision") or 2) + + if from_currency == company_currency and to_currency != company_currency: + to_rate = get_exchange_rate(to_currency, company_currency, posting_date) + to_amount = flt(amount / to_rate, currency_precision) + exact_to_rate = (amount / to_amount) if to_amount else to_rate + from_rate = 1 + from_amount = amount + to_rate = exact_to_rate + elif to_currency == company_currency and from_currency != company_currency: + from_rate = get_exchange_rate(from_currency, company_currency, posting_date) + from_amount = flt(amount / from_rate, currency_precision) + exact_from_rate = (amount / from_amount) if from_amount else from_rate + to_rate = 1 + to_amount = amount + from_rate = exact_from_rate + else: + # Both same currency OR both foreign (rare) + from_rate = 1 if from_currency == company_currency else get_exchange_rate(from_currency, company_currency, posting_date) + to_rate = 1 if to_currency == company_currency else get_exchange_rate(to_currency, company_currency, posting_date) + from_amount = flt(amount / from_rate, currency_precision) + to_amount = flt(amount / to_rate, currency_precision) je = frappe.new_doc("Journal Entry") je.voucher_type = "Bank Entry" @@ -353,28 +419,38 @@ def _create_journal_entry_for_brt(txn, paid_from, paid_to, bank_txn, multi_curre je.cheque_no = ref_no je.cheque_date = posting_date je.user_remark = bank_txn.description or txn.get("purpose") or "" - if multi_currency: + if is_multi: je.multi_currency = 1 # Pay: paid_from credit, paid_to debit # Receive: paid_from debit, paid_to credit + # Only set *_in_account_currency + exchange_rate; Frappe computes debit/credit itself. if is_pay: - from_credit, from_debit = amount, 0 - to_credit, to_debit = 0, amount + from_row = { + "account": paid_from, + "exchange_rate": from_rate, + "credit_in_account_currency": from_amount, + "debit_in_account_currency": 0, + } + to_row = { + "account": paid_to, + "exchange_rate": to_rate, + "debit_in_account_currency": to_amount, + "credit_in_account_currency": 0, + } else: - from_credit, from_debit = 0, amount - to_credit, to_debit = amount, 0 - - from_row = { - "account": paid_from, - "credit_in_account_currency": from_credit, - "debit_in_account_currency": from_debit, - } - to_row = { - "account": paid_to, - "credit_in_account_currency": to_credit, - "debit_in_account_currency": to_debit, - } + from_row = { + "account": paid_from, + "exchange_rate": from_rate, + "debit_in_account_currency": from_amount, + "credit_in_account_currency": 0, + } + to_row = { + "account": paid_to, + "exchange_rate": to_rate, + "credit_in_account_currency": to_amount, + "debit_in_account_currency": 0, + } if party_type and party: if paid_from_type in ("Receivable", "Payable"): @@ -386,11 +462,22 @@ def _create_journal_entry_for_brt(txn, paid_from, paid_to, bank_txn, multi_curre je.append("accounts", from_row) je.append("accounts", to_row) + + frappe.local.message_log = [] je.insert(ignore_permissions=True) je.submit() + + if frappe.local.message_log: + msgs = _extract_messages(frappe.local.message_log) + frappe.local.message_log = [] + frappe.db.rollback() + return {"success": False, "error": msgs} + return {"success": True, "doc_name": je.name} except Exception as e: + frappe.db.rollback() + frappe.local.message_log = [] frappe.log_error( f"_create_journal_entry_for_brt failed: {e}\n{frappe.get_traceback()}", "Kapital Bank Mapping",