diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index cd99d38d051..70962042f77 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -25,8 +25,8 @@ class BankReconciliationTool(Document): @frappe.whitelist() def get_bank_transactions(bank_account, from_date=None, to_date=None): # returns bank transactions for a bank account - from_date = frappe.db.get_single_value('Bank Reconciliation Tool','bank_statement_from_date') - to_date = frappe.db.get_single_value('Bank Reconciliation Tool','bank_statement_to_date') + from_date = frappe.db.get_single_value("Bank Reconciliation Tool", "bank_statement_from_date") + to_date = frappe.db.get_single_value("Bank Reconciliation Tool", "bank_statement_to_date") filters = [] filters.append(["bank_account", "=", bank_account]) filters.append(["docstatus", "=", 1]) @@ -53,11 +53,10 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None): ], filters=filters, ) - transactions= sorted(transactions, key=lambda x: x['date']) if transactions else [] + transactions = sorted(transactions, key=lambda x: x["date"]) if transactions else [] return transactions - @frappe.whitelist() def get_account_balance(bank_account, till_date): # returns account balance till the specified date @@ -344,7 +343,9 @@ def get_linked_payments(bank_transaction_name, document_types=None): def check_matching(bank_account, company, transaction, document_types): # combine all types of vouchers - filtered_by_reference_date = frappe.db.get_single_value('Bank Reconciliation Tool','filtered_by_reference_date') + filtered_by_reference_date = frappe.db.get_single_value( + "Bank Reconciliation Tool", "filtered_by_reference_date" + ) subquery = get_queries(bank_account, company, transaction, document_types) filters = { "amount": transaction.unallocated_amount, @@ -366,9 +367,13 @@ def check_matching(bank_account, company, transaction, document_types): filters, ) ) - matching_vouchers_with_ref_no = tuple(ele for ele in matching_vouchers if frappe.as_json(ele[5]) != "null") + matching_vouchers_with_ref_no = tuple( + ele for ele in matching_vouchers if frappe.as_json(ele[5]) != "null" + ) if filtered_by_reference_date: - matching_vouchers = sorted(matching_vouchers_with_ref_no , key=lambda x: x[5]) if matching_vouchers else [] + matching_vouchers = ( + sorted(matching_vouchers_with_ref_no, key=lambda x: x[5]) if matching_vouchers else [] + ) else: matching_vouchers = sorted(matching_vouchers, key=lambda x: x[8]) if matching_vouchers else [] return matching_vouchers @@ -515,17 +520,21 @@ def get_lr_matching_query(bank_account, amount_condition, filters): def get_pe_matching_query(amount_condition, account_from_to, transaction): # get matching payment entries query - from_date = frappe.db.get_single_value('Bank Reconciliation Tool','bank_statement_from_date') - to_date = frappe.db.get_single_value('Bank Reconciliation Tool','bank_statement_to_date') - from_reference_date = frappe.db.get_single_value('Bank Reconciliation Tool','from_reference_date') - to_reference_date = frappe.db.get_single_value('Bank Reconciliation Tool','to_reference_date') - filtered_by_reference_date = frappe.db.get_single_value('Bank Reconciliation Tool','filtered_by_reference_date') + from_date = frappe.db.get_single_value("Bank Reconciliation Tool", "bank_statement_from_date") + to_date = frappe.db.get_single_value("Bank Reconciliation Tool", "bank_statement_to_date") + from_reference_date = frappe.db.get_single_value( + "Bank Reconciliation Tool", "from_reference_date" + ) + to_reference_date = frappe.db.get_single_value("Bank Reconciliation Tool", "to_reference_date") + filtered_by_reference_date = frappe.db.get_single_value( + "Bank Reconciliation Tool", "filtered_by_reference_date" + ) if transaction.deposit > 0: currency_field = "paid_to_account_currency as currency" else: currency_field = "paid_from_account_currency as currency" - if (filtered_by_reference_date): - pe_data= f""" + if filtered_by_reference_date: + pe_data = f""" SELECT (CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END + CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END @@ -548,11 +557,11 @@ def get_pe_matching_query(amount_condition, account_from_to, transaction): AND ifnull(clearance_date, '') = "" AND {account_from_to} = %(bank_account)s AND reference_date >= '{from_reference_date}' - AND reference_date <= '{to_reference_date}' - + AND reference_date <= '{to_reference_date}' + """ else: - pe_data= f""" + pe_data = f""" SELECT (CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END + CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END @@ -575,13 +584,11 @@ def get_pe_matching_query(amount_condition, account_from_to, transaction): AND ifnull(clearance_date, '') = "" AND {account_from_to} = %(bank_account)s AND posting_date >= '{from_date}' - AND posting_date <= '{to_date}' - + AND posting_date <= '{to_date}' + """ return pe_data - - def get_je_matching_query(amount_condition, transaction): # get matching journal entry query @@ -589,14 +596,18 @@ def get_je_matching_query(amount_condition, transaction): # We have mapping at the bank level # So one bank could have both types of bank accounts like asset and liability # So cr_or_dr should be judged only on basis of withdrawal and deposit and not account type - from_date = frappe.db.get_single_value('Bank Reconciliation Tool','bank_statement_from_date') - to_date = frappe.db.get_single_value('Bank Reconciliation Tool','bank_statement_to_date') - from_reference_date = frappe.db.get_single_value('Bank Reconciliation Tool','from_reference_date') - to_reference_date = frappe.db.get_single_value('Bank Reconciliation Tool','to_reference_date') - filtered_by_reference_date = frappe.db.get_single_value('Bank Reconciliation Tool','filtered_by_reference_date') + from_date = frappe.db.get_single_value("Bank Reconciliation Tool", "bank_statement_from_date") + to_date = frappe.db.get_single_value("Bank Reconciliation Tool", "bank_statement_to_date") + from_reference_date = frappe.db.get_single_value( + "Bank Reconciliation Tool", "from_reference_date" + ) + to_reference_date = frappe.db.get_single_value("Bank Reconciliation Tool", "to_reference_date") + filtered_by_reference_date = frappe.db.get_single_value( + "Bank Reconciliation Tool", "filtered_by_reference_date" + ) cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit" - if (filtered_by_reference_date==1): - je_data = f""" + if filtered_by_reference_date == 1: + je_data = f""" SELECT (CASE WHEN je.cheque_no=%(reference_no)s THEN 1 ELSE 0 END + 1) AS rank , @@ -624,7 +635,7 @@ def get_je_matching_query(amount_condition, transaction): AND je.cheque_date <= '{to_reference_date}' """ else: - je_data = f""" + je_data = f""" SELECT (CASE WHEN je.cheque_no=%(reference_no)s THEN 1 ELSE 0 END + 1) AS rank , @@ -653,6 +664,7 @@ def get_je_matching_query(amount_condition, transaction): """ return je_data + def get_si_matching_query(amount_condition): # get matchin sales invoice query return f""" diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js index bd003747dda..d7c0e61fc0a 100644 --- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js @@ -42,7 +42,583 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { }, }); } + frappe.provide("erpnext.accounts.bank_reconciliation"); + erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { + constructor(company, bank_account) { + this.bank_account = bank_account; + this.company = company; + this.make_dialog(); + } + + show_dialog(bank_transaction_name, update_dt_cards) { + this.bank_transaction_name = bank_transaction_name; + this.update_dt_cards = update_dt_cards; + frappe.call({ + method: "frappe.client.get_value", + args: { + doctype: "Bank Transaction", + filters: { name: this.bank_transaction_name }, + fieldname: [ + "date as reference_date", + "deposit", + "withdrawal", + "currency", + "description", + "name", + "bank_account", + "company", + "reference_number", + "party_type", + "party", + "unallocated_amount", + "allocated_amount", + ], + }, + callback: (r) => { + if (r.message) { + this.bank_transaction = r.message; + r.message.payment_entry = 1; + r.message.journal_entry = 1; + this.dialog.set_values(r.message); + this.dialog.show(); + } + }, + }); + } + + get_linked_vouchers(document_types) { + frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_linked_payments", + args: { + bank_transaction_name: this.bank_transaction_name, + document_types: document_types, + }, + + callback: (result) => { + const data = result.message; + + + if (data && data.length > 0) { + const proposals_wrapper = this.dialog.fields_dict.payment_proposals.$wrapper; + proposals_wrapper.show(); + this.dialog.fields_dict.no_matching_vouchers.$wrapper.hide(); + this.data = []; + data.forEach((row) => { + const reference_date = row[5] ? row[5] : row[8]; + this.data.push([ + row[1], + row[2], + reference_date, + row[8], + format_currency(row[3], row[9]), + row[6], + row[4], + ]); + }); + this.get_dt_columns(); + this.get_datatable(proposals_wrapper); + } else { + const proposals_wrapper = this.dialog.fields_dict.payment_proposals.$wrapper; + proposals_wrapper.hide(); + this.dialog.fields_dict.no_matching_vouchers.$wrapper.show(); + + } + this.dialog.show(); + }, + }); + } + + get_dt_columns() { + this.columns = [ + { + name: __("Document Type"), + editable: false, + width: 125, + }, + { + name: __("Document Name"), + editable: false, + width: 150, + }, + { + name: __("Reference Date"), + editable: false, + width: 120, + }, + { + name: "Posting Date", + editable: false, + width: 120, + }, + { + name: __("Amount"), + editable: false, + width: 100, + }, + { + name: __("Party"), + editable: false, + width: 120, + }, + + { + name: __("Reference Number"), + editable: false, + width: 140, + }, + ]; + } + + get_datatable(proposals_wrapper) { + if (!this.datatable) { + const datatable_options = { + columns: this.columns, + data: this.data, + dynamicRowHeight: true, + checkboxColumn: true, + inlineFilters: true, + }; + this.datatable = new frappe.DataTable( + proposals_wrapper.get(0), + datatable_options + ); + } else { + this.datatable.refresh(this.data, this.columns); + this.datatable.rowmanager.checkMap = []; + } + } + + make_dialog() { + const me = this; + me.selected_payment = null; + + const fields = [ + { + label: __("Action"), + fieldname: "action", + fieldtype: "Select", + options: `Match Against Voucher\nCreate Voucher\nUpdate Bank Transaction`, + default: "Match Against Voucher", + }, + { + fieldname: "column_break_4", + fieldtype: "Column Break", + }, + { + label: __("Document Type"), + fieldname: "document_type", + fieldtype: "Select", + options: `Payment Entry\nJournal Entry`, + default: "Payment Entry", + depends_on: "eval:doc.action=='Create Voucher'", + }, + { + fieldtype: "Section Break", + fieldname: "section_break_1", + label: __("Filters"), + depends_on: "eval:doc.action=='Match Against Voucher'", + }, + ]; + + frappe.call({ + method: "erpnext.accounts.doctype.bank_transaction.bank_transaction.get_doctypes_for_bank_reconciliation", + callback: (r) => { + $.each(r.message, (_i, entry) => { + if (_i % 3 == 0) { + fields.push({ + fieldtype: "Column Break", + }); + } + fields.push({ + fieldtype: "Check", + label: entry, + fieldname: frappe.scrub(entry), + onchange: () => this.update_options(), + }); + }); + + fields.push(...this.get_voucher_fields()); + + me.dialog = new frappe.ui.Dialog({ + title: __("Reconcile the Bank Transaction"), + fields: fields, + size: "large", + primary_action: (values) => + this.reconciliation_dialog_primary_action(values), + }); + } + }); + } + + get_voucher_fields() { + return [ + { + fieldtype: "Check", + label: "Show Only Exact Amount", + fieldname: "exact_match", + onchange: () => this.update_options(), + }, + { + fieldtype: "Section Break", + fieldname: "section_break_1", + label: __("Select Vouchers to Match"), + depends_on: "eval:doc.action=='Match Against Voucher'", + }, + { + fieldtype: "HTML", + fieldname: "payment_proposals", + }, + { + fieldtype: "HTML", + fieldname: "no_matching_vouchers", + options: __("
{0}
", [__("No Matching Vouchers Found")]) + }, + { + fieldtype: "Section Break", + fieldname: "details", + label: "Details", + depends_on: "eval:doc.action!='Match Against Voucher'", + }, + { + fieldname: "reference_number", + fieldtype: "Data", + label: "Reference Number", + mandatory_depends_on: "eval:doc.action=='Create Voucher'", + }, + { + default: "Today", + fieldname: "posting_date", + fieldtype: "Date", + label: "Posting Date", + reqd: 1, + depends_on: "eval:doc.action=='Create Voucher'", + }, + { + fieldname: "reference_date", + fieldtype: "Date", + label: "Cheque/Reference Date", + mandatory_depends_on: "eval:doc.action=='Create Voucher'", + depends_on: "eval:doc.action=='Create Voucher'", + reqd: 1, + }, + { + fieldname: "mode_of_payment", + fieldtype: "Link", + label: "Mode of Payment", + options: "Mode of Payment", + depends_on: "eval:doc.action=='Create Voucher'", + }, + { + fieldname: "edit_in_full_page", + fieldtype: "Button", + label: "Edit in Full Page", + click: () => { + this.edit_in_full_page(); + }, + depends_on: + "eval:doc.action=='Create Voucher'", + }, + { + fieldname: "column_break_7", + fieldtype: "Column Break", + }, + { + default: "Journal Entry Type", + fieldname: "journal_entry_type", + fieldtype: "Select", + label: "Journal Entry Type", + options: + "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nExchange Rate Revaluation\nDeferred Revenue\nDeferred Expense", + depends_on: + "eval:doc.action=='Create Voucher' && doc.document_type=='Journal Entry'", + mandatory_depends_on: + "eval:doc.action=='Create Voucher' && doc.document_type=='Journal Entry'", + }, + { + fieldname: "second_account", + fieldtype: "Link", + label: "Account", + options: "Account", + depends_on: + "eval:doc.action=='Create Voucher' && doc.document_type=='Journal Entry'", + mandatory_depends_on: + "eval:doc.action=='Create Voucher' && doc.document_type=='Journal Entry'", + get_query: () => { + return { + filters: { + is_group: 0, + company: this.company, + }, + }; + }, + }, + { + fieldname: "party_type", + fieldtype: "Link", + label: "Party Type", + options: "DocType", + mandatory_depends_on: + "eval:doc.action=='Create Voucher' && doc.document_type=='Payment Entry'", + get_query: function () { + return { + filters: { + name: [ + "in", + Object.keys(frappe.boot.party_account_types), + ], + }, + }; + }, + }, + { + fieldname: "party", + fieldtype: "Dynamic Link", + label: "Party", + options: "party_type", + mandatory_depends_on: + "eval:doc.action=='Create Voucher' && doc.document_type=='Payment Entry'", + }, + { + fieldname: "project", + fieldtype: "Link", + label: "Project", + options: "Project", + depends_on: + "eval:doc.action=='Create Voucher' && doc.document_type=='Payment Entry'", + }, + { + fieldname: "cost_center", + fieldtype: "Link", + label: "Cost Center", + options: "Cost Center", + depends_on: + "eval:doc.action=='Create Voucher' && doc.document_type=='Payment Entry'", + }, + { + fieldtype: "Section Break", + fieldname: "details_section", + label: "Transaction Details", + collapsible: 1, + }, + { + fieldname: "deposit", + fieldtype: "Currency", + label: "Deposit", + read_only: 1, + }, + { + fieldname: "withdrawal", + fieldtype: "Currency", + label: "Withdrawal", + read_only: 1, + }, + { + fieldname: "description", + fieldtype: "Small Text", + label: "Description", + read_only: 1, + }, + { + fieldname: "column_break_17", + fieldtype: "Column Break", + read_only: 1, + }, + { + fieldname: "allocated_amount", + fieldtype: "Currency", + label: "Allocated Amount", + read_only: 1, + }, + + { + fieldname: "unallocated_amount", + fieldtype: "Currency", + label: "Unallocated Amount", + read_only: 1, + }, + ]; + } + + get_selected_attributes() { + let selected_attributes = []; + this.dialog.$wrapper.find(".checkbox input").each((i, col) => { + if ($(col).is(":checked")) { + selected_attributes.push($(col).attr("data-fieldname")); + } + }); + + return selected_attributes; + } + + update_options() { + let selected_attributes = this.get_selected_attributes(); + this.get_linked_vouchers(selected_attributes); + } + + reconciliation_dialog_primary_action(values) { + if (values.action == "Match Against Voucher") this.match(values); + if ( + values.action == "Create Voucher" && + values.document_type == "Payment Entry" + ) + this.add_payment_entry(values); + if ( + values.action == "Create Voucher" && + values.document_type == "Journal Entry" + ) + this.add_journal_entry(values); + else if (values.action == "Update Bank Transaction") + this.update_transaction(values); + } + + match() { + var selected_map = this.datatable.rowmanager.checkMap; + let rows = []; + selected_map.forEach((val, index) => { + if (val == 1) rows.push(this.datatable.datamanager.rows[index]); + }); + let vouchers = []; + rows.forEach((x) => { + vouchers.push({ + payment_doctype: x[2].content, + payment_name: x[3].content, + amount: x[5].content, + }); + }); + frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.reconcile_vouchers", + args: { + bank_transaction_name: this.bank_transaction.name, + vouchers: vouchers, + }, + callback: (response) => { + const alert_string = __("Bank Transaction {0} Matched", [this.bank_transaction.name]); + frappe.show_alert(alert_string); + this.update_dt_cards(response.message); + this.dialog.hide(); + }, + }); + } + + add_payment_entry(values) { + frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_payment_entry_bts", + args: { + bank_transaction_name: this.bank_transaction.name, + reference_number: values.reference_number, + reference_date: values.reference_date, + party_type: values.party_type, + party: values.party, + posting_date: values.posting_date, + mode_of_payment: values.mode_of_payment, + project: values.project, + cost_center: values.cost_center, + }, + callback: (response) => { + const alert_string = __("Bank Transaction {0} added as Payment Entry", [this.bank_transaction.name]); + frappe.show_alert(alert_string); + this.update_dt_cards(response.message); + this.dialog.hide(); + }, + }); + } + + add_journal_entry(values) { + frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_journal_entry_bts", + args: { + bank_transaction_name: this.bank_transaction.name, + reference_number: values.reference_number, + reference_date: values.reference_date, + party_type: values.party_type, + party: values.party, + posting_date: values.posting_date, + mode_of_payment: values.mode_of_payment, + entry_type: values.journal_entry_type, + second_account: values.second_account, + }, + callback: (response) => { + const alert_string = __("Bank Transaction {0} added as Journal Entry", [this.bank_transaction.name]); + frappe.show_alert(alert_string); + this.update_dt_cards(response.message); + this.dialog.hide(); + }, + }); + } + + update_transaction(values) { + frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.update_bank_transaction", + args: { + bank_transaction_name: this.bank_transaction.name, + reference_number: values.reference_number, + party_type: values.party_type, + party: values.party, + }, + callback: (response) => { + const alert_string = __("Bank Transaction {0} updated", [this.bank_transaction.name]); + frappe.show_alert(alert_string); + this.update_dt_cards(response.message); + this.dialog.hide(); + }, + }); + } + + edit_in_full_page() { + const values = this.dialog.get_values(true); + if (values.document_type == "Payment Entry") { + frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_payment_entry_bts", + args: { + bank_transaction_name: this.bank_transaction.name, + reference_number: values.reference_number, + reference_date: values.reference_date, + party_type: values.party_type, + party: values.party, + posting_date: values.posting_date, + mode_of_payment: values.mode_of_payment, + project: values.project, + cost_center: values.cost_center, + allow_edit: true + }, + callback: (r) => { + const doc = frappe.model.sync(r.message); + frappe.set_route("Form", doc[0].doctype, doc[0].name); + }, + }); + } else { + frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_journal_entry_bts", + args: { + bank_transaction_name: this.bank_transaction.name, + reference_number: values.reference_number, + reference_date: values.reference_date, + party_type: values.party_type, + party: values.party, + posting_date: values.posting_date, + mode_of_payment: values.mode_of_payment, + entry_type: values.journal_entry_type, + second_account: values.second_account, + allow_edit: true + }, + callback: (r) => { + var doc = frappe.model.sync(r.message); + frappe.set_route("Form", doc[0].doctype, doc[0].name); + }, + }); + } + } + + }; + get_linked_vouchers(document_types) { frappe.call({ method: