feat: case-insensitive party matching, with per-row override

Mirrors the same logic now in jey_erp.bank_integration. Party-name lookup at
Create & Reconcile gains a global 'Ignore Case in Party Matching' Check
(default ON) on Kapital Bank Settings, plus a 'Case Mode' Select on each
Customer / Supplier Mapping row that overrides the global per row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-05-13 16:25:49 +00:00
parent a6687da894
commit fd3f74d3ed
4 changed files with 59 additions and 6 deletions

View File

@ -10,6 +10,7 @@
"customer_group",
"territory",
"payment_terms",
"case_mode",
"mapping_type"
],
"fields": [
@ -55,6 +56,13 @@
"label": "Payment Terms",
"options": "Payment Terms Template"
},
{
"description": "Override the global 'Ignore Case in Party Matching' for this row. Blank = use the global setting.",
"fieldname": "case_mode",
"fieldtype": "Select",
"label": "Case Mode",
"options": "\nIgnore Case\nCase Sensitive"
},
{
"default": "Manual",
"fieldname": "mapping_type",

View File

@ -15,6 +15,7 @@
"similarity_threshold_suppliers",
"similarity_threshold_purpose",
"consider_azeri_chars",
"case_insensitive_party_match",
"default_party_settings_section",
"default_supplier_group",
"default_customer_group",
@ -119,6 +120,13 @@
"fieldtype": "Check",
"label": "Consider Azerbaijani Characters"
},
{
"default": "1",
"description": "When resolving a Bank Transaction's counterparty against the mappings, ignore case differences (e.g. 'Acme MMC' matches 'acme mmc'). Each row in Customer/Supplier Mappings can override this via 'Case Mode'.",
"fieldname": "case_insensitive_party_match",
"fieldtype": "Check",
"label": "Ignore Case in Party Matching"
},
{
"fieldname": "default_party_settings_section",
"fieldtype": "Section Break",

View File

@ -9,6 +9,7 @@
"erp_supplier",
"supplier_group",
"payment_terms",
"case_mode",
"mapping_type"
],
"fields": [
@ -47,6 +48,13 @@
"label": "Payment Terms",
"options": "Payment Terms Template"
},
{
"description": "Override the global 'Ignore Case in Party Matching' for this row. Blank = use the global setting.",
"fieldname": "case_mode",
"fieldtype": "Select",
"label": "Case Mode",
"options": "\nIgnore Case\nCase Sensitive"
},
{
"default": "Manual",
"fieldname": "mapping_type",

View File

@ -37,28 +37,57 @@ def _extract_messages(message_log):
return " | ".join(p for p in parts if p)
def _effective_case_insensitive(row, global_default):
"""Per-row Case Mode overrides the global Ignore Case flag."""
mode = (getattr(row, "case_mode", None) or "").strip()
if mode == "Ignore Case":
return True
if mode == "Case Sensitive":
return False
return global_default
def _build_name_to_party(settings):
"""Counterparty text -> {"Customer": erp_customer} / {"Supplier": erp_supplier}."""
"""Counterparty text -> {"Customer": erp_customer} / {"Supplier": erp_supplier}.
A row may be indexed under its strict key only, its lowercased key only, or
both depending on the row's Case Mode (which overrides the global
`case_insensitive_party_match` setting on Kapital Bank Settings).
"""
global_ci = bool(getattr(settings, "case_insensitive_party_match", 1))
idx = {}
def _add(name, party_type, erp_party, ci):
key = (name or "").strip()
if not key:
return
idx.setdefault(key, {})[party_type] = erp_party
if ci:
idx.setdefault(key.lower(), {})[party_type] = erp_party
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
_add(cust_name, "Customer", row.erp_customer, _effective_case_insensitive(row, global_ci))
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
_add(supp_name, "Supplier", row.erp_supplier, _effective_case_insensitive(row, global_ci))
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())
"""(party_type, erp_party) for `name`, or (None, None). Tries an exact (strip-only)
lookup first; falls back to lowercased lookup so rows indexed case-insensitively
can be reached from arbitrary Bank Transaction bank_party_name casing."""
key = (name or "").strip()
if not key:
return None, None
cands = name_to_party.get(key) or name_to_party.get(key.lower())
if not cands:
return None, None
preferred = "Customer" if payment_type == "Receive" else "Supplier"