diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool_beta/bank_reconciliation_tool_beta.js b/erpnext/accounts/doctype/bank_reconciliation_tool_beta/bank_reconciliation_tool_beta.js index 301046c179b..2ff87bd674e 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool_beta/bank_reconciliation_tool_beta.js +++ b/erpnext/accounts/doctype/bank_reconciliation_tool_beta/bank_reconciliation_tool_beta.js @@ -26,7 +26,7 @@ frappe.ui.form.on("Bank Reconciliation Tool Beta", { onload: function (frm) { // Set default filter dates - let today = frappe.datetime.get_today() + let today = frappe.datetime.get_today(); frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1); frm.doc.bank_statement_to_date = today; }, @@ -91,27 +91,6 @@ frappe.ui.form.on("Bank Reconciliation Tool Beta", { ); }, - setup_empty_state: function(frm) { - frm.$reconciliation_area.empty(); - let empty_area = frm.$reconciliation_area.append(` -
-

- ${__("Set Filters and Get Bank Transactions")} -

-

${__("Or")}

-
- `).find(".bank-reco-beta-empty-state"); - - frappe.utils.add_custom_button( - __("Upload a Bank Statement"), - () => frm.events.route_to_bank_statement_import(frm), - "", - __("Upload a Bank Statement"), - "btn-primary", - $(empty_area), - ) - }, - route_to_bank_statement_import(frm) { frappe.open_in_new_tab = true; @@ -206,6 +185,27 @@ frappe.ui.form.on("Bank Reconciliation Tool Beta", { } }, + setup_empty_state: function(frm) { + frm.$reconciliation_area.empty(); + let empty_area = frm.$reconciliation_area.append(` +
+

+ ${__("Set Filters and Get Bank Transactions")} +

+

${__("Or")}

+
+ `).find(".bank-reco-beta-empty-state"); + + frappe.utils.add_custom_button( + __("Upload a Bank Statement"), + () => frm.events.route_to_bank_statement_import(frm), + "", + __("Upload a Bank Statement"), + "btn-primary", + $(empty_area), + ) + }, + render_summary: function(frm) { frm.get_field("reconciliation_tool_cards").$wrapper.empty(); diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool_beta/bank_reconciliation_tool_beta.json b/erpnext/accounts/doctype/bank_reconciliation_tool_beta/bank_reconciliation_tool_beta.json index 0fe074eb5ae..49ac705fc10 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool_beta/bank_reconciliation_tool_beta.json +++ b/erpnext/accounts/doctype/bank_reconciliation_tool_beta/bank_reconciliation_tool_beta.json @@ -59,6 +59,7 @@ "fieldtype": "Column Break" }, { + "depends_on": "bank_account", "fieldname": "account_opening_balance", "fieldtype": "Currency", "label": "Account Opening Balance", @@ -66,6 +67,7 @@ "read_only": 1 }, { + "depends_on": "bank_account", "fieldname": "bank_statement_closing_balance", "fieldtype": "Currency", "label": "Closing Balance", @@ -119,7 +121,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-08-24 15:29:55.140942", + "modified": "2023-08-31 16:18:36.244114", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Reconciliation Tool Beta", diff --git a/erpnext/public/js/bank-reconciliation-tool-beta.bundle.js b/erpnext/public/js/bank-reconciliation-tool-beta.bundle.js index 005e8f9fe42..35e82708426 100644 --- a/erpnext/public/js/bank-reconciliation-tool-beta.bundle.js +++ b/erpnext/public/js/bank-reconciliation-tool-beta.bundle.js @@ -1,3 +1,7 @@ import "./bank_reconciliation_tool_beta/panel_manager"; -import "./bank_reconciliation_tool_beta/actions_panel"; -import "./bank_reconciliation_tool_beta/summary_number_card"; \ No newline at end of file +import "./bank_reconciliation_tool_beta/summary_number_card"; + +import "./bank_reconciliation_tool_beta/actions_panel/actions_panel_manager"; +import "./bank_reconciliation_tool_beta/actions_panel/details_tab"; +import "./bank_reconciliation_tool_beta/actions_panel/match_tab"; +import "./bank_reconciliation_tool_beta/actions_panel/create_tab"; diff --git a/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel.js b/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel.js deleted file mode 100644 index c3f875a0a67..00000000000 --- a/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel.js +++ /dev/null @@ -1,1010 +0,0 @@ -frappe.provide("erpnext.accounts.bank_reconciliation"); - -erpnext.accounts.bank_reconciliation.ActionsPanel = class ActionsPanel { - constructor(opts) { - Object.assign(this, opts); - this.make(); - } - - make() { - this.init_actions_container(); - this.render_tabs(); - - // Default to last selected tab - this.$actions_container.find("#" + this.panel_manager.actions_tab).trigger("click"); - } - - init_actions_container() { - if (this.$wrapper.find(".actions-panel").length > 0) { - this.$actions_container = this.$wrapper.find(".actions-panel"); - this.$actions_container.empty(); - } else { - this.$actions_container = this.$wrapper.append(` -
- `).find(".actions-panel"); - } - - this.$actions_container.append(` -
- -
- -
- `); - } - - render_tabs() { - this.tabs_list_ul = this.$actions_container.find(".form-tabs"); - this.$tab_content = this.$actions_container.find(".tab-content"); - - ["Details", "Match Voucher", "Create Voucher"].forEach(tab => { - let tab_name = frappe.scrub(tab); - this.add_tab(tab_name, tab); - - let $tab_link = this.tabs_list_ul.find(`#${tab_name}-tab`); - $tab_link.on("click", () => { - if (tab == "Details") { - this.details_section(); - } else if (tab == "Match Voucher") { - this.render_match_section(); - } else { - this.create_section(); - } - }); - }); - } - - add_tab(tab_name, tab) { - this.tabs_list_ul.append(` - - `); - } - - details_section() { - this.$tab_content.empty(); - this.panel_manager.actions_tab = "details-tab"; - - this.details_field_group = new frappe.ui.FieldGroup({ - fields: this.get_detail_tab_fields(), - body: this.$tab_content, - card_layout: true, - }); - this.details_field_group.make(); - } - - create_section() { - this.$tab_content.empty(); - this.panel_manager.actions_tab = "create_voucher-tab"; - - this.create_field_group = new frappe.ui.FieldGroup({ - fields: this.get_create_tab_fields(), - body: this.$tab_content, - card_layout: true, - }); - this.create_field_group.make(); - } - - async render_match_section() { - this.$tab_content.empty(); - this.panel_manager.actions_tab = "match_voucher-tab"; - - this.match_field_group = new frappe.ui.FieldGroup({ - fields: this.get_match_tab_fields(), - body: this.$tab_content, - card_layout: true, - }); - this.match_field_group.make() - - this.summary_empty_state(); - await this.populate_matching_vouchers(); - } - - summary_empty_state() { - let summary_field = this.match_field_group.get_field("transaction_amount_summary").$wrapper; - summary_field.append( - `
-
` - ); - } - - async populate_matching_vouchers() { - let filter_fields = this.match_field_group.get_values(); - let document_types = Object.keys(filter_fields).filter(field => filter_fields[field] === 1); - - this.update_filters_in_state(document_types); - - let vouchers = await this.get_matching_vouchers(document_types); - this.render_data_table(vouchers); - - let transaction_amount = this.transaction.withdrawal || this.transaction.deposit; - this.render_transaction_amount_summary( - flt(transaction_amount), - flt(this.transaction.unallocated_amount), - this.transaction.currency, - ); - } - - update_filters_in_state(document_types) { - Object.keys(this.panel_manager.actions_filters).map((key) => { - let value = document_types.includes(key) ? 1 : 0; - this.panel_manager.actions_filters[key] = value; - }) - } - - render_data_table(vouchers) { - this.summary_data = {}; - this.match_params = {}; - let table_data = vouchers.map((row) => { - this.match_params[row.name] = { - "Reference No": row.reference_number_match || 0, - "Party": row.party_match || 0, - "Transaction Amount": row.amount_match || 0, - "Unallocated Amount": row.unallocated_amount_match || 0, - "Name in Description": row.name_in_desc_match || 0, - } - return [ - this.help_button(row.name), - row.doctype, - row.reference_date || row.posting_date, // Reference Date - format_currency(row.paid_amount, row.currency), - row.reference_no || '', - row.party || '', - row.name - ]; - }); - - const datatable_options = { - columns: this.get_data_table_columns(), - data: table_data, - dynamicRowHeight: true, - checkboxColumn: true, - inlineFilters: true, - }; - - - this.actions_table = new frappe.DataTable( - this.match_field_group.get_field("vouchers").$wrapper[0], - datatable_options - ); - - // Highlight first row - this.actions_table.style.setStyle( - ".dt-cell[data-row-index='0']", {backgroundColor: '#F4FAEE'} - ); - - this.bind_row_check_event(); - this.bind_help_button(); - } - - help_button(voucher_name) { - return ` -
- -
- `; - } - - bind_row_check_event() { - // Resistant to row removal on being out of view in datatable - $(this.actions_table.bodyScrollable).on("click", ".dt-cell__content input", (e) => { - let idx = $(e.currentTarget).closest(".dt-cell").data().rowIndex; - let voucher_row = this.actions_table.getRows()[idx]; - - this.check_data_table_row(voucher_row) - }) - } - - bind_help_button() { - var me = this; - $(this.actions_table.bodyScrollable).on("mouseenter", ".match-reasons-btn", (e) => { - let $btn = $(e.currentTarget); - let voucher_name = $btn.data().name; - $btn.popover({ - trigger: "manual", - placement: "top", - html: true, - content: () => { - return ` -
-
${__("Match Reasons")}
- ${me.get_match_reasons(voucher_name)} -
- `; - - } - }); - $btn.popover("toggle"); - }); - - $(this.actions_table.bodyScrollable).on("mouseleave", ".match-reasons-btn", (e) => { - let $btn = $(e.currentTarget); - $btn.popover("toggle"); - }); - } - - get_match_reasons(voucher_name) { - let reasons = this.match_params[voucher_name], html = ""; - for (let key in reasons) { - if (reasons[key]) { - html += `
${__(key)}
`; - } - } - return html || __("No Specific Match Reasons"); - - } - - check_data_table_row(row) { - if (!row) return; - - let id = row[1].content; - let value = this.get_amount_from_row(row); - - // If `id` in summary_data, remove it (row was unchecked), else add it - if (id in this.summary_data) { - delete this.summary_data[id]; - } else { - this.summary_data[id] = value; - } - - // Total of selected row amounts in summary_data - let total_allocated = Object.values(this.summary_data).reduce( - (a, b) => a + b, 0 - ); - - // Deduct allocated amount from transaction's unallocated amount - // to show the final effect on reconciling - let transaction_amount = this.transaction.withdrawal || this.transaction.deposit; - let unallocated = flt(this.transaction.unallocated_amount) - flt(total_allocated); - - this.render_transaction_amount_summary( - flt(transaction_amount), unallocated, this.transaction.currency, - ); - } - - render_transaction_amount_summary(total_amount, unallocated_amount, currency) { - let summary_field = this.match_field_group.get_field("transaction_amount_summary").$wrapper; - summary_field.empty(); - - let allocated_amount = flt(total_amount) - flt(unallocated_amount); - - new erpnext.accounts.bank_reconciliation.SummaryCard({ - $wrapper: summary_field, - values: { - "Amount": [total_amount], - "Allocated Amount": [allocated_amount], - "To Allocate": [ - unallocated_amount, - (unallocated_amount < 0 ? "text-danger" : unallocated_amount > 0 ? "text-blue" : "text-success") - ] - }, - currency: currency, - wrapper_class: "reconciliation-summary" - }); - } - - async get_matching_vouchers(document_types) { - let vouchers = await frappe.call({ - method: - "erpnext.accounts.doctype.bank_reconciliation_tool_beta.bank_reconciliation_tool_beta.get_linked_payments", - args: { - bank_transaction_name: this.transaction.name, - document_types: document_types, - from_date: this.doc.bank_statement_from_date, - to_date: this.doc.bank_statement_to_date, - filter_by_reference_date: this.doc.filter_by_reference_date, - from_reference_date: this.doc.from_reference_date, - to_reference_date: this.doc.to_reference_date - }, - }).then(result => result.message); - return vouchers || []; - } - - update_bank_transaction() { - var me = this; - const reference_number = this.details_field_group.get_value("reference_number"); - const party = this.details_field_group.get_value("party"); - const party_type = this.details_field_group.get_value("party_type"); - - let diff = ["reference_number", "party", "party_type"].some(field => { - return me.details_field_group.get_value(field) !== me.transaction[field]; - }); - if (!diff) { - frappe.show_alert({message: __("No changes to update"), indicator: "yellow"}); - return; - } - - frappe.call({ - method: - "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.update_bank_transaction", - args: { - bank_transaction_name: me.transaction.name, - reference_number: reference_number, - party_type: party_type, - party: party, - }, - freeze: true, - freeze_message: __("Updating ..."), - callback: (response) => { - if (response.exc) { - frappe.show_alert(__("Failed to update {0}", [me.transaction.name])); - return; - } - - // Update transaction - me.panel_manager.refresh_transaction( - null, reference_number, party_type, party - ); - - frappe.show_alert( - __("Bank Transaction {0} updated", [me.transaction.name]) - ); - }, - }); - } - - reconcile_selected_vouchers() { - var me = this; - let selected_vouchers = []; - let selected_map = this.actions_table.rowmanager.checkMap; - let voucher_rows = this.actions_table.getRows(); - - selected_map.forEach((value, idx) => { - if (value === 1) { - let row = voucher_rows[idx]; - selected_vouchers.push({ - payment_doctype: row[3].content, - payment_name: row[8].content, - amount: this.get_amount_from_row(row), - }); - } - }); - - if (!selected_vouchers.length > 0) { - frappe.show_alert({ - message: __("Please select at least one voucher to reconcile"), - indicator: "red" - }); - return; - } - - frappe.call({ - method: - "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.reconcile_vouchers", - args: { - bank_transaction_name: this.transaction.name, - vouchers: selected_vouchers, - }, - freeze: true, - freeze_message: __("Reconciling ..."), - callback: (response) => { - if (response.exc) { - frappe.show_alert({ - message: __("Failed to reconcile {0}", [this.transaction.name]), - indicator: "red" - }); - return; - } - - me.after_transaction_reconcile(response.message, false); - }, - }); - } - - create_voucher() { - var me = this; - let values = this.create_field_group.get_values(); - let document_type = values.document_type; - - // Create new voucher and delete or refresh current BT row depending on reconciliation - this.create_voucher_bts( - null, - (message) => me.after_transaction_reconcile(message, true, document_type) - ) - } - - edit_in_full_page() { - this.create_voucher_bts(true, (message) => { - const doc = frappe.model.sync(message); - frappe.open_in_new_tab = true; - frappe.set_route("Form", doc[0].doctype, doc[0].name); - }); - } - - create_voucher_bts(allow_edit=false, success_callback) { - // Create PE or JV and run `success_callback` - let values = this.create_field_group.get_values(); - let document_type = values.document_type; - let method = "erpnext.accounts.doctype.bank_reconciliation_tool_beta.bank_reconciliation_tool_beta"; - let args = { - bank_transaction_name: this.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, - allow_edit: allow_edit - }; - - if (document_type === "Payment Entry") { - method = method + ".create_payment_entry_bts"; - args = { - ...args, - project: values.project, - cost_center: values.cost_center - } - } else { - method = method + ".create_journal_entry_bts"; - args = { - ...args, - entry_type: values.journal_entry_type, - second_account: values.second_account, - } - } - - frappe.call({ - method: method, - args: args, - callback: (response) => { - if (response.exc) { - frappe.show_alert({ - message: __("Failed to create {0} against {1}", [document_type, this.transaction.name]), - indicator: "red" - }); - return; - } else if (response.message) { - success_callback(response.message); - } - } - }) - - } - - after_transaction_reconcile(message, with_new_voucher=false, document_type) { - // Actions after a transaction is matched with a voucher - // `with_new_voucher`: If a new voucher was created and reconciled with the transaction - let doc = message; - let unallocated_amount = flt(doc.unallocated_amount); - if (unallocated_amount > 0) { - // if partial update this.transaction, re-click on list row - frappe.show_alert({ - message: __( - "Bank Transaction {0} Partially {1}", - [this.transaction.name, with_new_voucher ? "Reconciled" : "Matched"] - ), - indicator: "blue" - }); - this.panel_manager.refresh_transaction(unallocated_amount); - } else { - let alert_string = __("Bank Transaction {0} Matched", [this.transaction.name]) - if (with_new_voucher) { - alert_string = __("Bank Transaction {0} reconciled with a new {1}", [this.transaction.name, document_type]); - } - frappe.show_alert({message: alert_string, indicator: "green"}); - this.panel_manager.move_to_next_transaction(); - } - } - - get_amount_from_row(row) { - let value = row[5].content; - return flt(value.split(" ") ? value.split(" ")[1] : 0); - } - - get_match_tab_fields() { - const filters_state = this.panel_manager.actions_filters; - return [ - { - label: __("Payment Entry"), - fieldname: "payment_entry", - fieldtype: "Check", - default: filters_state.payment_entry, - onchange: () => { - this.populate_matching_vouchers(); - } - }, - { - label: __("Journal Entry"), - fieldname: "journal_entry", - fieldtype: "Check", - default: filters_state.journal_entry, - onchange: () => { - this.populate_matching_vouchers(); - } - }, - { - fieldtype: "Column Break" - }, - { - label: __("Purchase Invoice"), - fieldname: "purchase_invoice", - fieldtype: "Check", - default: filters_state.purchase_invoice, - onchange: () => { - let value = this.match_field_group.get_value("purchase_invoice"); - this.match_field_group.get_field("unpaid_invoices").df.hidden = !value; - this.match_field_group.refresh(); - - this.populate_matching_vouchers(); - } - }, - { - label: __("Sales Invoice"), - fieldname: "sales_invoice", - fieldtype: "Check", - default: filters_state.sales_invoice, - onchange: () => { - let value = this.match_field_group.get_value("sales_invoice"); - this.match_field_group.get_field("unpaid_invoices").df.hidden = !value; - this.match_field_group.refresh(); - - this.populate_matching_vouchers(); - } - }, - { - fieldtype: "Column Break" - }, - { - label: __("Loan Repayment"), - fieldname: "loan_repayment", - fieldtype: "Check", - default: filters_state.loan_repayment, - onchange: () => { - this.populate_matching_vouchers(); - } - }, - { - label: __("Loan Disbursement"), - fieldname: "loan_disbursement", - fieldtype: "Check", - default: filters_state.loan_disbursement, - onchange: () => { - this.populate_matching_vouchers(); - } - }, - { - fieldtype: "Column Break" - }, - { - label: __("Expense Claim"), - fieldname: "expense_claim", - fieldtype: "Check", - default: filters_state.expense_claim, - onchange: () => { - this.populate_matching_vouchers(); - } - }, - { - label: __("Bank Transaction"), - fieldname: "bank_transaction", - fieldtype: "Check", - default: filters_state.bank_transaction, - onchange: () => { - this.populate_matching_vouchers(); - } - }, - { - fieldtype: "Section Break" - }, - { - label: __("Show Exact Amount"), - fieldname: "exact_match", - fieldtype: "Check", - default: filters_state.exact_match, - onchange: () => { - this.populate_matching_vouchers(); - } - }, - { - fieldtype: "Column Break" - }, - { - label: __("Show Exact Party"), - fieldname: "exact_party_match", - fieldtype: "Check", - default: filters_state.exact_party_match, - onchange: () => { - this.populate_matching_vouchers(); - }, - read_only: !Boolean(this.transaction.party_type && this.transaction.party) - }, - { - fieldtype: "Column Break" - }, - { - label: __("Unpaid Invoices"), - fieldname: "unpaid_invoices", - fieldtype: "Check", - default: filters_state.unpaid_invoices, - onchange: () => { - this.populate_matching_vouchers(); - }, - hidden: (filters_state.sales_invoice || filters_state.purchase_invoice) ? 0 : 1 - }, - { - fieldtype: "Column Break" - }, - { - fieldtype: "Section Break" - }, - { - fieldname: "transaction_amount_summary", - fieldtype: "HTML", - }, - { - fieldname: "vouchers", - fieldtype: "HTML", - }, - { - fieldtype: "Section Break", - fieldname: "section_break_reconcile", - hide_border: 1, - }, - { - label: __("Hidden field for alignment"), - fieldname: "hidden_field_2", - fieldtype: "Data", - hidden: 1 - }, - { - fieldtype: "Column Break" - }, - { - label: __("Reconcile"), - fieldname: "bt_reconcile", - fieldtype: "Button", - primary: true, - click: () => { - this.reconcile_selected_vouchers(); - } - } - ]; - } - - get_detail_tab_fields() { - return [ - { - label: __("ID"), - fieldname: "name", - fieldtype: "Link", - options: "Bank Transaction", - default: this.transaction.name, - read_only: 1, - }, - { - label: __("Date"), - fieldname: "date", - fieldtype: "Date", - default: this.transaction.date, - read_only: 1, - }, - { - label: __("Deposit"), - fieldname: "deposit", - fieldtype: "Currency", - default: this.transaction.deposit, - read_only: 1, - }, - { - label: __("Withdrawal"), - fieldname: "withdrawal", - fieldtype: "Currency", - default: this.transaction.withdrawal, - read_only: 1, - }, - { - fieldtype: "Column Break" - }, - { - label: __("Description"), - fieldname: "description", - fieldtype: "Small Text", - default: this.transaction.description, - read_only: 1, - }, - { - label: __("To Allocate"), - fieldname: "unallocated_amount", - fieldtype: "Currency", - options: "account_currency", - default: this.transaction.unallocated_amount, - read_only: 1, - }, - { - label: __("Currency"), - fieldname: "account_currency", - fieldtype: "Link", - options: "Currency", - read_only: 1, - default: this.transaction.currency, - hidden: 1, - }, - { - label: __("Account Holder"), - fieldname: "account", - fieldtype: "Data", - default: this.transaction.bank_party_name, - read_only: 1, - hidden: this.transaction.bank_party_name ? 0 : 1, - }, - { - label: __("Party Account Number"), - fieldname: "account_number", - fieldtype: "Data", - default: this.transaction.bank_party_account_number, - read_only: 1, - hidden: this.transaction.bank_party_account_number ? 0 : 1, - }, - { - label: __("Party IBAN"), - fieldname: "iban", - fieldtype: "Data", - default: this.transaction.bank_party_iban, - read_only: 1, - hidden: this.transaction.bank_party_iban ? 0 : 1, - }, - { - label: __("Update"), - fieldtype: "Section Break", - fieldname: "update_section", - }, - { - label: __("Reference Number"), - fieldname: "reference_number", - fieldtype: "Data", - default: this.transaction.reference_number, - }, - { - fieldtype: "Column Break" - }, - { - label: __("Party Type"), - fieldname: "party_type", - fieldtype: "Link", - options: "DocType", - get_query: function () { - return { - filters: { - name: [ - "in", Object.keys(frappe.boot.party_account_types), - ], - }, - }; - }, - onchange: () => { - let value = this.details_field_group.get_value("party_type"); - this.details_field_group.get_field("party").df.options = value; - }, - default: this.transaction.party_type || null, - }, - { - label: __("Party"), - fieldname: "party", - fieldtype: "Link", - default: this.transaction.party, - options: this.transaction.party_type || null, - }, - { - fieldtype: "Section Break" - }, - { - label: __("Hidden field for alignment"), - fieldname: "hidden_field", - fieldtype: "Data", - hidden: 1 - }, - { - fieldtype: "Column Break" - }, - { - label: __("Submit"), - fieldname: "submit_transaction", - fieldtype: "Button", - primary: true, - click: () => this.update_bank_transaction(), - } - ]; - } - - get_create_tab_fields() { - let party_type = this.transaction.party_type || (flt(this.transaction.withdrawal) > 0 ? "Supplier" : "Customer"); - return [ - { - label: __("Document Type"), - fieldname: "document_type", - fieldtype: "Select", - options: `Payment Entry\nJournal Entry`, - default: "Payment Entry", - onchange: () => { - let value = this.create_field_group.get_value("document_type"); - let fields = this.create_field_group; - - fields.get_field("journal_entry_type").df.reqd = value === "Journal Entry"; - fields.get_field("second_account").df.reqd = value === "Journal Entry"; - - this.create_field_group.refresh(); - } - }, - { - fieldtype: "Section Break", - fieldname: "details", - label: "Details", - }, - { - fieldname: "reference_number", - fieldtype: "Data", - label: __("Reference Number"), - default: this.transaction.reference_number || this.transaction.description.slice(0, 140), - }, - { - fieldname: "posting_date", - fieldtype: "Date", - label: __("Posting Date"), - reqd: 1, - default: this.transaction.date, - }, - { - fieldname: "reference_date", - fieldtype: "Date", - label: __("Cheque/Reference Date"), - reqd: 1, - default: this.transaction.date, - }, - { - fieldname: "mode_of_payment", - fieldtype: "Link", - label: __("Mode of Payment"), - options: "Mode of Payment", - }, - { - fieldname: "edit_in_full_page", - fieldtype: "Button", - label: __("Edit in Full Page"), - click: () => { - this.edit_in_full_page(); - }, - }, - { - fieldname: "column_break_7", - fieldtype: "Column Break", - }, - { - label: __("Journal Entry Type"), - fieldname: "journal_entry_type", - fieldtype: "Select", - options: - `Bank Entry\nJournal Entry\nInter Company Journal 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`, - default: "Bank Entry", - depends_on: "eval: doc.document_type == 'Journal Entry'", - }, - { - fieldname: "second_account", - fieldtype: "Link", - label: "Account", - options: "Account", - get_query: () => { - return { - filters: { - is_group: 0, - company: this.doc.company, - }, - }; - }, - depends_on: "eval: doc.document_type == 'Journal Entry'", - }, - { - fieldname: "party_type", - fieldtype: "Link", - label: "Party Type", - options: "DocType", - reqd: 1, - default: party_type, - get_query: function () { - return { - filters: { - name: [ - "in", - Object.keys(frappe.boot.party_account_types), - ], - }, - }; - }, - onchange: () => { - let value = this.create_field_group.get_value("party_type"); - this.create_field_group.get_field("party").df.options = value; - } - }, - { - fieldname: "party", - fieldtype: "Link", - label: "Party", - default: this.transaction.party, - options: party_type, - reqd: 1, - }, - { - fieldname: "project", - fieldtype: "Link", - label: "Project", - options: "Project", - depends_on: "eval: doc.document_type == 'Payment Entry'", - }, - { - fieldname: "cost_center", - fieldtype: "Link", - label: "Cost Center", - options: "Cost Center", - depends_on: "eval: doc.document_type == 'Payment Entry'", - }, - { - fieldtype: "Section Break" - }, - { - label: __("Hidden field for alignment"), - fieldname: "hidden_field", - fieldtype: "Data", - hidden: 1 - }, - { - fieldtype: "Column Break" - }, - { - label: __("Create"), - fieldtype: "Button", - primary: true, - click: () => this.create_voucher(), - } - ]; - } - - get_data_table_columns() { - return [ - { - name: __("Reason"), - editable: false, - width: 50, - }, - { - name: __("Document Type"), - editable: false, - width: 100, - }, - { - name: __("Reference Date"), - editable: false, - width: 120, - }, - { - name: __("Remaining"), - editable: false, - width: 100, - }, - { - name: __("Reference Number"), - editable: false, - width: 200, - }, - { - name: __("Party"), - editable: false, - width: 100, - }, - { - name: __("Document Name"), - editable: false, - width: 100, - }, - ]; - } -} \ No newline at end of file diff --git a/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel/actions_panel_manager.js b/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel/actions_panel_manager.js new file mode 100644 index 00000000000..3ff125de6d2 --- /dev/null +++ b/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel/actions_panel_manager.js @@ -0,0 +1,111 @@ +frappe.provide("erpnext.accounts.bank_reconciliation"); + +erpnext.accounts.bank_reconciliation.ActionsPanelManager = class ActionsPanelManager { + constructor(opts) { + Object.assign(this, opts); + this.make(); + } + + make() { + this.init_actions_container(); + this.render_tabs(); + + // Default to last selected tab + this.$actions_container.find("#" + this.panel_manager.actions_tab).trigger("click"); + } + + init_actions_container() { + if (this.$wrapper.find(".actions-panel").length > 0) { + this.$actions_container = this.$wrapper.find(".actions-panel"); + this.$actions_container.empty(); + } else { + this.$actions_container = this.$wrapper.append(` +
+ `).find(".actions-panel"); + } + + this.$actions_container.append(` +
+ +
+ +
+ `); + } + + render_tabs() { + this.tabs_list_ul = this.$actions_container.find(".form-tabs"); + this.$tab_content = this.$actions_container.find(".tab-content"); + + ["Details", "Match Voucher", "Create Voucher"].forEach(tab => { + let tab_name = frappe.scrub(tab); + this.add_tab(tab_name, tab); + + let $tab_link = this.tabs_list_ul.find(`#${tab_name}-tab`); + $tab_link.on("click", () => { + this.$tab_content.empty(); + + if (tab == "Details") { + new erpnext.accounts.bank_reconciliation.DetailsTab({ + actions_panel: this, + transaction: this.transaction, + panel_manager: this.panel_manager, + }); + } else if (tab == "Match Voucher") { + new erpnext.accounts.bank_reconciliation.MatchTab({ + actions_panel: this, + transaction: this.transaction, + panel_manager: this.panel_manager, + doc: this.doc, + }); + } else { + new erpnext.accounts.bank_reconciliation.CreateTab({ + actions_panel: this, + transaction: this.transaction, + panel_manager: this.panel_manager, + company: this.doc.company, + }); + } + }); + }); + } + + add_tab(tab_name, tab) { + this.tabs_list_ul.append(` + + `); + } + + after_transaction_reconcile(message, with_new_voucher=false, document_type) { + // Actions after a transaction is matched with a voucher + // `with_new_voucher`: If a new voucher was created and reconciled with the transaction + let doc = message; + let unallocated_amount = flt(doc.unallocated_amount); + if (unallocated_amount > 0) { + // if partial update this.transaction, re-click on list row + frappe.show_alert({ + message: __( + "Bank Transaction {0} Partially {1}", + [this.transaction.name, with_new_voucher ? "Reconciled" : "Matched"] + ), + indicator: "blue" + }); + this.panel_manager.refresh_transaction(unallocated_amount); + } else { + let alert_string = __("Bank Transaction {0} Matched", [this.transaction.name]) + if (with_new_voucher) { + alert_string = __("Bank Transaction {0} reconciled with a new {1}", [this.transaction.name, document_type]); + } + frappe.show_alert({message: alert_string, indicator: "green"}); + this.panel_manager.move_to_next_transaction(); + } + } +} \ No newline at end of file diff --git a/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel/create_tab.js b/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel/create_tab.js new file mode 100644 index 00000000000..346120410bf --- /dev/null +++ b/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel/create_tab.js @@ -0,0 +1,242 @@ +frappe.provide("erpnext.accounts.bank_reconciliation"); + +erpnext.accounts.bank_reconciliation.CreateTab = class CreateTab { + constructor(opts) { + Object.assign(this, opts); + this.make(); + } + + make() { + this.panel_manager.actions_tab = "create_voucher-tab"; + + this.create_field_group = new frappe.ui.FieldGroup({ + fields: this.get_create_tab_fields(), + body: this.actions_panel.$tab_content, + card_layout: true, + }); + this.create_field_group.make(); + } + + create_voucher() { + var me = this; + let values = this.create_field_group.get_values(); + let document_type = values.document_type; + + // Create new voucher and delete or refresh current BT row depending on reconciliation + this.create_voucher_bts( + null, + (message) => me.actions_panel.after_transaction_reconcile( + message, true, document_type + ) + ) + } + + edit_in_full_page() { + this.create_voucher_bts(true, (message) => { + const doc = frappe.model.sync(message); + frappe.open_in_new_tab = true; + frappe.set_route("Form", doc[0].doctype, doc[0].name); + }); + } + + create_voucher_bts(allow_edit=false, success_callback) { + // Create PE or JV and run `success_callback` + let values = this.create_field_group.get_values(); + let document_type = values.document_type; + let method = "erpnext.accounts.doctype.bank_reconciliation_tool_beta.bank_reconciliation_tool_beta"; + let args = { + bank_transaction_name: this.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, + allow_edit: allow_edit + }; + + if (document_type === "Payment Entry") { + method = method + ".create_payment_entry_bts"; + args = { + ...args, + project: values.project, + cost_center: values.cost_center + } + } else { + method = method + ".create_journal_entry_bts"; + args = { + ...args, + entry_type: values.journal_entry_type, + second_account: values.second_account, + } + } + + frappe.call({ + method: method, + args: args, + callback: (response) => { + if (response.exc) { + frappe.show_alert({ + message: __("Failed to create {0} against {1}", [document_type, this.transaction.name]), + indicator: "red" + }); + return; + } else if (response.message) { + success_callback(response.message); + } + } + }) + + } + + get_create_tab_fields() { + let party_type = this.transaction.party_type || (flt(this.transaction.withdrawal) > 0 ? "Supplier" : "Customer"); + return [ + { + label: __("Document Type"), + fieldname: "document_type", + fieldtype: "Select", + options: `Payment Entry\nJournal Entry`, + default: "Payment Entry", + onchange: () => { + let value = this.create_field_group.get_value("document_type"); + let fields = this.create_field_group; + + fields.get_field("journal_entry_type").df.reqd = value === "Journal Entry"; + fields.get_field("second_account").df.reqd = value === "Journal Entry"; + + this.create_field_group.refresh(); + } + }, + { + fieldtype: "Section Break", + fieldname: "details", + label: "Details", + }, + { + fieldname: "reference_number", + fieldtype: "Data", + label: __("Reference Number"), + default: this.transaction.reference_number || this.transaction.description.slice(0, 140), + }, + { + fieldname: "posting_date", + fieldtype: "Date", + label: __("Posting Date"), + reqd: 1, + default: this.transaction.date, + }, + { + fieldname: "reference_date", + fieldtype: "Date", + label: __("Cheque/Reference Date"), + reqd: 1, + default: this.transaction.date, + }, + { + fieldname: "mode_of_payment", + fieldtype: "Link", + label: __("Mode of Payment"), + options: "Mode of Payment", + }, + { + fieldname: "edit_in_full_page", + fieldtype: "Button", + label: __("Edit in Full Page"), + click: () => { + this.edit_in_full_page(); + }, + }, + { + fieldname: "column_break_7", + fieldtype: "Column Break", + }, + { + label: __("Journal Entry Type"), + fieldname: "journal_entry_type", + fieldtype: "Select", + options: + `Bank Entry\nJournal Entry\nInter Company Journal 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`, + default: "Bank Entry", + depends_on: "eval: doc.document_type == 'Journal Entry'", + }, + { + fieldname: "second_account", + fieldtype: "Link", + label: "Account", + options: "Account", + get_query: () => { + return { + filters: { + is_group: 0, + company: this.company, + }, + }; + }, + depends_on: "eval: doc.document_type == 'Journal Entry'", + }, + { + fieldname: "party_type", + fieldtype: "Link", + label: "Party Type", + options: "DocType", + reqd: 1, + default: party_type, + get_query: function () { + return { + filters: { + name: [ + "in", + Object.keys(frappe.boot.party_account_types), + ], + }, + }; + }, + onchange: () => { + let value = this.create_field_group.get_value("party_type"); + this.create_field_group.get_field("party").df.options = value; + } + }, + { + fieldname: "party", + fieldtype: "Link", + label: "Party", + default: this.transaction.party, + options: party_type, + reqd: 1, + }, + { + fieldname: "project", + fieldtype: "Link", + label: "Project", + options: "Project", + depends_on: "eval: doc.document_type == 'Payment Entry'", + }, + { + fieldname: "cost_center", + fieldtype: "Link", + label: "Cost Center", + options: "Cost Center", + depends_on: "eval: doc.document_type == 'Payment Entry'", + }, + { + fieldtype: "Section Break" + }, + { + label: __("Hidden field for alignment"), + fieldname: "hidden_field", + fieldtype: "Data", + hidden: 1 + }, + { + fieldtype: "Column Break" + }, + { + label: __("Create"), + fieldtype: "Button", + primary: true, + click: () => this.create_voucher(), + } + ]; + } +} \ No newline at end of file diff --git a/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel/details_tab.js b/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel/details_tab.js new file mode 100644 index 00000000000..1ec4f2e59ee --- /dev/null +++ b/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel/details_tab.js @@ -0,0 +1,207 @@ +frappe.provide("erpnext.accounts.bank_reconciliation"); + +erpnext.accounts.bank_reconciliation.DetailsTab = class DetailsTab { + constructor(opts) { + $.extend(this, opts); + this.make(); + } + + make() { + this.panel_manager.actions_tab = "details-tab"; + + this.details_field_group = new frappe.ui.FieldGroup({ + fields: this.get_detail_tab_fields(), + body: this.actions_panel.$tab_content, + card_layout: true, + }); + this.details_field_group.make(); + } + + update_bank_transaction() { + var me = this; + const reference_number = this.details_field_group.get_value("reference_number"); + const party = this.details_field_group.get_value("party"); + const party_type = this.details_field_group.get_value("party_type"); + + let diff = ["reference_number", "party", "party_type"].some(field => { + return me.details_field_group.get_value(field) !== me.transaction[field]; + }); + if (!diff) { + frappe.show_alert({message: __("No changes to update"), indicator: "yellow"}); + return; + } + + frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.update_bank_transaction", + args: { + bank_transaction_name: me.transaction.name, + reference_number: reference_number, + party_type: party_type, + party: party, + }, + freeze: true, + freeze_message: __("Updating ..."), + callback: (response) => { + if (response.exc) { + frappe.show_alert(__("Failed to update {0}", [me.transaction.name])); + return; + } + + // Update transaction + me.panel_manager.refresh_transaction( + null, reference_number, party_type, party + ); + + frappe.show_alert( + __("Bank Transaction {0} updated", [me.transaction.name]) + ); + }, + }); + } + + get_detail_tab_fields() { + return [ + { + label: __("ID"), + fieldname: "name", + fieldtype: "Link", + options: "Bank Transaction", + default: this.transaction.name, + read_only: 1, + }, + { + label: __("Date"), + fieldname: "date", + fieldtype: "Date", + default: this.transaction.date, + read_only: 1, + }, + { + label: __("Deposit"), + fieldname: "deposit", + fieldtype: "Currency", + default: this.transaction.deposit, + read_only: 1, + }, + { + label: __("Withdrawal"), + fieldname: "withdrawal", + fieldtype: "Currency", + default: this.transaction.withdrawal, + read_only: 1, + }, + { + fieldtype: "Column Break" + }, + { + label: __("Description"), + fieldname: "description", + fieldtype: "Small Text", + default: this.transaction.description, + read_only: 1, + }, + { + label: __("To Allocate"), + fieldname: "unallocated_amount", + fieldtype: "Currency", + options: "account_currency", + default: this.transaction.unallocated_amount, + read_only: 1, + }, + { + label: __("Currency"), + fieldname: "account_currency", + fieldtype: "Link", + options: "Currency", + read_only: 1, + default: this.transaction.currency, + hidden: 1, + }, + { + label: __("Account Holder"), + fieldname: "account", + fieldtype: "Data", + default: this.transaction.bank_party_name, + read_only: 1, + hidden: this.transaction.bank_party_name ? 0 : 1, + }, + { + label: __("Party Account Number"), + fieldname: "account_number", + fieldtype: "Data", + default: this.transaction.bank_party_account_number, + read_only: 1, + hidden: this.transaction.bank_party_account_number ? 0 : 1, + }, + { + label: __("Party IBAN"), + fieldname: "iban", + fieldtype: "Data", + default: this.transaction.bank_party_iban, + read_only: 1, + hidden: this.transaction.bank_party_iban ? 0 : 1, + }, + { + label: __("Update"), + fieldtype: "Section Break", + fieldname: "update_section", + }, + { + label: __("Reference Number"), + fieldname: "reference_number", + fieldtype: "Data", + default: this.transaction.reference_number, + }, + { + fieldtype: "Column Break" + }, + { + label: __("Party Type"), + fieldname: "party_type", + fieldtype: "Link", + options: "DocType", + get_query: function () { + return { + filters: { + name: [ + "in", Object.keys(frappe.boot.party_account_types), + ], + }, + }; + }, + onchange: () => { + let value = this.details_field_group.get_value("party_type"); + this.details_field_group.get_field("party").df.options = value; + }, + default: this.transaction.party_type || null, + }, + { + label: __("Party"), + fieldname: "party", + fieldtype: "Link", + default: this.transaction.party, + options: this.transaction.party_type || null, + }, + { + fieldtype: "Section Break" + }, + { + label: __("Hidden field for alignment"), + fieldname: "hidden_field", + fieldtype: "Data", + hidden: 1 + }, + { + fieldtype: "Column Break" + }, + { + label: __("Submit"), + fieldname: "submit_transaction", + fieldtype: "Button", + primary: true, + click: () => this.update_bank_transaction(), + } + ]; + } +} \ No newline at end of file diff --git a/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel/match_tab.js b/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel/match_tab.js new file mode 100644 index 00000000000..ff7166dace2 --- /dev/null +++ b/erpnext/public/js/bank_reconciliation_tool_beta/actions_panel/match_tab.js @@ -0,0 +1,480 @@ +frappe.provide("erpnext.accounts.bank_reconciliation"); + +erpnext.accounts.bank_reconciliation.MatchTab = class MatchTab { + constructor(opts) { + $.extend(this, opts); + this.make(); + } + + async make() { + this.panel_manager.actions_tab = "match_voucher-tab"; + + this.match_field_group = new frappe.ui.FieldGroup({ + fields: this.get_match_tab_fields(), + body: this.actions_panel.$tab_content, + card_layout: true, + }); + this.match_field_group.make() + + this.summary_empty_state(); + await this.populate_matching_vouchers(); + } + + summary_empty_state() { + let summary_field = this.match_field_group.get_field("transaction_amount_summary").$wrapper; + summary_field.append( + `
+
` + ); + } + + async populate_matching_vouchers() { + let filter_fields = this.match_field_group.get_values(); + let document_types = Object.keys(filter_fields).filter(field => filter_fields[field] === 1); + + this.update_filters_in_state(document_types); + + let vouchers = await this.get_matching_vouchers(document_types); + this.render_data_table(vouchers); + + let transaction_amount = this.transaction.withdrawal || this.transaction.deposit; + this.render_transaction_amount_summary( + flt(transaction_amount), + flt(this.transaction.unallocated_amount), + this.transaction.currency, + ); + } + + update_filters_in_state(document_types) { + Object.keys(this.panel_manager.actions_filters).map((key) => { + let value = document_types.includes(key) ? 1 : 0; + this.panel_manager.actions_filters[key] = value; + }) + } + + async get_matching_vouchers(document_types) { + let vouchers = await frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool_beta.bank_reconciliation_tool_beta.get_linked_payments", + args: { + bank_transaction_name: this.transaction.name, + document_types: document_types, + from_date: this.doc.bank_statement_from_date, + to_date: this.doc.bank_statement_to_date, + filter_by_reference_date: this.doc.filter_by_reference_date, + from_reference_date: this.doc.from_reference_date, + to_reference_date: this.doc.to_reference_date + }, + }).then(result => result.message); + return vouchers || []; + } + + render_data_table(vouchers) { + this.summary_data = {}; + this.match_params = {}; + let table_data = vouchers.map((row) => { + this.match_params[row.name] = { + "Reference No": row.reference_number_match || 0, + "Party": row.party_match || 0, + "Transaction Amount": row.amount_match || 0, + "Unallocated Amount": row.unallocated_amount_match || 0, + "Name in Description": row.name_in_desc_match || 0, + } + return [ + this.help_button(row.name), + row.doctype, + row.reference_date || row.posting_date, // Reference Date + format_currency(row.paid_amount, row.currency), + row.reference_no || '', + row.party || '', + row.name + ]; + }); + + const datatable_options = { + columns: this.get_data_table_columns(), + data: table_data, + dynamicRowHeight: true, + checkboxColumn: true, + inlineFilters: true, + }; + + + this.actions_table = new frappe.DataTable( + this.match_field_group.get_field("vouchers").$wrapper[0], + datatable_options + ); + + // Highlight first row + this.actions_table.style.setStyle( + ".dt-cell[data-row-index='0']", {backgroundColor: '#F4FAEE'} + ); + + this.bind_row_check_event(); + this.bind_help_button(); + } + + help_button(voucher_name) { + return ` +
+ +
+ `; + } + + bind_row_check_event() { + // Resistant to row removal on being out of view in datatable + $(this.actions_table.bodyScrollable).on("click", ".dt-cell__content input", (e) => { + let idx = $(e.currentTarget).closest(".dt-cell").data().rowIndex; + let voucher_row = this.actions_table.getRows()[idx]; + + this.check_data_table_row(voucher_row) + }) + } + + bind_help_button() { + var me = this; + $(this.actions_table.bodyScrollable).on("mouseenter", ".match-reasons-btn", (e) => { + let $btn = $(e.currentTarget); + let voucher_name = $btn.data().name; + $btn.popover({ + trigger: "manual", + placement: "top", + html: true, + content: () => { + return ` +
+
${__("Match Reasons")}
+ ${me.get_match_reasons(voucher_name)} +
+ `; + + } + }); + $btn.popover("toggle"); + }); + + $(this.actions_table.bodyScrollable).on("mouseleave", ".match-reasons-btn", (e) => { + let $btn = $(e.currentTarget); + $btn.popover("toggle"); + }); + } + + get_match_reasons(voucher_name) { + let reasons = this.match_params[voucher_name], html = ""; + for (let key in reasons) { + if (reasons[key]) { + html += `
${__(key)}
`; + } + } + return html || __("No Specific Match Reasons"); + } + + check_data_table_row(row) { + if (!row) return; + + let id = row[1].content; + let value = this.get_amount_from_row(row); + + // If `id` in summary_data, remove it (row was unchecked), else add it + if (id in this.summary_data) { + delete this.summary_data[id]; + } else { + this.summary_data[id] = value; + } + + // Total of selected row amounts in summary_data + let total_allocated = Object.values(this.summary_data).reduce( + (a, b) => a + b, 0 + ); + + // Deduct allocated amount from transaction's unallocated amount + // to show the final effect on reconciling + let transaction_amount = this.transaction.withdrawal || this.transaction.deposit; + let unallocated = flt(this.transaction.unallocated_amount) - flt(total_allocated); + + this.render_transaction_amount_summary( + flt(transaction_amount), unallocated, this.transaction.currency, + ); + } + + render_transaction_amount_summary(total_amount, unallocated_amount, currency) { + let summary_field = this.match_field_group.get_field("transaction_amount_summary").$wrapper; + summary_field.empty(); + + let allocated_amount = flt(total_amount) - flt(unallocated_amount); + + new erpnext.accounts.bank_reconciliation.SummaryCard({ + $wrapper: summary_field, + values: { + "Amount": [total_amount], + "Allocated Amount": [allocated_amount], + "To Allocate": [ + unallocated_amount, + (unallocated_amount < 0 ? "text-danger" : unallocated_amount > 0 ? "text-blue" : "text-success") + ] + }, + currency: currency, + wrapper_class: "reconciliation-summary" + }); + } + + reconcile_selected_vouchers() { + var me = this; + let selected_vouchers = []; + let selected_map = this.actions_table.rowmanager.checkMap; + let voucher_rows = this.actions_table.getRows(); + + selected_map.forEach((value, idx) => { + if (value === 1) { + let row = voucher_rows[idx]; + selected_vouchers.push({ + payment_doctype: row[3].content, + payment_name: row[8].content, + amount: this.get_amount_from_row(row), + }); + } + }); + + if (!selected_vouchers.length > 0) { + frappe.show_alert({ + message: __("Please select at least one voucher to reconcile"), + indicator: "red" + }); + return; + } + + frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.reconcile_vouchers", + args: { + bank_transaction_name: this.transaction.name, + vouchers: selected_vouchers, + }, + freeze: true, + freeze_message: __("Reconciling ..."), + callback: (response) => { + if (response.exc) { + frappe.show_alert({ + message: __("Failed to reconcile {0}", [this.transaction.name]), + indicator: "red" + }); + return; + } + + me.actions_panel.after_transaction_reconcile(response.message, false); + }, + }); + } + + get_match_tab_fields() { + const filters_state = this.panel_manager.actions_filters; + return [ + { + label: __("Payment Entry"), + fieldname: "payment_entry", + fieldtype: "Check", + default: filters_state.payment_entry, + onchange: () => { + this.populate_matching_vouchers(); + } + }, + { + label: __("Journal Entry"), + fieldname: "journal_entry", + fieldtype: "Check", + default: filters_state.journal_entry, + onchange: () => { + this.populate_matching_vouchers(); + } + }, + { + fieldtype: "Column Break" + }, + { + label: __("Purchase Invoice"), + fieldname: "purchase_invoice", + fieldtype: "Check", + default: filters_state.purchase_invoice, + onchange: () => { + this.populate_matching_vouchers(); + } + }, + { + label: __("Sales Invoice"), + fieldname: "sales_invoice", + fieldtype: "Check", + default: filters_state.sales_invoice, + onchange: () => { + this.populate_matching_vouchers(); + } + }, + { + fieldtype: "Column Break" + }, + { + label: __("Loan Repayment"), + fieldname: "loan_repayment", + fieldtype: "Check", + default: filters_state.loan_repayment, + onchange: () => { + this.populate_matching_vouchers(); + } + }, + { + label: __("Loan Disbursement"), + fieldname: "loan_disbursement", + fieldtype: "Check", + default: filters_state.loan_disbursement, + onchange: () => { + this.populate_matching_vouchers(); + } + }, + { + fieldtype: "Column Break" + }, + { + label: __("Expense Claim"), + fieldname: "expense_claim", + fieldtype: "Check", + default: filters_state.expense_claim, + onchange: () => { + this.populate_matching_vouchers(); + } + }, + { + label: __("Bank Transaction"), + fieldname: "bank_transaction", + fieldtype: "Check", + default: filters_state.bank_transaction, + onchange: () => { + this.populate_matching_vouchers(); + } + }, + { + fieldtype: "Section Break" + }, + { + label: __("Show Exact Amount"), + fieldname: "exact_match", + fieldtype: "Check", + default: filters_state.exact_match, + onchange: () => { + this.populate_matching_vouchers(); + } + }, + { + fieldtype: "Column Break" + }, + { + label: __("Show Exact Party"), + fieldname: "exact_party_match", + fieldtype: "Check", + default: filters_state.exact_party_match, + onchange: () => { + this.populate_matching_vouchers(); + }, + read_only: !Boolean(this.transaction.party_type && this.transaction.party) + }, + { + fieldtype: "Column Break" + }, + { + label: __("Unpaid Invoices"), + fieldname: "unpaid_invoices", + fieldtype: "Check", + default: filters_state.unpaid_invoices, + onchange: () => { + this.populate_matching_vouchers(); + }, + depends_on: "eval: doc.sales_invoice || doc.purchase_invoice", + }, + { + fieldtype: "Column Break" + }, + { + fieldtype: "Section Break" + }, + { + fieldname: "transaction_amount_summary", + fieldtype: "HTML", + }, + { + fieldname: "vouchers", + fieldtype: "HTML", + }, + { + fieldtype: "Section Break", + fieldname: "section_break_reconcile", + hide_border: 1, + }, + { + label: __("Hidden field for alignment"), + fieldname: "hidden_field_2", + fieldtype: "Data", + hidden: 1 + }, + { + fieldtype: "Column Break" + }, + { + label: __("Reconcile"), + fieldname: "bt_reconcile", + fieldtype: "Button", + primary: true, + click: () => { + this.reconcile_selected_vouchers(); + } + } + ]; + } + + get_data_table_columns() { + return [ + { + name: __("Reason"), + editable: false, + width: 50, + }, + { + name: __("Document Type"), + editable: false, + width: 100, + }, + { + name: __("Reference Date"), + editable: false, + width: 120, + }, + { + name: __("Remaining"), + editable: false, + width: 100, + }, + { + name: __("Reference Number"), + editable: false, + width: 200, + }, + { + name: __("Party"), + editable: false, + width: 100, + }, + { + name: __("Document Name"), + editable: false, + width: 100, + }, + ]; + } + + get_amount_from_row(row) { + let value = row[5].content; + return flt(value.split(" ") ? value.split(" ")[1] : 0); + } +} \ No newline at end of file diff --git a/erpnext/public/js/bank_reconciliation_tool_beta/panel_manager.js b/erpnext/public/js/bank_reconciliation_tool_beta/panel_manager.js index bb762e6e1a0..fd517be8cab 100644 --- a/erpnext/public/js/bank_reconciliation_tool_beta/panel_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool_beta/panel_manager.js @@ -91,7 +91,7 @@ erpnext.accounts.bank_reconciliation.PanelManager = class PanelManager { } render_actions_panel() { - this.actions_panel = new erpnext.accounts.bank_reconciliation.ActionsPanel({ + this.actions_panel = new erpnext.accounts.bank_reconciliation.ActionsPanelManager({ $wrapper: this.$panel_wrapper, transaction: this.active_transaction, doc: this.doc, @@ -192,7 +192,7 @@ erpnext.accounts.bank_reconciliation.PanelManager = class PanelManager { } refresh_transaction(updated_amount=null, reference_number=null, party_type=null, party=null) { - // Update the transaction object's unallocated_amount **OR** other details + // Update the transaction object's & view's unallocated_amount **OR** other details let id = this.active_transaction.name; let current_index = this.transactions.findIndex(({name}) => name === id); @@ -218,6 +218,7 @@ erpnext.accounts.bank_reconciliation.PanelManager = class PanelManager { } move_to_next_transaction() { + // Remove the current transaction from the list and move to the next/previous one let id = this.active_transaction.name; let $current_transaction = this.$list_container.find("#" + id); let current_index = this.transactions.findIndex(({name}) => name === id); diff --git a/erpnext/public/js/bank_reconciliation_tool_beta/summary_number_card.js b/erpnext/public/js/bank_reconciliation_tool_beta/summary_number_card.js index 6a161f0895b..94a5b60b140 100644 --- a/erpnext/public/js/bank_reconciliation_tool_beta/summary_number_card.js +++ b/erpnext/public/js/bank_reconciliation_tool_beta/summary_number_card.js @@ -1,16 +1,17 @@ frappe.provide("erpnext.accounts.bank_reconciliation"); erpnext.accounts.bank_reconciliation.SummaryCard = class SummaryCard { - // { - // $wrapper : $wrapper, - // values: { - // "Amount": [120, "text-blue"], - // "Unallocated Amount": [200] - // }, - // wrapper_class: "custom-style", - // currency: "USD" - // } - + /** + * { + * $wrapper: $wrapper, + * values: { + * "Amount": [120, "text-blue"], + * "Unallocated Amount": [200] + * }, + * wrapper_class: "custom-style", + * currency: "USD" + * } + */ constructor(opts) { Object.assign(this, opts); this.make();