fixed documents & reconcile option
This commit is contained in:
parent
2f05dfe8d3
commit
7ced7d18a5
|
|
@ -179,6 +179,7 @@ const KBBRTMapping = {
|
||||||
options: "Both\nMappings Only\nDocuments & Reconcile",
|
options: "Both\nMappings Only\nDocuments & Reconcile",
|
||||||
default: "Both",
|
default: "Both",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
|
onchange: function () { KBBRTMapping._applyModeVisibility(d); },
|
||||||
},
|
},
|
||||||
{ fieldname: "preview_html", fieldtype: "HTML", options: previewHtml },
|
{ fieldname: "preview_html", fieldtype: "HTML", options: previewHtml },
|
||||||
{ fieldname: "sec_options", fieldtype: "Section Break", label: __("Options") },
|
{ fieldname: "sec_options", fieldtype: "Section Break", label: __("Options") },
|
||||||
|
|
@ -188,7 +189,6 @@ const KBBRTMapping = {
|
||||||
label: __("Document Type"),
|
label: __("Document Type"),
|
||||||
options: "Payment Entry\nJournal Entry",
|
options: "Payment Entry\nJournal Entry",
|
||||||
default: "Payment Entry",
|
default: "Payment Entry",
|
||||||
reqd: 1,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "use_original_purpose",
|
fieldname: "use_original_purpose",
|
||||||
|
|
@ -210,20 +210,32 @@ const KBBRTMapping = {
|
||||||
{ fieldname: "sec_accounts", fieldtype: "Section Break", label: __("GL Accounts") },
|
{ fieldname: "sec_accounts", fieldtype: "Section Break", label: __("GL Accounts") },
|
||||||
{
|
{
|
||||||
fieldname: "paid_from", fieldtype: "Link", options: "Account",
|
fieldname: "paid_from", fieldtype: "Link", options: "Account",
|
||||||
label: __("Paid From (Account)"), reqd: 1,
|
label: __("Paid From (Account)"),
|
||||||
},
|
},
|
||||||
{ fieldname: "col_break", fieldtype: "Column Break" },
|
{ fieldname: "col_break", fieldtype: "Column Break" },
|
||||||
{
|
{
|
||||||
fieldname: "paid_to", fieldtype: "Link", options: "Account",
|
fieldname: "paid_to", fieldtype: "Link", options: "Account",
|
||||||
label: __("Paid To (Account)"), reqd: 1,
|
label: __("Paid To (Account)"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
primary_action_label: __("Create & Reconcile"),
|
primary_action_label: __("Create & Reconcile"),
|
||||||
primary_action: function () {
|
primary_action: function () {
|
||||||
const values = d.get_values();
|
const isDR = d.get_value("mode") === "Documents & Reconcile";
|
||||||
|
const values = d.get_values(true);
|
||||||
if (!values) return;
|
if (!values) return;
|
||||||
|
if (!isDR) {
|
||||||
|
const missing = [];
|
||||||
|
if (!values.paid_from) missing.push(__("Paid From (Account)"));
|
||||||
|
if (!values.paid_to) missing.push(__("Paid To (Account)"));
|
||||||
|
if (missing.length) {
|
||||||
|
frappe.msgprint({ title: __("Missing Values"), indicator: "red",
|
||||||
|
message: __("Following fields are required:") + "<br>" + missing.join("<br>") });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let finalTxns = txns.slice();
|
let finalTxns = txns.slice();
|
||||||
|
if (values.mode !== "Documents & Reconcile") {
|
||||||
if (!values.use_original_purpose) {
|
if (!values.use_original_purpose) {
|
||||||
const cp = (values.custom_purpose || "").trim();
|
const cp = (values.custom_purpose || "").trim();
|
||||||
if (cp) {
|
if (cp) {
|
||||||
|
|
@ -240,6 +252,7 @@ const KBBRTMapping = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
KBBRTMapping.submitMappings(d, finalTxns, values.paid_from, values.paid_to, values.document_type, values.mode);
|
KBBRTMapping.submitMappings(d, finalTxns, values.paid_from, values.paid_to, values.document_type, values.mode);
|
||||||
},
|
},
|
||||||
|
|
@ -247,6 +260,7 @@ const KBBRTMapping = {
|
||||||
|
|
||||||
d._kb_frm = dtm._kb_frm;
|
d._kb_frm = dtm._kb_frm;
|
||||||
d.show();
|
d.show();
|
||||||
|
KBBRTMapping._applyModeVisibility(d);
|
||||||
|
|
||||||
// Pre-fill custom purpose from the Description inline filter if active
|
// Pre-fill custom purpose from the Description inline filter if active
|
||||||
const $fi = $(`.${dtm._kb_scopeClass} .dt-row-filter input.dt-filter[data-name="Description"]`);
|
const $fi = $(`.${dtm._kb_scopeClass} .dt-row-filter input.dt-filter[data-name="Description"]`);
|
||||||
|
|
@ -256,6 +270,14 @@ const KBBRTMapping = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_applyModeVisibility: function (d) {
|
||||||
|
const isDR = d.get_value("mode") === "Documents & Reconcile";
|
||||||
|
const toHide = ["preview_html", "sec_options", "document_type", "use_original_purpose",
|
||||||
|
"custom_purpose", "sec_accounts", "paid_from", "col_break", "paid_to"];
|
||||||
|
toHide.forEach(fn => d.set_df_property(fn, "hidden", isDR ? 1 : 0));
|
||||||
|
d.refresh_fields(toHide);
|
||||||
|
},
|
||||||
|
|
||||||
submitMappings: function (dialog, txns, paid_from, paid_to, document_type, mode) {
|
submitMappings: function (dialog, txns, paid_from, paid_to, document_type, mode) {
|
||||||
dialog.disable_primary_action();
|
dialog.disable_primary_action();
|
||||||
frappe.call({
|
frappe.call({
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import frappe
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_purpose_mappings(transactions, paid_from, paid_to, document_type="Payment Entry", mode="Both"):
|
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."""
|
"""Bulk-create transaction mappings and/or documents from selected BRT transactions."""
|
||||||
try:
|
try:
|
||||||
txn_list = json.loads(transactions) if isinstance(transactions, str) else transactions
|
txn_list = json.loads(transactions) if isinstance(transactions, str) else transactions
|
||||||
|
|
@ -19,8 +19,11 @@ def create_purpose_mappings(transactions, paid_from, paid_to, document_type="Pay
|
||||||
reconciled = 0
|
reconciled = 0
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
if do_mappings:
|
needs_settings = do_mappings or mode == "Documents & Reconcile"
|
||||||
|
if needs_settings:
|
||||||
settings = frappe.get_single("Kapital Bank Settings")
|
settings = frappe.get_single("Kapital Bank Settings")
|
||||||
|
|
||||||
|
if do_mappings:
|
||||||
existing_purpose = {
|
existing_purpose = {
|
||||||
(row.purpose_keyword, row.payment_type)
|
(row.purpose_keyword, row.payment_type)
|
||||||
for row in settings.transaction_mappings
|
for row in settings.transaction_mappings
|
||||||
|
|
@ -118,6 +121,17 @@ def create_purpose_mappings(transactions, paid_from, paid_to, document_type="Pay
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
if do_documents:
|
if do_documents:
|
||||||
|
txn_mappings = settings.transaction_mappings if mode == "Documents & Reconcile" else None
|
||||||
|
|
||||||
|
if mode == "Documents & Reconcile":
|
||||||
|
purpose_text_cache = {}
|
||||||
|
for row in settings.transaction_mappings:
|
||||||
|
if row.purpose_keyword and row.purpose_keyword not in purpose_text_cache:
|
||||||
|
text = frappe.db.get_value("Kapital Bank Purpose", row.purpose_keyword, "purpose_keyword") or ""
|
||||||
|
purpose_text_cache[row.purpose_keyword] = text.lower()
|
||||||
|
else:
|
||||||
|
purpose_text_cache = {}
|
||||||
|
|
||||||
for txn in txn_list:
|
for txn in txn_list:
|
||||||
if not txn.get("bank_transaction_name"):
|
if not txn.get("bank_transaction_name"):
|
||||||
errors.append({
|
errors.append({
|
||||||
|
|
@ -126,7 +140,23 @@ def create_purpose_mappings(transactions, paid_from, paid_to, document_type="Pay
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
result = _create_and_reconcile_doc(txn, paid_from, paid_to, document_type)
|
if mode == "Documents & Reconcile":
|
||||||
|
mapping_row = _find_mapping_for_txn(txn, txn_mappings, purpose_text_cache)
|
||||||
|
if not mapping_row:
|
||||||
|
errors.append({
|
||||||
|
"reference_number": txn.get("reference_number") or "",
|
||||||
|
"message": f"No mapping found for purpose: '{txn.get('purpose', '')!s:.60}', party: '{txn.get('party', '')}'",
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
txn_paid_from = mapping_row.paid_from
|
||||||
|
txn_paid_to = mapping_row.paid_to
|
||||||
|
txn_doc_type = mapping_row.document_type or "Payment Entry"
|
||||||
|
else:
|
||||||
|
txn_paid_from = paid_from
|
||||||
|
txn_paid_to = paid_to
|
||||||
|
txn_doc_type = document_type
|
||||||
|
|
||||||
|
result = _create_and_reconcile_doc(txn, txn_paid_from, txn_paid_to, txn_doc_type)
|
||||||
if result.get("success"):
|
if result.get("success"):
|
||||||
created_docs += 1
|
created_docs += 1
|
||||||
if result.get("reconciled"):
|
if result.get("reconciled"):
|
||||||
|
|
@ -160,6 +190,58 @@ def create_purpose_mappings(transactions, paid_from, paid_to, document_type="Pay
|
||||||
return {"success": False, "message": str(e)}
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
def _find_mapping_for_txn(txn, transaction_mappings, purpose_text_cache):
|
||||||
|
"""Find the best matching transaction mapping row for a BRT txn.
|
||||||
|
|
||||||
|
Priority: rows matching both purpose+counterparty > purpose-only > counterparty-only.
|
||||||
|
Returns the first matching row or None.
|
||||||
|
"""
|
||||||
|
payment_type = "Pay" if txn.get("drcr") == "D" else "Receive"
|
||||||
|
txn_purpose = (txn.get("purpose") or "").lower()
|
||||||
|
txn_party = (txn.get("party") or "").lower()
|
||||||
|
|
||||||
|
both_rules = []
|
||||||
|
purpose_only_rules = []
|
||||||
|
counterparty_only_rules = []
|
||||||
|
|
||||||
|
for row in transaction_mappings:
|
||||||
|
if not row.paid_from or not row.paid_to:
|
||||||
|
continue
|
||||||
|
if row.payment_type and row.payment_type != payment_type:
|
||||||
|
continue
|
||||||
|
|
||||||
|
has_purpose = bool(row.purpose_keyword)
|
||||||
|
has_counterparty = bool(row.counterparty)
|
||||||
|
|
||||||
|
if has_purpose and has_counterparty:
|
||||||
|
both_rules.append(row)
|
||||||
|
elif has_purpose:
|
||||||
|
purpose_only_rules.append(row)
|
||||||
|
elif has_counterparty:
|
||||||
|
counterparty_only_rules.append(row)
|
||||||
|
|
||||||
|
def _purpose_matches(row):
|
||||||
|
keyword_text = purpose_text_cache.get(row.purpose_keyword, "")
|
||||||
|
return keyword_text and keyword_text in txn_purpose
|
||||||
|
|
||||||
|
def _counterparty_matches(row):
|
||||||
|
return row.counterparty.lower() == txn_party
|
||||||
|
|
||||||
|
for row in both_rules:
|
||||||
|
if _purpose_matches(row) and _counterparty_matches(row):
|
||||||
|
return row
|
||||||
|
|
||||||
|
for row in purpose_only_rules:
|
||||||
|
if _purpose_matches(row):
|
||||||
|
return row
|
||||||
|
|
||||||
|
for row in counterparty_only_rules:
|
||||||
|
if _counterparty_matches(row):
|
||||||
|
return row
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _create_and_reconcile_doc(txn, paid_from, paid_to, document_type):
|
def _create_and_reconcile_doc(txn, paid_from, paid_to, document_type):
|
||||||
"""Create a Payment Entry or Journal Entry and reconcile it with the bank transaction."""
|
"""Create a Payment Entry or Journal Entry and reconcile it with the bank transaction."""
|
||||||
bank_transaction_name = txn.get("bank_transaction_name")
|
bank_transaction_name = txn.get("bank_transaction_name")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue