jey_erp/jey_erp/bank_integration/matching.py

148 lines
4.7 KiB
Python

"""Auto-match Bank Integration registry rows to ERPNext Customer/Supplier/Bank Account."""
from difflib import SequenceMatcher
import frappe
from frappe.utils import flt
_AZERI_MAP = str.maketrans("ƏəÜüÖöĞğİıÇ窺", "EeUuOoGgIiCcSs")
def _normalize(text, consider_azeri=True):
if not text:
return ""
s = str(text)
if consider_azeri:
s = s.translate(_AZERI_MAP)
return " ".join(s.lower().split())
@frappe.whitelist()
def match_similar_customers(bank_integration):
"""Auto-match BI Customers to ERPNext Customers by VOEN or name similarity."""
bi = frappe.get_doc("Bank Statement Importer", bank_integration)
threshold = flt(bi.similarity_threshold_customers or 90) / 100.0
consider_azeri = bool(bi.consider_azeri_chars)
new_customers = frappe.get_all(
"Bank Integration Customer",
filters={"status": "New", "parent_bank_integration": bank_integration},
fields=["name", "customer_name", "tax_id"],
)
if not new_customers:
return {"success": True, "matched_count": 0, "total_processed": 0}
erp_customers = frappe.get_all("Customer", fields=["name", "customer_name", "tax_id"])
voen_index = {c.tax_id.strip(): c.name for c in erp_customers if c.tax_id}
existing_mappings = {row.bi_customer_name: row.erp_customer for row in bi.customer_mappings if row.bi_customer_name}
to_process = []
for c in new_customers:
if c.name in existing_mappings:
if not existing_mappings[c.name]:
to_process.append(c)
else:
to_process.append(c)
matched = 0
for cust in to_process:
best_party = None
if cust.tax_id and cust.tax_id.strip() in voen_index:
best_party = voen_index[cust.tax_id.strip()]
else:
kb_name = _normalize(cust.customer_name, consider_azeri)
best_score = 0
for c in erp_customers:
score = SequenceMatcher(None, kb_name, _normalize(c.customer_name, consider_azeri)).ratio()
if score > best_score:
best_score = score
best_party = c.name
if best_score < threshold:
best_party = None
if best_party:
_set_mapping_row(bi, "customer_mappings", "bi_customer_name", cust.name,
{"erp_customer": best_party, "tax_id": cust.tax_id or None, "mapping_type": "Automatic"})
frappe.db.set_value("Bank Integration Customer", cust.name, {
"mapped_customer": best_party,
"status": "Mapped",
}, update_modified=False)
matched += 1
if matched > 0:
bi.save(ignore_permissions=True)
frappe.db.commit()
return {"success": True, "matched_count": matched, "total_processed": len(to_process)}
@frappe.whitelist()
def match_similar_suppliers(bank_integration):
bi = frappe.get_doc("Bank Statement Importer", bank_integration)
threshold = flt(bi.similarity_threshold_suppliers or 80) / 100.0
consider_azeri = bool(bi.consider_azeri_chars)
new_suppliers = frappe.get_all(
"Bank Integration Supplier",
filters={"status": "New", "parent_bank_integration": bank_integration},
fields=["name", "supplier_name", "tax_id"],
)
if not new_suppliers:
return {"success": True, "matched_count": 0, "total_processed": 0}
erp_suppliers = frappe.get_all("Supplier", fields=["name", "supplier_name", "tax_id"])
voen_index = {s.tax_id.strip(): s.name for s in erp_suppliers if s.tax_id}
existing_mappings = {row.bi_supplier_name: row.erp_supplier for row in bi.supplier_mappings if row.bi_supplier_name}
to_process = []
for s in new_suppliers:
if s.name in existing_mappings:
if not existing_mappings[s.name]:
to_process.append(s)
else:
to_process.append(s)
matched = 0
for supp in to_process:
best_party = None
if supp.tax_id and supp.tax_id.strip() in voen_index:
best_party = voen_index[supp.tax_id.strip()]
else:
kb_name = _normalize(supp.supplier_name, consider_azeri)
best_score = 0
for s in erp_suppliers:
score = SequenceMatcher(None, kb_name, _normalize(s.supplier_name, consider_azeri)).ratio()
if score > best_score:
best_score = score
best_party = s.name
if best_score < threshold:
best_party = None
if best_party:
_set_mapping_row(bi, "supplier_mappings", "bi_supplier_name", supp.name,
{"erp_supplier": best_party, "tax_id": supp.tax_id or None, "mapping_type": "Automatic"})
frappe.db.set_value("Bank Integration Supplier", supp.name, {
"mapped_supplier": best_party,
"status": "Mapped",
}, update_modified=False)
matched += 1
if matched > 0:
bi.save(ignore_permissions=True)
frappe.db.commit()
return {"success": True, "matched_count": matched, "total_processed": len(to_process)}
def _set_mapping_row(bi, table_field, key_field, key_value, values):
"""Update an existing child row matching key_field=key_value, or append new."""
for row in bi.get(table_field):
if row.get(key_field) == key_value:
for k, v in values.items():
row.set(k, v)
return
bi.append(table_field, dict({key_field: key_value}, **values))