Added create & reconcile to brt
This commit is contained in:
parent
bced451359
commit
1c002a5e87
|
|
@ -1,27 +1,31 @@
|
|||
// 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", {
|
||||
render: function (frm) {
|
||||
// DTM is created synchronously but its datatable is built after an async API call.
|
||||
// Poll until datatable is ready, then enhance once.
|
||||
if (frm._kb_check_interval) clearInterval(frm._kb_check_interval);
|
||||
// Only activate if at least one Kapital Bank Login exists
|
||||
frappe.db.count("Kapital Bank Login").then(function (count) {
|
||||
if (!count) return;
|
||||
|
||||
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);
|
||||
if (frm._kb_check_interval) clearInterval(frm._kb_check_interval);
|
||||
|
||||
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 = {
|
||||
enhance: function (frm, dtm) {
|
||||
dtm._kb_enhanced = true;
|
||||
dtm._kb_frm = frm;
|
||||
|
||||
// Rebuild DataTable with checkboxColumn: true
|
||||
dtm.datatable.destroy();
|
||||
|
|
@ -52,7 +56,7 @@ const KBBRTMapping = {
|
|||
border:1px solid var(--border-color); border-radius:6px;">
|
||||
<span class="kb-brt-selected-count text-muted"></span>
|
||||
<button class="btn btn-primary btn-sm kb-brt-create-mapping-btn">
|
||||
${__("Create Purpose Mapping")}
|
||||
${__("Create & Reconcile")}
|
||||
</button>
|
||||
</div>
|
||||
`);
|
||||
|
|
@ -90,7 +94,7 @@ const KBBRTMapping = {
|
|||
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 = [];
|
||||
let skippedCount = 0;
|
||||
checkedIndices.forEach(function (rowIndex) {
|
||||
|
|
@ -100,16 +104,22 @@ const KBBRTMapping = {
|
|||
if (!description) { skippedCount++; return; }
|
||||
const partyType = (row[1] || "").trim();
|
||||
const partyName = $("<div>").html(row[2] || "").text().trim();
|
||||
const deposit = parseFloat(row[4]) || 0;
|
||||
const withdrawal = parseFloat(row[5]) || 0;
|
||||
const bankTransactionName = $(row[8] || "").data("name") || "";
|
||||
txns.push({
|
||||
purpose: description,
|
||||
drcr: withdrawal > 0 ? "D" : "C",
|
||||
party_type: partyType,
|
||||
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();
|
||||
txns.forEach(function (txn) {
|
||||
const key = txn.purpose + "|" + txn.drcr;
|
||||
|
|
@ -157,9 +167,17 @@ const KBBRTMapping = {
|
|||
}
|
||||
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __("Create Purpose Mappings") + " (" + purposeMap.size + ")",
|
||||
title: __("Create & Reconcile") + " (" + purposeMap.size + ")",
|
||||
size: "large",
|
||||
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: "sec_options", fieldtype: "Section Break", label: __("Options") },
|
||||
{
|
||||
|
|
@ -199,29 +217,22 @@ const KBBRTMapping = {
|
|||
label: __("Paid To (Account)"), reqd: 1,
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Create Mappings"),
|
||||
primary_action_label: __("Create & Reconcile"),
|
||||
primary_action: function () {
|
||||
const values = d.get_values();
|
||||
if (!values) return;
|
||||
|
||||
let finalTxns = txns;
|
||||
let finalTxns = txns.slice();
|
||||
if (values.use_custom_purpose && (values.custom_purpose || "").trim()) {
|
||||
const cp = values.custom_purpose.trim();
|
||||
const seen = new Set();
|
||||
finalTxns = [];
|
||||
txns.forEach(function (t) {
|
||||
const key = cp + "|" + t.drcr;
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
finalTxns.push(Object.assign({}, t, { purpose: cp }));
|
||||
}
|
||||
});
|
||||
finalTxns = txns.map(t => 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();
|
||||
|
||||
// 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();
|
||||
frappe.call({
|
||||
method: "kapital_bank.mapping.create_purpose_mappings",
|
||||
|
|
@ -245,6 +256,7 @@ const KBBRTMapping = {
|
|||
paid_from: paid_from,
|
||||
paid_to: paid_to,
|
||||
document_type: document_type || "Payment Entry",
|
||||
mode: mode || "Both",
|
||||
},
|
||||
callback: function (r) {
|
||||
dialog.enable_primary_action();
|
||||
|
|
@ -256,13 +268,20 @@ const KBBRTMapping = {
|
|||
dialog.hide();
|
||||
const res = r.message;
|
||||
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.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({
|
||||
message: parts.join(" • ") || __("No changes"),
|
||||
indicator: res.created > 0 ? "green" : "blue",
|
||||
indicator: (res.created_mappings > 0 || res.created_docs > 0) ? "green" : "blue",
|
||||
}, 5);
|
||||
if (dialog._kb_frm) dialog._kb_frm.refresh();
|
||||
if (res.errors && res.errors.length) {
|
||||
KBBRTMapping.showErrors(res.errors);
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
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 =======
|
||||
frappe.listview_settings['Payment Entry'] = {
|
||||
onload(listview) {
|
||||
listview.page.add_inner_button(__('Import'), function() {
|
||||
KBImport.import.showDialog();
|
||||
}, __('Kapital Bank'));
|
||||
}
|
||||
};
|
||||
// frappe.listview_settings['Payment Entry'] = {
|
||||
// onload(listview) {
|
||||
// listview.page.add_inner_button(__('Import'), function() {
|
||||
// KBImport.import.showDialog();
|
||||
// }, __('Kapital Bank'));
|
||||
// }
|
||||
// };
|
||||
|
|
|
|||
|
|
@ -4,77 +4,113 @@ import frappe
|
|||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_purpose_mappings(transactions, paid_from, paid_to, document_type="Payment Entry"):
|
||||
"""Bulk-create transaction mappings from selected BRT transactions."""
|
||||
def create_purpose_mappings(transactions, paid_from, paid_to, document_type="Payment Entry", mode="Both"):
|
||||
"""Bulk-create transaction mappings and/or documents from selected BRT transactions."""
|
||||
try:
|
||||
txn_list = json.loads(transactions) if isinstance(transactions, str) else transactions
|
||||
|
||||
settings = frappe.get_single("Kapital Bank Settings")
|
||||
existing = {
|
||||
(row.purpose_keyword, row.payment_type)
|
||||
for row in settings.transaction_mappings
|
||||
if row.purpose_keyword and row.payment_type
|
||||
}
|
||||
do_mappings = mode in ("Both", "Mappings Only")
|
||||
do_documents = mode in ("Both", "Documents & Reconcile")
|
||||
|
||||
# Deduplicate within batch; first occurrence wins for party info
|
||||
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
|
||||
created_mappings = 0
|
||||
already_mapped = 0
|
||||
for (purpose_text, payment_type), data in unique_purposes.items():
|
||||
purpose_name = _ensure_purpose(purpose_text)
|
||||
skipped_no_data = 0
|
||||
created_docs = 0
|
||||
reconciled = 0
|
||||
errors = []
|
||||
|
||||
if (purpose_name, payment_type) in existing:
|
||||
already_mapped += 1
|
||||
continue
|
||||
if do_mappings:
|
||||
settings = frappe.get_single("Kapital Bank Settings")
|
||||
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", {
|
||||
"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 += 1
|
||||
# Deduplicate within batch; first occurrence wins for party info
|
||||
unique_purposes = {}
|
||||
|
||||
if created > 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"
|
||||
)
|
||||
if purpose_name:
|
||||
frappe.db.set_value(
|
||||
"Kapital Bank Purpose", purpose_name, "status", "Mapped",
|
||||
update_modified=False,
|
||||
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
|
||||
|
||||
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 {
|
||||
"success": True,
|
||||
"created": created,
|
||||
"skipped_no_data": skipped_no_data,
|
||||
"created_mappings": created_mappings,
|
||||
"already_mapped": already_mapped,
|
||||
"skipped_no_data": skipped_no_data,
|
||||
"created_docs": created_docs,
|
||||
"reconciled": reconciled,
|
||||
"errors": errors,
|
||||
}
|
||||
|
||||
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)}
|
||||
|
||||
|
||||
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):
|
||||
"""Return the name of an existing or newly created Kapital Bank Purpose."""
|
||||
existing = frappe.db.get_value(
|
||||
|
|
|
|||
Loading…
Reference in New Issue