feat: resolve party at Create & Reconcile time from the mappings
A Bank Transaction created before its counterparty was mapped carries no party. When reconciling, look up the BT's bank_party_name in the customer/supplier mappings — if that counterparty is mapped to an ERPNext party, the txn gets it, so counterparty-based rules match and the created PE/JE carries the party. Also: only attach a party to a Payment Entry when one of the GL accounts is Receivable/Payable (otherwise ERPNext rejects it). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e589dcdaf4
commit
39c8733099
|
|
@ -37,6 +37,39 @@ def _extract_messages(message_log):
|
|||
return " | ".join(p for p in parts if p)
|
||||
|
||||
|
||||
def _build_name_to_party(settings):
|
||||
"""Counterparty text -> {"Customer": erp_customer} / {"Supplier": erp_supplier}."""
|
||||
idx = {}
|
||||
for row in settings.customer_mappings:
|
||||
if not row.erp_customer or not row.kb_customer_name:
|
||||
continue
|
||||
cust_name = frappe.db.get_value("Kapital Bank Customer", row.kb_customer_name, "customer_name")
|
||||
if cust_name:
|
||||
idx.setdefault(cust_name.strip(), {})["Customer"] = row.erp_customer
|
||||
for row in settings.supplier_mappings:
|
||||
if not row.erp_supplier or not row.kb_supplier_name:
|
||||
continue
|
||||
supp_name = frappe.db.get_value("Kapital Bank Supplier", row.kb_supplier_name, "supplier_name")
|
||||
if supp_name:
|
||||
idx.setdefault(supp_name.strip(), {})["Supplier"] = row.erp_supplier
|
||||
return idx
|
||||
|
||||
|
||||
def _resolve_party_from_name(name, name_to_party, payment_type):
|
||||
"""(party_type, erp_party) for `name`, or (None, None). Prefers Customer for
|
||||
incoming (Receive) and Supplier for outgoing (Pay), falling back to the other."""
|
||||
cands = name_to_party.get((name or "").strip())
|
||||
if not cands:
|
||||
return None, None
|
||||
preferred = "Customer" if payment_type == "Receive" else "Supplier"
|
||||
other = "Supplier" if preferred == "Customer" else "Customer"
|
||||
if preferred in cands:
|
||||
return preferred, cands[preferred]
|
||||
if other in cands:
|
||||
return other, cands[other]
|
||||
return None, None
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_purpose_mappings(transactions, paid_from=None, paid_to=None, document_type="Payment Entry", mode="Both"):
|
||||
"""Bulk-create transaction mappings and/or documents from selected BRT transactions."""
|
||||
|
|
@ -57,6 +90,20 @@ def create_purpose_mappings(transactions, paid_from=None, paid_to=None, document
|
|||
if needs_settings:
|
||||
settings = frappe.get_single("Kapital Bank Settings")
|
||||
|
||||
# A Bank Transaction created before its counterparty was mapped has no
|
||||
# party. Resolve it now, from the customer/supplier mappings, so both
|
||||
# counterparty-based rule matching and the created PE/JE pick it up.
|
||||
name_to_party = _build_name_to_party(settings)
|
||||
for txn in txn_list:
|
||||
if txn.get("party") or not txn.get("bank_transaction_name"):
|
||||
continue
|
||||
bp_name = frappe.db.get_value("Bank Transaction", txn["bank_transaction_name"], "bank_party_name")
|
||||
payment_type = "Pay" if txn.get("drcr") == "D" else "Receive"
|
||||
pt, ep = _resolve_party_from_name(bp_name, name_to_party, payment_type)
|
||||
if pt and ep:
|
||||
txn["party_type"] = pt
|
||||
txn["party"] = ep
|
||||
|
||||
if mode in ("Both", "Mappings Only"):
|
||||
existing_purpose = {
|
||||
(row.purpose_keyword, row.payment_type, (row.currency or "").upper())
|
||||
|
|
@ -459,7 +506,12 @@ def _create_payment_entry_for_brt(txn, paid_from, paid_to, bank_txn):
|
|||
pe.reference_no = bank_txn.reference_number or txn.get("reference_number") or ""
|
||||
pe.reference_date = posting_date
|
||||
pe.remarks = bank_txn.description or txn.get("purpose") or ""
|
||||
# Only attach a party if one of the GL accounts is a party account —
|
||||
# otherwise ERPNext rejects the PE ("Party Account ... is required").
|
||||
if party_type and party:
|
||||
pf_type = frappe.get_cached_value("Account", paid_from, "account_type") or ""
|
||||
pt_type = frappe.get_cached_value("Account", paid_to, "account_type") or ""
|
||||
if pf_type in ("Receivable", "Payable") or pt_type in ("Receivable", "Payable"):
|
||||
pe.party_type = party_type
|
||||
pe.party = party
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue