Added create & reconcile to brt
This commit is contained in:
parent
bced451359
commit
1c002a5e87
|
|
@ -1,27 +1,31 @@
|
||||||
// Kapital Bank — Bank Reconciliation Tool integration
|
// Kapital Bank — Bank Reconciliation Tool integration
|
||||||
// Adds checkbox column + "Create Purpose Mapping" toolbar
|
// Adds checkbox column + "Create & Reconcile" toolbar
|
||||||
|
|
||||||
frappe.ui.form.on("Bank Reconciliation Tool", {
|
frappe.ui.form.on("Bank Reconciliation Tool", {
|
||||||
render: function (frm) {
|
render: function (frm) {
|
||||||
// DTM is created synchronously but its datatable is built after an async API call.
|
// Only activate if at least one Kapital Bank Login exists
|
||||||
// Poll until datatable is ready, then enhance once.
|
frappe.db.count("Kapital Bank Login").then(function (count) {
|
||||||
if (frm._kb_check_interval) clearInterval(frm._kb_check_interval);
|
if (!count) return;
|
||||||
|
|
||||||
frm._kb_check_interval = setInterval(function () {
|
if (frm._kb_check_interval) clearInterval(frm._kb_check_interval);
|
||||||
const dtm = frm.bank_reconciliation_data_table_manager;
|
|
||||||
if (dtm && dtm.datatable && !dtm._kb_enhanced) {
|
|
||||||
clearInterval(frm._kb_check_interval);
|
|
||||||
KBBRTMapping.enhance(frm, dtm);
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
setTimeout(function () { clearInterval(frm._kb_check_interval); }, 15000);
|
frm._kb_check_interval = setInterval(function () {
|
||||||
|
const dtm = frm.bank_reconciliation_data_table_manager;
|
||||||
|
if (dtm && dtm.datatable && !dtm._kb_enhanced) {
|
||||||
|
clearInterval(frm._kb_check_interval);
|
||||||
|
KBBRTMapping.enhance(frm, dtm);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
setTimeout(function () { clearInterval(frm._kb_check_interval); }, 15000);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const KBBRTMapping = {
|
const KBBRTMapping = {
|
||||||
enhance: function (frm, dtm) {
|
enhance: function (frm, dtm) {
|
||||||
dtm._kb_enhanced = true;
|
dtm._kb_enhanced = true;
|
||||||
|
dtm._kb_frm = frm;
|
||||||
|
|
||||||
// Rebuild DataTable with checkboxColumn: true
|
// Rebuild DataTable with checkboxColumn: true
|
||||||
dtm.datatable.destroy();
|
dtm.datatable.destroy();
|
||||||
|
|
@ -52,7 +56,7 @@ const KBBRTMapping = {
|
||||||
border:1px solid var(--border-color); border-radius:6px;">
|
border:1px solid var(--border-color); border-radius:6px;">
|
||||||
<span class="kb-brt-selected-count text-muted"></span>
|
<span class="kb-brt-selected-count text-muted"></span>
|
||||||
<button class="btn btn-primary btn-sm kb-brt-create-mapping-btn">
|
<button class="btn btn-primary btn-sm kb-brt-create-mapping-btn">
|
||||||
${__("Create Purpose Mapping")}
|
${__("Create & Reconcile")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|
@ -90,7 +94,7 @@ const KBBRTMapping = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// dtm.transactions row: [date, party_type, party(HTML), description, deposit, withdrawal, ...]
|
// dtm.transactions row: [date, party_type, party(HTML), description, deposit, withdrawal, ..., reference_number, bank_transaction]
|
||||||
const txns = [];
|
const txns = [];
|
||||||
let skippedCount = 0;
|
let skippedCount = 0;
|
||||||
checkedIndices.forEach(function (rowIndex) {
|
checkedIndices.forEach(function (rowIndex) {
|
||||||
|
|
@ -100,16 +104,22 @@ const KBBRTMapping = {
|
||||||
if (!description) { skippedCount++; return; }
|
if (!description) { skippedCount++; return; }
|
||||||
const partyType = (row[1] || "").trim();
|
const partyType = (row[1] || "").trim();
|
||||||
const partyName = $("<div>").html(row[2] || "").text().trim();
|
const partyName = $("<div>").html(row[2] || "").text().trim();
|
||||||
|
const deposit = parseFloat(row[4]) || 0;
|
||||||
const withdrawal = parseFloat(row[5]) || 0;
|
const withdrawal = parseFloat(row[5]) || 0;
|
||||||
|
const bankTransactionName = $(row[8] || "").data("name") || "";
|
||||||
txns.push({
|
txns.push({
|
||||||
purpose: description,
|
purpose: description,
|
||||||
drcr: withdrawal > 0 ? "D" : "C",
|
drcr: withdrawal > 0 ? "D" : "C",
|
||||||
party_type: partyType,
|
party_type: partyType,
|
||||||
party: partyName,
|
party: partyName,
|
||||||
|
amount: deposit || withdrawal,
|
||||||
|
date: (row[0] || "").trim(),
|
||||||
|
reference_number: (row[7] || "").trim(),
|
||||||
|
bank_transaction_name: bankTransactionName,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Deduplicate for preview
|
// Deduplicate for preview only
|
||||||
const purposeMap = new Map();
|
const purposeMap = new Map();
|
||||||
txns.forEach(function (txn) {
|
txns.forEach(function (txn) {
|
||||||
const key = txn.purpose + "|" + txn.drcr;
|
const key = txn.purpose + "|" + txn.drcr;
|
||||||
|
|
@ -157,9 +167,17 @@ const KBBRTMapping = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const d = new frappe.ui.Dialog({
|
const d = new frappe.ui.Dialog({
|
||||||
title: __("Create Purpose Mappings") + " (" + purposeMap.size + ")",
|
title: __("Create & Reconcile") + " (" + purposeMap.size + ")",
|
||||||
size: "large",
|
size: "large",
|
||||||
fields: [
|
fields: [
|
||||||
|
{
|
||||||
|
fieldname: "mode",
|
||||||
|
fieldtype: "Select",
|
||||||
|
label: __("Action"),
|
||||||
|
options: "Both\nMappings Only\nDocuments & Reconcile",
|
||||||
|
default: "Both",
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
{ 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") },
|
||||||
{
|
{
|
||||||
|
|
@ -199,29 +217,22 @@ const KBBRTMapping = {
|
||||||
label: __("Paid To (Account)"), reqd: 1,
|
label: __("Paid To (Account)"), reqd: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
primary_action_label: __("Create Mappings"),
|
primary_action_label: __("Create & Reconcile"),
|
||||||
primary_action: function () {
|
primary_action: function () {
|
||||||
const values = d.get_values();
|
const values = d.get_values();
|
||||||
if (!values) return;
|
if (!values) return;
|
||||||
|
|
||||||
let finalTxns = txns;
|
let finalTxns = txns.slice();
|
||||||
if (values.use_custom_purpose && (values.custom_purpose || "").trim()) {
|
if (values.use_custom_purpose && (values.custom_purpose || "").trim()) {
|
||||||
const cp = values.custom_purpose.trim();
|
const cp = values.custom_purpose.trim();
|
||||||
const seen = new Set();
|
finalTxns = txns.map(t => Object.assign({}, t, { purpose: cp }));
|
||||||
finalTxns = [];
|
|
||||||
txns.forEach(function (t) {
|
|
||||||
const key = cp + "|" + t.drcr;
|
|
||||||
if (!seen.has(key)) {
|
|
||||||
seen.add(key);
|
|
||||||
finalTxns.push(Object.assign({}, t, { purpose: cp }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KBBRTMapping.submitMappings(d, finalTxns, values.paid_from, values.paid_to, values.document_type);
|
KBBRTMapping.submitMappings(d, finalTxns, values.paid_from, values.paid_to, values.document_type, values.mode);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
d._kb_frm = dtm._kb_frm;
|
||||||
d.show();
|
d.show();
|
||||||
|
|
||||||
// Pre-fill custom purpose from the Description inline filter if active
|
// Pre-fill custom purpose from the Description inline filter if active
|
||||||
|
|
@ -236,7 +247,7 @@ const KBBRTMapping = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
submitMappings: function (dialog, txns, paid_from, paid_to, document_type) {
|
submitMappings: function (dialog, txns, paid_from, paid_to, document_type, mode) {
|
||||||
dialog.disable_primary_action();
|
dialog.disable_primary_action();
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "kapital_bank.mapping.create_purpose_mappings",
|
method: "kapital_bank.mapping.create_purpose_mappings",
|
||||||
|
|
@ -245,6 +256,7 @@ const KBBRTMapping = {
|
||||||
paid_from: paid_from,
|
paid_from: paid_from,
|
||||||
paid_to: paid_to,
|
paid_to: paid_to,
|
||||||
document_type: document_type || "Payment Entry",
|
document_type: document_type || "Payment Entry",
|
||||||
|
mode: mode || "Both",
|
||||||
},
|
},
|
||||||
callback: function (r) {
|
callback: function (r) {
|
||||||
dialog.enable_primary_action();
|
dialog.enable_primary_action();
|
||||||
|
|
@ -256,13 +268,20 @@ const KBBRTMapping = {
|
||||||
dialog.hide();
|
dialog.hide();
|
||||||
const res = r.message;
|
const res = r.message;
|
||||||
const parts = [];
|
const parts = [];
|
||||||
if (res.created) parts.push(__("Created: {0}", [res.created]));
|
if (res.created_mappings) parts.push(__("Mappings created: {0}", [res.created_mappings]));
|
||||||
if (res.already_mapped) parts.push(__("Already mapped: {0}", [res.already_mapped]));
|
if (res.already_mapped) parts.push(__("Already mapped: {0}", [res.already_mapped]));
|
||||||
if (res.skipped_no_data) parts.push(__("Skipped (no purpose): {0}", [res.skipped_no_data]));
|
if (res.skipped_no_data) parts.push(__("Skipped (no purpose): {0}", [res.skipped_no_data]));
|
||||||
|
if (res.created_docs) parts.push(__("Documents created: {0}", [res.created_docs]));
|
||||||
|
if (res.reconciled) parts.push(__("Reconciled: {0}", [res.reconciled]));
|
||||||
|
if (res.errors && res.errors.length) parts.push(__("Errors: {0}", [res.errors.length]));
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: parts.join(" • ") || __("No changes"),
|
message: parts.join(" • ") || __("No changes"),
|
||||||
indicator: res.created > 0 ? "green" : "blue",
|
indicator: (res.created_mappings > 0 || res.created_docs > 0) ? "green" : "blue",
|
||||||
}, 5);
|
}, 5);
|
||||||
|
if (dialog._kb_frm) dialog._kb_frm.refresh();
|
||||||
|
if (res.errors && res.errors.length) {
|
||||||
|
KBBRTMapping.showErrors(res.errors);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error: function () {
|
error: function () {
|
||||||
dialog.enable_primary_action();
|
dialog.enable_primary_action();
|
||||||
|
|
@ -271,4 +290,28 @@ const KBBRTMapping = {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showErrors: function (errors) {
|
||||||
|
let html = '<div style="max-height:500px; overflow-y:auto;">';
|
||||||
|
errors.forEach(function (err) {
|
||||||
|
const ref = err.reference_number || __("Unknown");
|
||||||
|
html += '<div style="margin-bottom:14px; padding:12px; border:1px solid var(--border-color); border-radius:6px; background:var(--bg-color);">';
|
||||||
|
html += '<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; padding-bottom:6px; border-bottom:1px solid var(--border-color);">';
|
||||||
|
html += '<strong>' + frappe.utils.escape_html(ref) + '</strong>';
|
||||||
|
html += '<span class="label label-danger">' + __("Error") + '</span>';
|
||||||
|
html += '</div>';
|
||||||
|
html += '<div style="color:var(--text-color);">' + frappe.utils.escape_html(err.message || __("Unknown error")) + '</div>';
|
||||||
|
html += '</div>';
|
||||||
|
});
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
const d = new frappe.ui.Dialog({
|
||||||
|
title: __("Errors ({0})", [errors.length]),
|
||||||
|
size: "large",
|
||||||
|
fields: [{ fieldname: "errors_html", fieldtype: "HTML", options: html }],
|
||||||
|
primary_action_label: __("Close"),
|
||||||
|
primary_action: function () { d.hide(); },
|
||||||
|
});
|
||||||
|
d.show();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -672,10 +672,10 @@ KBImport.import = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// ======= LIST VIEW SETUP =======
|
// ======= LIST VIEW SETUP =======
|
||||||
frappe.listview_settings['Payment Entry'] = {
|
// frappe.listview_settings['Payment Entry'] = {
|
||||||
onload(listview) {
|
// onload(listview) {
|
||||||
listview.page.add_inner_button(__('Import'), function() {
|
// listview.page.add_inner_button(__('Import'), function() {
|
||||||
KBImport.import.showDialog();
|
// KBImport.import.showDialog();
|
||||||
}, __('Kapital Bank'));
|
// }, __('Kapital Bank'));
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
|
||||||
|
|
@ -4,77 +4,113 @@ import frappe
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_purpose_mappings(transactions, paid_from, paid_to, document_type="Payment Entry"):
|
def create_purpose_mappings(transactions, paid_from, paid_to, document_type="Payment Entry", mode="Both"):
|
||||||
"""Bulk-create transaction mappings 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
|
||||||
|
|
||||||
settings = frappe.get_single("Kapital Bank Settings")
|
do_mappings = mode in ("Both", "Mappings Only")
|
||||||
existing = {
|
do_documents = mode in ("Both", "Documents & Reconcile")
|
||||||
(row.purpose_keyword, row.payment_type)
|
|
||||||
for row in settings.transaction_mappings
|
|
||||||
if row.purpose_keyword and row.payment_type
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deduplicate within batch; first occurrence wins for party info
|
created_mappings = 0
|
||||||
unique_purposes = {}
|
|
||||||
skipped_no_data = 0
|
|
||||||
|
|
||||||
for txn in txn_list:
|
|
||||||
purpose = (txn.get("purpose") or "").strip()
|
|
||||||
if not purpose:
|
|
||||||
skipped_no_data += 1
|
|
||||||
continue
|
|
||||||
payment_type = "Pay" if txn.get("drcr") == "D" else "Receive"
|
|
||||||
key = (purpose, payment_type)
|
|
||||||
if key not in unique_purposes:
|
|
||||||
unique_purposes[key] = {
|
|
||||||
"count": 0,
|
|
||||||
# party_type is already "Customer"/"Supplier" from ERPNext BRT
|
|
||||||
"counterparty_type": (txn.get("party_type") or "").strip(),
|
|
||||||
"counterparty": (txn.get("party") or "").strip(),
|
|
||||||
}
|
|
||||||
unique_purposes[key]["count"] += 1
|
|
||||||
|
|
||||||
created = 0
|
|
||||||
already_mapped = 0
|
already_mapped = 0
|
||||||
for (purpose_text, payment_type), data in unique_purposes.items():
|
skipped_no_data = 0
|
||||||
purpose_name = _ensure_purpose(purpose_text)
|
created_docs = 0
|
||||||
|
reconciled = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
if (purpose_name, payment_type) in existing:
|
if do_mappings:
|
||||||
already_mapped += 1
|
settings = frappe.get_single("Kapital Bank Settings")
|
||||||
continue
|
existing = {
|
||||||
|
(row.purpose_keyword, row.payment_type)
|
||||||
|
for row in settings.transaction_mappings
|
||||||
|
if row.purpose_keyword and row.payment_type
|
||||||
|
}
|
||||||
|
|
||||||
settings.append("transaction_mappings", {
|
# Deduplicate within batch; first occurrence wins for party info
|
||||||
"purpose_keyword": purpose_name,
|
unique_purposes = {}
|
||||||
"payment_type": payment_type,
|
|
||||||
"paid_from": paid_from,
|
|
||||||
"paid_to": paid_to,
|
|
||||||
"document_type": document_type,
|
|
||||||
"counterparty_type": data["counterparty_type"] or None,
|
|
||||||
"counterparty": data["counterparty"] or None,
|
|
||||||
})
|
|
||||||
existing.add((purpose_name, payment_type))
|
|
||||||
created += 1
|
|
||||||
|
|
||||||
if created > 0:
|
for txn in txn_list:
|
||||||
settings.save(ignore_permissions=True)
|
purpose = (txn.get("purpose") or "").strip()
|
||||||
for (purpose_text, _payment_type) in unique_purposes:
|
if not purpose:
|
||||||
purpose_name = frappe.db.get_value(
|
skipped_no_data += 1
|
||||||
"Kapital Bank Purpose", {"purpose_keyword": purpose_text}, "name"
|
continue
|
||||||
)
|
payment_type = "Pay" if txn.get("drcr") == "D" else "Receive"
|
||||||
if purpose_name:
|
key = (purpose, payment_type)
|
||||||
frappe.db.set_value(
|
if key not in unique_purposes:
|
||||||
"Kapital Bank Purpose", purpose_name, "status", "Mapped",
|
unique_purposes[key] = {
|
||||||
update_modified=False,
|
"count": 0,
|
||||||
|
# party_type is already "Customer"/"Supplier" from ERPNext BRT
|
||||||
|
"counterparty_type": (txn.get("party_type") or "").strip(),
|
||||||
|
"counterparty": (txn.get("party") or "").strip(),
|
||||||
|
}
|
||||||
|
unique_purposes[key]["count"] += 1
|
||||||
|
|
||||||
|
for (purpose_text, payment_type), data in unique_purposes.items():
|
||||||
|
purpose_name = _ensure_purpose(purpose_text)
|
||||||
|
|
||||||
|
if (purpose_name, payment_type) in existing:
|
||||||
|
already_mapped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
settings.append("transaction_mappings", {
|
||||||
|
"purpose_keyword": purpose_name,
|
||||||
|
"payment_type": payment_type,
|
||||||
|
"paid_from": paid_from,
|
||||||
|
"paid_to": paid_to,
|
||||||
|
"document_type": document_type,
|
||||||
|
"counterparty_type": data["counterparty_type"] or None,
|
||||||
|
"counterparty": data["counterparty"] or None,
|
||||||
|
})
|
||||||
|
existing.add((purpose_name, payment_type))
|
||||||
|
created_mappings += 1
|
||||||
|
|
||||||
|
if created_mappings > 0:
|
||||||
|
settings.save(ignore_permissions=True)
|
||||||
|
for (purpose_text, _payment_type) in unique_purposes:
|
||||||
|
purpose_name = frappe.db.get_value(
|
||||||
|
"Kapital Bank Purpose", {"purpose_keyword": purpose_text}, "name"
|
||||||
)
|
)
|
||||||
frappe.db.commit()
|
if purpose_name:
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Kapital Bank Purpose", purpose_name, "status", "Mapped",
|
||||||
|
update_modified=False,
|
||||||
|
)
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
if do_documents:
|
||||||
|
for txn in txn_list:
|
||||||
|
if not txn.get("bank_transaction_name"):
|
||||||
|
errors.append({
|
||||||
|
"reference_number": txn.get("reference_number") or "",
|
||||||
|
"message": "No bank transaction name",
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
result = _create_and_reconcile_doc(txn, paid_from, paid_to, document_type)
|
||||||
|
if result.get("success"):
|
||||||
|
created_docs += 1
|
||||||
|
if result.get("reconciled"):
|
||||||
|
reconciled += 1
|
||||||
|
elif result.get("error"):
|
||||||
|
errors.append({
|
||||||
|
"reference_number": txn.get("reference_number") or "",
|
||||||
|
"message": result.get("error"),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
errors.append({
|
||||||
|
"reference_number": txn.get("reference_number") or "",
|
||||||
|
"message": result.get("error") or "Unknown error",
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"created": created,
|
"created_mappings": created_mappings,
|
||||||
"skipped_no_data": skipped_no_data,
|
|
||||||
"already_mapped": already_mapped,
|
"already_mapped": already_mapped,
|
||||||
|
"skipped_no_data": skipped_no_data,
|
||||||
|
"created_docs": created_docs,
|
||||||
|
"reconciled": reconciled,
|
||||||
|
"errors": errors,
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -85,6 +121,149 @@ 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 _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."""
|
||||||
|
bank_transaction_name = txn.get("bank_transaction_name")
|
||||||
|
try:
|
||||||
|
bank_txn = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||||
|
|
||||||
|
if document_type == "Journal Entry":
|
||||||
|
result = _create_journal_entry_for_brt(txn, paid_from, paid_to, bank_txn)
|
||||||
|
else:
|
||||||
|
result = _create_payment_entry_for_brt(txn, paid_from, paid_to, bank_txn)
|
||||||
|
|
||||||
|
if not result.get("success"):
|
||||||
|
return result
|
||||||
|
|
||||||
|
doc_name = result["doc_name"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
|
||||||
|
reconcile_vouchers,
|
||||||
|
)
|
||||||
|
|
||||||
|
reconcile_vouchers(
|
||||||
|
bank_transaction_name,
|
||||||
|
json.dumps([{
|
||||||
|
"payment_doctype": document_type,
|
||||||
|
"payment_name": doc_name,
|
||||||
|
"amount": abs(float(txn.get("amount") or 0)),
|
||||||
|
}]),
|
||||||
|
)
|
||||||
|
return {"success": True, "doc_name": doc_name, "reconciled": True}
|
||||||
|
except Exception as re:
|
||||||
|
frappe.log_error(
|
||||||
|
f"reconcile_vouchers failed for {doc_name}: {re}\n{frappe.get_traceback()}",
|
||||||
|
"Kapital Bank Mapping",
|
||||||
|
)
|
||||||
|
return {"success": True, "doc_name": doc_name, "reconciled": False, "error": str(re)}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(
|
||||||
|
f"_create_and_reconcile_doc failed for {bank_transaction_name}: {e}\n{frappe.get_traceback()}",
|
||||||
|
"Kapital Bank Mapping",
|
||||||
|
)
|
||||||
|
return {"success": False, "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
def _create_payment_entry_for_brt(txn, paid_from, paid_to, bank_txn):
|
||||||
|
"""Create and submit a Payment Entry for a BRT transaction."""
|
||||||
|
try:
|
||||||
|
amount = abs(float(txn.get("amount") or 0))
|
||||||
|
party_type = (txn.get("party_type") or "").strip()
|
||||||
|
party = (txn.get("party") or "").strip()
|
||||||
|
posting_date = bank_txn.date or txn.get("date")
|
||||||
|
|
||||||
|
pe = frappe.new_doc("Payment Entry")
|
||||||
|
pe.payment_type = "Pay" if txn.get("drcr") == "D" else "Receive"
|
||||||
|
pe.company = bank_txn.company
|
||||||
|
pe.posting_date = posting_date
|
||||||
|
pe.paid_from = paid_from
|
||||||
|
pe.paid_to = paid_to
|
||||||
|
pe.paid_amount = amount
|
||||||
|
pe.received_amount = amount
|
||||||
|
pe.reference_no = bank_txn.reference_number or txn.get("reference_number") or ""
|
||||||
|
pe.reference_date = posting_date
|
||||||
|
pe.remarks = txn.get("purpose") or ""
|
||||||
|
if party_type and party:
|
||||||
|
pe.party_type = party_type
|
||||||
|
pe.party = party
|
||||||
|
pe.insert(ignore_permissions=True)
|
||||||
|
pe.submit()
|
||||||
|
return {"success": True, "doc_name": pe.name}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(
|
||||||
|
f"_create_payment_entry_for_brt failed: {e}\n{frappe.get_traceback()}",
|
||||||
|
"Kapital Bank Mapping",
|
||||||
|
)
|
||||||
|
return {"success": False, "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
def _create_journal_entry_for_brt(txn, paid_from, paid_to, bank_txn):
|
||||||
|
"""Create and submit a Journal Entry for a BRT transaction."""
|
||||||
|
try:
|
||||||
|
amount = abs(float(txn.get("amount") or 0))
|
||||||
|
party_type = (txn.get("party_type") or "").strip()
|
||||||
|
party = (txn.get("party") or "").strip()
|
||||||
|
is_pay = txn.get("drcr") == "D"
|
||||||
|
posting_date = bank_txn.date or txn.get("date")
|
||||||
|
ref_no = bank_txn.reference_number or txn.get("reference_number") or ""
|
||||||
|
|
||||||
|
# Determine account types to assign party to the right row
|
||||||
|
paid_from_type = frappe.db.get_value("Account", paid_from, "account_type") or ""
|
||||||
|
paid_to_type = frappe.db.get_value("Account", paid_to, "account_type") or ""
|
||||||
|
|
||||||
|
je = frappe.new_doc("Journal Entry")
|
||||||
|
je.voucher_type = "Bank Entry"
|
||||||
|
je.posting_date = posting_date
|
||||||
|
je.company = bank_txn.company
|
||||||
|
je.cheque_no = ref_no
|
||||||
|
je.cheque_date = posting_date
|
||||||
|
je.user_remark = txn.get("purpose") or ""
|
||||||
|
|
||||||
|
# Pay: paid_from credit, paid_to debit
|
||||||
|
# Receive: paid_from debit, paid_to credit
|
||||||
|
if is_pay:
|
||||||
|
from_credit, from_debit = amount, 0
|
||||||
|
to_credit, to_debit = 0, amount
|
||||||
|
else:
|
||||||
|
from_credit, from_debit = 0, amount
|
||||||
|
to_credit, to_debit = amount, 0
|
||||||
|
|
||||||
|
from_row = {
|
||||||
|
"account": paid_from,
|
||||||
|
"credit_in_account_currency": from_credit,
|
||||||
|
"debit_in_account_currency": from_debit,
|
||||||
|
}
|
||||||
|
to_row = {
|
||||||
|
"account": paid_to,
|
||||||
|
"credit_in_account_currency": to_credit,
|
||||||
|
"debit_in_account_currency": to_debit,
|
||||||
|
}
|
||||||
|
|
||||||
|
if party_type and party:
|
||||||
|
if paid_from_type in ("Receivable", "Payable"):
|
||||||
|
from_row["party_type"] = party_type
|
||||||
|
from_row["party"] = party
|
||||||
|
if paid_to_type in ("Receivable", "Payable"):
|
||||||
|
to_row["party_type"] = party_type
|
||||||
|
to_row["party"] = party
|
||||||
|
|
||||||
|
je.append("accounts", from_row)
|
||||||
|
je.append("accounts", to_row)
|
||||||
|
je.insert(ignore_permissions=True)
|
||||||
|
je.submit()
|
||||||
|
return {"success": True, "doc_name": je.name}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(
|
||||||
|
f"_create_journal_entry_for_brt failed: {e}\n{frappe.get_traceback()}",
|
||||||
|
"Kapital Bank Mapping",
|
||||||
|
)
|
||||||
|
return {"success": False, "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
def _ensure_purpose(purpose_text):
|
def _ensure_purpose(purpose_text):
|
||||||
"""Return the name of an existing or newly created Kapital Bank Purpose."""
|
"""Return the name of an existing or newly created Kapital Bank Purpose."""
|
||||||
existing = frappe.db.get_value(
|
existing = frappe.db.get_value(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue