148 lines
4.7 KiB
Python
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))
|