diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 17:14:58 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 17:14:58 +0700 |
| commit | 1ca3b3df3421961caec3b747a364071c80f5c7da (patch) | |
| tree | 6778a1f0f3f9b4c6e26d6d87ccde16e24da6c9d6 /base_accounting_kit/static/src | |
| parent | b57188be371d36d96caac4b8d65a40745c0e972c (diff) | |
initial commit
Diffstat (limited to 'base_accounting_kit/static/src')
| -rw-r--r-- | base_accounting_kit/static/src/js/account_asset.js | 87 | ||||
| -rw-r--r-- | base_accounting_kit/static/src/js/account_dashboard.js | 1844 | ||||
| -rw-r--r-- | base_accounting_kit/static/src/js/payment_matching.js | 505 | ||||
| -rw-r--r-- | base_accounting_kit/static/src/js/payment_model.js | 1881 | ||||
| -rw-r--r-- | base_accounting_kit/static/src/js/payment_render.js | 929 | ||||
| -rw-r--r-- | base_accounting_kit/static/src/scss/account_asset.scss | 9 | ||||
| -rw-r--r-- | base_accounting_kit/static/src/scss/style.scss | 1164 | ||||
| -rw-r--r-- | base_accounting_kit/static/src/xml/payment_matching.xml | 402 | ||||
| -rw-r--r-- | base_accounting_kit/static/src/xml/template.xml | 324 |
9 files changed, 7145 insertions, 0 deletions
diff --git a/base_accounting_kit/static/src/js/account_asset.js b/base_accounting_kit/static/src/js/account_asset.js new file mode 100644 index 0000000..a60c8b6 --- /dev/null +++ b/base_accounting_kit/static/src/js/account_asset.js @@ -0,0 +1,87 @@ +odoo.define('base_accounting_kit.account_asset', function(require) { +"use strict"; + +/** + * The purpose of this widget is to shows a toggle button on depreciation and + * installment lines for posted/unposted line. When clicked, it calls the method + * create_move on the object account.asset.depreciation.line. + * Note that this widget can only work on the account.asset.depreciation.line + * model as some of its fields are harcoded. + */ + +var AbstractField = require('web.AbstractField'); +var core = require('web.core'); +var registry = require('web.field_registry'); + +var _t = core._t; + +var AccountAssetWidget = AbstractField.extend({ + events: _.extend({}, AbstractField.prototype.events, { + 'click': '_onClick', + }), + description: "", + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + isSet: function () { + return true; // it should always be displayed, whatever its value + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + _render: function () { + var className = ''; + var disabled = true; + var title; + if (this.recordData.move_posted_check) { + className = 'o_is_posted'; + title = _t('Posted'); + } else if (this.recordData.move_check) { + className = 'o_unposted'; + title = _t('Accounting entries waiting for manual verification'); + } else { + disabled = false; + title = _t('Unposted'); + } + var $button = $('<button/>', { + type: 'button', + title: title, + disabled: disabled, + }).addClass('btn btn-sm btn-link fa fa-circle o_deprec_lines_toggler ' + className); + this.$el.html($button); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {MouseEvent} event + */ + _onClick: function (event) { + event.stopPropagation(); + this.trigger_up('button_clicked', { + attrs: { + name: 'create_move', + type: 'object', + }, + record: this.record, + }); + }, +}); + +registry.add("deprec_lines_toggler", AccountAssetWidget); + +}); diff --git a/base_accounting_kit/static/src/js/account_dashboard.js b/base_accounting_kit/static/src/js/account_dashboard.js new file mode 100644 index 0000000..79f7b1a --- /dev/null +++ b/base_accounting_kit/static/src/js/account_dashboard.js @@ -0,0 +1,1844 @@ +odoo.define('AccountingDashboard.AccountingDashboard', function(require) { + 'use strict'; + var AbstractAction = require('web.AbstractAction'); + var ajax = require('web.ajax'); + var core = require('web.core'); + var rpc = require('web.rpc'); + var web_client = require('web.web_client'); + var _t = core._t; + var QWeb = core.qweb; + var self = this; + var currency; + var ActionMenu = AbstractAction.extend({ + + contentTemplate: 'Invoicedashboard', + + events: { + 'click .invoice_dashboard': 'onclick_dashboard', + 'click #prog_bar': 'onclick_prog_bar', + 'click #invoice_this_month': 'onclick_invoice_this_month', + 'click #invoice_this_year': 'onclick_invoice_this_year', + 'click #invoice_last_month': 'onclick_invoice_last_month', + 'click #invoice_last_year': 'onclick_invoice_last_year', + 'click #onclick_banks_balance': 'onclick_bank_balance', + 'click #income_this_month': 'onclick_income_this_month', + 'click #income_this_year': 'onclick_income_this_year', + 'click #income_last_month': 'onclick_income_last_month', + 'click #income_last_year': 'onclick_income_last_year', + 'click #total_aged_payable': 'onclick_total_aged_payable', + 'click #in_ex_bar_chart': 'onclick_in_ex_bar_chart', + 'click #aged_recevable_pie_chart': 'onclick_aged_recevable_pie_chart', + 'click #invoice_bar_chart': 'onclick_invoice_bar_chart', + 'click .overdue_line_cust': 'onclick_overdue_line_cust', + 'click .top_customers': 'onclick_top_customers', + 'click .top_customers_amount': 'onclick_top_customers_amount', + 'click #bank_balance_hide': 'onclick_bank_balance_hide', + 'click #cash_balance_hide': 'onclick_cash_balance_hide', + 'click #in_ex_hide': 'onclick_in_ex_hide', + 'click #aged_payable_hide': 'onclick_aged_payable_hide', + 'change #aged_receivable_values': function(e) { + e.stopPropagation(); + var $target = $(e.target); + var value = $target.val(); + // this.$('.aged_receivable_this_month').empty(); + this.onclick_aged_payable(this.$('#aged_receivable_values').val()); + }, + 'change #aged_payable_value': function(e) { + e.stopPropagation(); + var $target = $(e.target); + var value = $target.val(); + this.$('.aged_receivable_this_month').empty(); + this.onclick_aged_receivable(this.$('#aged_payable_value').val()); + }, + 'change #top_10_customer_value': function(e) { + e.stopPropagation(); + var $target = $(e.target); + var value = $target.val(); + this.$('.top_10_customers_this_month').empty(); + this.onclick_top_10_month(this.$('#top_10_customer_value').val()); + }, + 'change #toggle-two': 'onclick_toggle_two', + 'click #unreconciled_counts_this_year': 'unreconciled_year', + 'click #unreconciled_items_': 'unreconciled_month', + 'click #total_customer_invoice_paid_current_month': 'invoice_month_paid', + 'click #total_customer_invoice_current_month': 'invoice_month', + 'click #total_supplier_invoice_paid_current_month': 'bill_month_paid', + 'click #total_supplier_invoice_current_month': 'bill_month', + 'click #total_customer_invoice_paid_current_year': 'invoice_year_paid', + 'click #total_customer_invoice_current_year': 'invoice_year', + 'click #total_supplier_invoice_paid_current_year': 'bill_year_paid', + 'click #total_supplier_invoice_current_year': 'bill_year', + 'click #net_profit_current_year': 'profit_income_year', + 'click #net_profit_current_months': 'profit_income_month', + 'click #total_incomes_this_year': 'total_income_year', + 'click #total_incomes_': 'total_income_month', + 'click #total_expense_this_year': 'expense_year', + 'click #total_expenses_': 'expense_month', + }, + profit_income_year: function(ev) { + var posted = false; + var self = this; + rpc.query({ + model: "account.move", + method: "click_profit_income_year", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move.line', + name: _t('Net Profit or Loss'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + profit_income_month: function(ev) { + var posted = false; + var self = this; + rpc.query({ + model: "account.move", + method: "click_profit_income_month", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move.line', + name: _t('Net Profit or Loss'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + total_income_year: function(ev) { + var posted = false; + var self = this; + rpc.query({ + model: "account.move", + method: "click_total_income_year", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move.line', + name: _t('Total Income'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + total_income_month: function(ev) { + var posted = false; + var self = this; + rpc.query({ + model: "account.move", + method: "click_total_income_month", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move.line', + name: _t('Total Income'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + expense_year: function(ev) { + var posted = false; + var self = this; + rpc.query({ + model: "account.move", + method: "click_expense_year", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move.line', + name: _t('Total Expenses'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + expense_month: function(ev) { + var posted = false; + var self = this; + rpc.query({ + model: "account.move", + method: "click_expense_month", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move.line', + name: _t('Total Expenses'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + unreconciled_year: function(ev) { + var posted = false; + var self = this; + rpc.query({ + model: "account.move", + method: "click_unreconcile_year", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move.line', + name: _t('Unreconciled'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + unreconciled_month: function(ev) { + var posted = false; + var self = this; + rpc.query({ + model: "account.move", + method: "click_unreconcile_month", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move.line', + name: _t('Unreconciled'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + invoice_month_paid: function(ev) { + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + var self = this; + rpc.query({ + model: "account.move", + method: "click_invoice_month_paid", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move', + name: _t('Paid'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + invoice_month: function(ev) { + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + var self = this; + rpc.query({ + model: "account.move", + method: "click_invoice_month", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move', + name: _t('Invoice'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + bill_month_paid: function(ev) { + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + var self = this; + rpc.query({ + model: "account.move", + method: "click_bill_month_paid", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move', + name: _t('Paid'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + bill_month: function(ev) { + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + var self = this; + rpc.query({ + model: "account.move", + method: "click_bill_month", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move', + name: _t('Invoice'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + bill_year: function(ev) { + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + var self = this; + rpc.query({ + model: "account.move", + method: "click_bill_year", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move', + name: _t('Invoice'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + bill_year_paid: function(ev) { + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + var self = this; + rpc.query({ + model: "account.move", + method: "click_bill_year_paid", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move', + name: _t('Paid'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + invoice_year: function(ev) { + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + var self = this; + rpc.query({ + model: "account.move", + method: "click_invoice_year", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move', + name: _t('Invoice'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + invoice_year_paid: function(ev) { + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + var self = this; + rpc.query({ + model: "account.move", + method: "click_invoice_year_paid", + args: [posted], + }).then(function(result) { + self.do_action({ + res_model: 'account.move', + name: _t('Paid'), + views: [ + [false, 'list'], + [false, 'form'] + ], + type: 'ir.actions.act_window', + domain: [ + ['id', 'in', result] + ], + }); + }) + }, + + onclick_toggle_two: function(ev) { + + this.onclick_aged_payable(this.$('#aged_receivable_values').val()); + + this.onclick_aged_receivable(this.$('#aged_payable_value').val()); + this.onclick_invoice_this_year(ev); + this.onclick_invoice_this_month(ev); + + this.onclick_income_this_month(ev); + this.onclick_income_last_month(ev); + this.onclick_income_last_year(ev); + this.onclick_income_this_year(ev); + }, + + onclick_top_10_month: function(f) { + var selected = $('.btn.btn-tool.income'); + var data = $(selected[0]).data(); + var posted = false; + var self = this; + var f = f; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + rpc.query({ + model: "account.move", + method: "get_currency", + }).then(function(result) { + currency = result; + }) + rpc.query({ + model: "account.move", + method: "get_top_10_customers_month", + args: [posted, f] + }) + .then(function(result) { + $('#top_10_customers').hide(); + $('#top_10_customers_last_month').hide(); + $('#top_10_customers_this_month').show(); + $('#top_10_customers_this_month').empty(); + + var due_count = 0; + _.forEach(result, function(x) { + due_count++; + var amount = self.format_currency(currency, x.amount); + $('#top_10_customers_this_month').append('<li><div id="line_' + x.parent + '" data-user-id="' + x.parent + '">' + x.customers + '</div>' + '<div id="line_' + x.parent + '" data-user-id="' + x.parent + '">' + amount + '</div>' + '</li>'); + $('#line_' + x.parent).on("click", function() { + self.do_action({ + res_model: 'res.partner', + name: _t('Partner'), + views: [ + [false, 'form'] + ], + type: 'ir.actions.act_window', + res_id: x.parent, + }); + }); + }); + + + }) + }, + + onclick_income_last_year: function(ev) { + ev.preventDefault(); + var selected = $('.btn.btn-tool.income'); + var data = $(selected[0]).data(); + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + rpc.query({ + model: 'account.move', + method: 'get_income_last_year', + args: [posted], + }) + .then(function(result) { + + $('#net_profit_current_months').hide(); + $('#net_profit_last_month').hide(); + $('#net_profit_last_year').show(); + $('#net_profit_this_year').hide(); + + var ctx = document.getElementById("canvas").getContext('2d'); + + // Define the data + var income = result.income; // Add data values to array + var expense = result.expense; + var profit = result.profit; + + var labels = result.month; // Add labels to array + // End Defining data + + // End Defining data + if (window.myCharts != undefined) + window.myCharts.destroy(); + window.myCharts = new Chart(ctx, { + //var myChart = new Chart(ctx, { + type: 'bar', + data: { + labels: labels, + datasets: [{ + label: 'Income', // Name the series + data: income, // Specify the data values array + backgroundColor: '#66aecf', + borderColor: '#66aecf', + + borderWidth: 1, // Specify bar border width + type: 'bar', // Set this data to a line chart + fill: false + }, + { + label: 'Expense', // Name the series + data: expense, // Specify the data values array + backgroundColor: '#6993d6', + borderColor: '#6993d6', + + borderWidth: 1, // Specify bar border width + type: 'bar', // Set this data to a line chart + fill: false + }, + { + label: 'Profit/Loss', // Name the series + data: profit, // Specify the data values array + backgroundColor: '#0bd465', + borderColor: '#0bd465', + + borderWidth: 1, // Specify bar border width + type: 'line', // Set this data to a line chart + fill: false + } + ] + }, + options: { + responsive: true, // Instruct chart js to respond nicely. + maintainAspectRatio: false, // Add to prevent default behaviour of full-width/height + } + }); + + }) + }, + + onclick_income_last_month: function(ev) { + ev.preventDefault(); + var selected = $('.btn.btn-tool.income'); + var data = $(selected[0]).data(); + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + rpc.query({ + model: 'account.move', + method: 'get_income_last_month', + args: [posted], + }) + .then(function(result) { + $('#net_profit_current_months').hide(); + $('#net_profit_last_month').show(); + $('#net_profit_this_year').hide(); + $('#net_profit_last_year').hide(); + + var ctx = document.getElementById("canvas").getContext('2d'); + + // Define the data + var income = result.income; // Add data values to array + var expense = result.expense; + var profit = result.profit; + + var labels = result.date; // Add labels to array + // End Defining data + + // End Defining data + if (window.myCharts != undefined) + window.myCharts.destroy(); + window.myCharts = new Chart(ctx, { + //var myChart = new Chart(ctx, { + type: 'bar', + data: { + labels: labels, + datasets: [{ + label: 'Income', // Name the series + data: income, // Specify the data values array + backgroundColor: '#66aecf', + borderColor: '#66aecf', + + borderWidth: 1, // Specify bar border width + type: 'bar', // Set this data to a line chart + fill: false + }, + { + label: 'Expense', // Name the series + data: expense, // Specify the data values array + backgroundColor: '#6993d6', + borderColor: '#6993d6', + + borderWidth: 1, // Specify bar border width + type: 'bar', // Set this data to a line chart + fill: false + }, + { + label: 'Profit/Loss', // Name the series + data: profit, // Specify the data values array + backgroundColor: '#0bd465', + borderColor: '#0bd465', + + borderWidth: 1, // Specify bar border width + type: 'line', // Set this data to a line chart + fill: false + } + ] + }, + options: { + responsive: true, // Instruct chart js to respond nicely. + maintainAspectRatio: false, // Add to prevent default behaviour of full-width/height + } + }); + + }) + }, + onclick_income_this_year: function(ev) { + ev.preventDefault(); + var selected = $('.btn.btn-tool.income'); + var data = $(selected[0]).data(); + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + + + rpc.query({ + model: 'account.move', + method: 'get_income_this_year', + args: [posted], + + }) + .then(function(result) { + + + $('#net_profit_current_months').hide(); + $('#net_profit_last_month').hide(); + $('#net_profit_last_year').hide(); + $('#net_profit_this_year').show(); + + var ctx = document.getElementById("canvas").getContext('2d'); + + // Define the data + var income = result.income; // Add data values to array + var expense = result.expense; + var profit = result.profit; + + var labels = result.month; // Add labels to array + + + if (window.myCharts != undefined) + window.myCharts.destroy(); + window.myCharts = new Chart(ctx, { + //var myChart = new Chart(ctx, { + type: 'bar', + data: { + labels: labels, + datasets: [{ + label: 'Income', // Name the series + data: income, // Specify the data values array + backgroundColor: '#66aecf', + borderColor: '#66aecf', + + borderWidth: 1, // Specify bar border width + type: 'bar', // Set this data to a line chart + fill: false + }, + { + label: 'Expense', // Name the series + data: expense, // Specify the data values array + backgroundColor: '#6993d6', + borderColor: '#6993d6', + + borderWidth: 1, // Specify bar border width + type: 'bar', // Set this data to a line chart + fill: false + }, + { + label: 'Profit/Loss', // Name the series + data: profit, // Specify the data values array + backgroundColor: '#0bd465', + borderColor: '#0bd465', + + borderWidth: 1, // Specify bar border width + type: 'line', // Set this data to a line chart + fill: false + } + ] + }, + options: { + responsive: true, // Instruct chart js to respond nicely. + maintainAspectRatio: false, // Add to prevent default behaviour of full-width/height + } + }); + + }) + }, + + + onclick_invoice_this_year: function(ev) { + ev.preventDefault(); + var selected = $('.btn.btn-tool.selected'); + var data = $(selected[0]).data(); + var posted = false; + var self = this; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + + rpc.query({ + model: "account.move", + method: "get_currency", + }).then(function(result) { + currency = result; + }) + + rpc.query({ + model: "account.move", + method: "get_total_invoice_current_year", + args: [posted], + }) + .then(function(result) { + + $('#total_supplier_invoice_paid').hide(); + $('#total_supplier_invoice').hide(); + $('#total_customer_invoice_paid').hide(); + $('#total_customer_invoice').hide(); + $('#tot_invoice').hide(); + $('#tot_supplier_inv').hide(); + + $('#total_supplier_invoice_paid_current_month').hide(); + $('#total_supplier_invoice_current_month').hide(); + $('#total_customer_invoice_paid_current_month').hide(); + $('#total_customer_invoice_current_month').hide(); + $('#tot_invoice_current_month').hide(); + $('#tot_supplier_inv_current_month').hide(); + + + $('#total_supplier_invoice_paid_current_year').empty(); + $('#total_supplier_invoice_current_year').empty(); + $('#total_customer_invoice_paid_current_year').empty(); + $('#total_customer_invoice_current_year').empty(); + $('#tot_invoice_current_year').empty(); + $('#tot_supplier_inv_current_year').empty(); + + $('#total_supplier_invoice_paid_current_year').show(); + $('#total_supplier_invoice_current_year').show(); + $('#total_customer_invoice_paid_current_year').show(); + $('#total_customer_invoice_current_year').show(); + $('#tot_invoice_current_year').show(); + $('#tot_supplier_inv_current_year').show(); + var tot_invoice_current_year = result[0][0] + var tot_credit_current_year = result[1][0] + var tot_supplier_inv_current_year = result[2][0] + var tot_supplier_refund_current_year = result[3][0] + var tot_customer_invoice_paid_current_year = result[4][0] + var tot_supplier_invoice_paid_current_year = result[5][0] + var tot_customer_credit_paid_current_year = result[6][0] + var tot_supplier_refund_paid_current_year = result[7][0] + var customer_invoice_total_current_year = (tot_invoice_current_year - tot_credit_current_year).toFixed(2) + var customer_invoice_paid_current_year = (tot_customer_invoice_paid_current_year - tot_customer_credit_paid_current_year).toFixed(2) + var invoice_percentage_current_year = ((customer_invoice_total_current_year / customer_invoice_paid_current_year) * 100).toFixed(2) + var supplier_invoice_total_current_year = (tot_supplier_inv_current_year - tot_supplier_refund_current_year).toFixed(2) + var supplier_invoice_paid_current_year = (tot_supplier_invoice_paid_current_year - tot_supplier_refund_paid_current_year).toFixed(2) + var supplier_percentage_current_year = ((supplier_invoice_total_current_year / supplier_invoice_paid_current_year) * 100).toFixed(2) + + $('#tot_supplier_inv_current_year').attr("value", supplier_invoice_paid_current_year); + $('#tot_supplier_inv_current_year').attr("max", supplier_invoice_total_current_year); + + $('#tot_invoice_current_year').attr("value", customer_invoice_paid_current_year); + $('#tot_invoice_current_year').attr("max", customer_invoice_total_current_year); + + customer_invoice_paid_current_year = self.format_currency(currency, customer_invoice_paid_current_year); + customer_invoice_total_current_year = self.format_currency(currency, customer_invoice_total_current_year); + supplier_invoice_paid_current_year = self.format_currency(currency, supplier_invoice_paid_current_year); + supplier_invoice_total_current_year = self.format_currency(currency, supplier_invoice_total_current_year); + + $('#total_customer_invoice_paid_current_year').append('<div class="logo">' + '<span>' + customer_invoice_paid_current_year + '</span><span>Total Paid<span></div>'); + $('#total_customer_invoice_current_year').append('<div" class="logo">' + '<span>' + customer_invoice_total_current_year + '</span><span>Total Invoice <span></div>'); + + $('#total_supplier_invoice_paid_current_year').append('<div" class="logo">' + '<span>' + supplier_invoice_paid_current_year + '</span><span>Total Paid<span></div>'); + $('#total_supplier_invoice_current_year').append('<div" class="logo">' + '<span>' + supplier_invoice_total_current_year + '</span><span>Total Invoice<span></div>'); + + }) + }, + onclick_invoice_this_month: function(ev) { + ev.preventDefault(); + var selected = $('.btn.btn-tool.selected'); + var data = $(selected[0]).data(); + var posted = false; + var self = this; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + rpc.query({ + model: "account.move", + method: "get_currency", + }).then(function(result) { + currency = result; + }) + rpc.query({ + model: "account.move", + method: "get_total_invoice_current_month", + args: [posted], + }) + .then(function(result) { + $('#total_supplier_invoice_paid').hide(); + $('#total_supplier_invoice').hide(); + $('#total_customer_invoice_paid').hide(); + $('#total_customer_invoice').hide(); + $('#tot_invoice').hide(); + $('#tot_supplier_inv').hide(); + $('#total_supplier_invoice_paid_current_month').empty(); + $('#total_supplier_invoice_current_month').empty(); + $('#total_customer_invoice_paid_current_month').empty(); + $('#total_customer_invoice_current_month').empty(); + $('#tot_invoice_current_month').empty(); + $('#tot_supplier_inv_current_month').empty(); + $('#total_supplier_invoice_paid_current_year').hide(); + $('#total_supplier_invoice_current_year').hide(); + $('#total_customer_invoice_paid_current_year').hide(); + $('#total_customer_invoice_current_year').hide(); + $('#tot_invoice_current_year').hide(); + $('#tot_supplier_inv_current_year').hide(); + $('#total_supplier_invoice_paid_current_month').show(); + $('#total_supplier_invoice_current_month').show(); + $('#total_customer_invoice_paid_current_month').show(); + $('#total_customer_invoice_current_month').show(); + $('#tot_invoice_current_month').show(); + $('#tot_supplier_inv_current_month').show(); + var tot_invoice_current_month = result[0][0] + var tot_credit_current_month = result[1][0] + var tot_supplier_inv_current_month = result[2][0] + var tot_supplier_refund_current_month = result[3][0] + var tot_customer_invoice_paid_current_month = result[4][0] + var tot_supplier_invoice_paid_current_month = result[5][0] + var tot_customer_credit_paid_current_month = result[6][0] + var tot_supplier_refund_paid_current_month = result[7][0] + var customer_invoice_total_current_month = (tot_invoice_current_month - tot_credit_current_month).toFixed(2) + var customer_invoice_paid_current_month = (tot_customer_invoice_paid_current_month - tot_customer_credit_paid_current_month).toFixed(2) + var invoice_percentage_current_month = ((customer_invoice_total_current_month / customer_invoice_paid_current_month) * 100).toFixed(2) + var supplier_invoice_total_current_month = (tot_supplier_inv_current_month - tot_supplier_refund_current_month).toFixed(2) + var supplier_invoice_paid_current_month = (tot_supplier_invoice_paid_current_month - tot_supplier_refund_paid_current_month).toFixed(2) + var supplier_percentage_current_month = ((supplier_invoice_total_current_month / supplier_invoice_paid_current_month) * 100).toFixed(2) + + $('#tot_supplier_inv_current_month').attr("value", supplier_invoice_paid_current_month); + $('#tot_supplier_inv_current_month').attr("max", supplier_invoice_total_current_month); + + $('#tot_invoice_current_month').attr("value", customer_invoice_paid_current_month); + $('#tot_invoice_current_month').attr("max", customer_invoice_total_current_month); + + customer_invoice_paid_current_month = self.format_currency(currency, customer_invoice_paid_current_month); + customer_invoice_total_current_month = self.format_currency(currency, customer_invoice_total_current_month); + supplier_invoice_paid_current_month = self.format_currency(currency, supplier_invoice_paid_current_month); + supplier_invoice_total_current_month = self.format_currency(currency, supplier_invoice_total_current_month); + + + $('#total_customer_invoice_paid_current_month').append('<div class="logo">' + '<span>' + customer_invoice_paid_current_month + '</span><span>Total Paid<span></div>'); + $('#total_customer_invoice_current_month').append('<div" class="logo">' + '<span>' + customer_invoice_total_current_month + '</span><span>Total Invoice<span></div>'); + + $('#total_supplier_invoice_paid_current_month').append('<div" class="logo">' + '<span>' + supplier_invoice_paid_current_month + '</span><span>Total Paid<span></div>'); + $('#total_supplier_invoice_current_month').append('<div" class="logo">' + '<span>' + supplier_invoice_total_current_month + '</span><span>Total Invoice<span></div>'); + + }) + }, + + onclick_income_this_month: function(ev) { + ev.preventDefault(); + var selected = $('.btn.btn-tool.income'); + var data = $(selected[0]).data(); + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + rpc.query({ + model: 'account.move', + method: 'get_income_this_month', + args: [posted], + + }) + .then(function(result) { + + + var ctx = document.getElementById("canvas").getContext('2d'); + + // Define the data + var income = result.income; // Add data values to array + var expense = result.expense; + var profit = result.profit; + + var labels = result.date; // Add labels to array + // End Defining data + + // End Defining data + if (window.myCharts != undefined) + window.myCharts.destroy(); + window.myCharts = new Chart(ctx, { + //var myChart = new Chart(ctx, { + type: 'bar', + data: { + labels: labels, + datasets: [{ + label: 'Income', // Name the series + data: income, // Specify the data values array + backgroundColor: '#66aecf', + borderColor: '#66aecf', + + borderWidth: 1, // Specify bar border width + type: 'bar', // Set this data to a line chart + fill: false + }, + { + label: 'Expense', // Name the series + data: expense, // Specify the data values array + backgroundColor: '#6993d6', + borderColor: '#6993d6', + + borderWidth: 1, // Specify bar border width + type: 'bar', // Set this data to a line chart + fill: false + }, + { + label: 'Profit/Loss', // Name the series + data: profit, // Specify the data values array + backgroundColor: '#0bd465', + borderColor: '#0bd465', + + borderWidth: 1, // Specify bar border width + type: 'line', // Set this data to a line chart + fill: false + } + ] + }, + options: { + responsive: true, // Instruct chart js to respond nicely. + maintainAspectRatio: false, // Add to prevent default behaviour of full-width/height + } + }); + + }) + }, + + onclick_aged_payable: function(f) { + + // ev.preventDefault(); + var arg = f; + var selected = $('.btn.btn-tool.expense'); + var data = $(selected[0]).data(); + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + rpc.query({ + model: 'account.move', + method: 'get_overdues_this_month_and_year', + args: [posted, f], + }) + .then(function(result) { + // Doughnut Chart + $(document).ready(function() { + var options = { + // legend: false, + responsive: false + }; + if (window.donut != undefined) + window.donut.destroy(); + + + window.donut = new Chart($("#canvas1"), { + type: 'doughnut', + tooltipFillColor: "rgba(51, 51, 51, 0.55)", + data: { + labels: result.due_partner, + datasets: [{ + data: result.due_amount, + backgroundColor: [ + '#66aecf ', '#6993d6 ', '#666fcf', '#7c66cf', '#9c66cf', + '#bc66cf ', '#b75fcc', ' #cb5fbf ', ' #cc5f7f ', ' #cc6260', + '#cc815f', '#cca15f ', '#ccc25f', '#b9cf66', '#99cf66', + ' #75cb5f ', '#60cc6c', '#804D8000', '#80B33300', '#80CC80CC', '#f2552c', '#00cccc', + '#1f2e2e', '#993333', '#00cca3', '#1a1a00', '#3399ff', + '#8066664D', '#80991AFF', '#808E666FF', '#804DB3FF', '#801AB399', + '#80E666B3', '#8033991A', '#80CC9999', '#80B3B31A', '#8000E680', + '#804D8066', '#80809980', '#80E6FF80', '#801AFF33', '#80999933', + '#80FF3380', '#80CCCC00', '#8066E64D', '#804D80CC', '#809900B3', + '#80E64D66', '#804DB380', '#80FF4D4D', '#8099E6E6', '#806666FF' + ], + hoverBackgroundColor: [ + '#66aecf ', '#6993d6 ', '#666fcf', '#7c66cf', '#9c66cf', + '#bc66cf ', '#b75fcc', ' #cb5fbf ', ' #cc5f7f ', ' #cc6260', + '#cc815f', '#cca15f ', '#ccc25f', '#b9cf66', '#99cf66', + ' #75cb5f ', '#60cc6c', '#804D8000', '#80B33300', '#80CC80CC', '#f2552c', '#00cccc', + '#1f2e2e', '#993333', '#00cca3', '#1a1a00', '#3399ff', + '#8066664D', '#80991AFF', '#808E666FF', '#804DB3FF', '#801AB399', + '#80E666B3', '#8033991A', '#80CC9999', '#80B3B31A', '#8000E680', + '#804D8066', '#80809980', '#80E6FF80', '#801AFF33', '#80999933', + '#80FF3380', '#80CCCC00', '#8066E64D', '#804D80CC', '#809900B3', + '#80E64D66', '#804DB380', '#80FF4D4D', '#8099E6E6', '#806666FF' + ] + }] + }, + options: { + responsive: false + } + }); + }); + // Doughnut Chart + + }) + }, + + + onclick_aged_receivable: function(f) { + var selected = $('.btn.btn-tool.expense'); + var data = $(selected[0]).data(); + var posted = false; + var f = f + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + rpc.query({ + model: 'account.move', + method: 'get_latebillss', + args: [posted, f], + + }) + .then(function(result) { + function myFunction() { + document.getElementByClass("btn btn-tool dropdown-toggle").text + document.getElementById("aged_receivable_this_month").text + } + + $(document).ready(function() { + var options = { + // legend: false, + responsive: true, + legend: { + position: 'bottom' + } + }; + + + if (window.donuts != undefined) + window.donuts.destroy(); + + + window.donuts = new Chart($("#horizontalbarChart"), { + type: 'doughnut', + tooltipFillColor: "rgba(51, 51, 51, 0.55)", + data: { + labels: result.bill_partner, + datasets: [{ + data: result.bill_amount, + backgroundColor: [ + '#66aecf ', '#6993d6 ', '#666fcf', '#7c66cf', '#9c66cf', + '#bc66cf ', '#b75fcc', ' #cb5fbf ', ' #cc5f7f ', ' #cc6260', + '#cc815f', '#cca15f ', '#ccc25f', '#b9cf66', '#99cf66', + ' #75cb5f ', '#60cc6c', '#804D8000', '#80B33300', '#80CC80CC', '#f2552c', '#00cccc', + '#1f2e2e', '#993333', '#00cca3', '#1a1a00', '#3399ff', + '#8066664D', '#80991AFF', '#808E666FF', '#804DB3FF', '#801AB399', + '#80E666B3', '#8033991A', '#80CC9999', '#80B3B31A', '#8000E680', + '#804D8066', '#80809980', '#80E6FF80', '#801AFF33', '#80999933', + '#80FF3380', '#80CCCC00', '#8066E64D', '#804D80CC', '#809900B3', + '#80E64D66', '#804DB380', '#80FF4D4D', '#8099E6E6', '#806666FF' + ], + hoverBackgroundColor: [ + '#66aecf ', '#6993d6 ', '#666fcf', '#7c66cf', '#9c66cf', + '#bc66cf ', '#b75fcc', ' #cb5fbf ', ' #cc5f7f ', ' #cc6260', + '#cc815f', '#cca15f ', '#ccc25f', '#b9cf66', '#99cf66', + ' #75cb5f ', '#60cc6c', '#804D8000', '#80B33300', '#80CC80CC', '#f2552c', '#00cccc', + '#1f2e2e', '#993333', '#00cca3', '#1a1a00', '#3399ff', + '#8066664D', '#80991AFF', '#808E666FF', '#804DB3FF', '#801AB399', + '#80E666B3', '#8033991A', '#80CC9999', '#80B3B31A', '#8000E680', + '#804D8066', '#80809980', '#80E6FF80', '#801AFF33', '#80999933', + '#80FF3380', '#80CCCC00', '#8066E64D', '#804D80CC', '#809900B3', + '#80E64D66', '#804DB380', '#80FF4D4D', '#8099E6E6', '#806666FF' + ] + }] + }, + options: { + responsive: false + } + }); + }); + + + }) + }, + + renderElement: function(ev) { + var self = this; + $.when(this._super()) + .then(function(ev) { + + + $('#toggle-two').bootstrapToggle({ + on: 'View All Entries', + off: 'View Posted Entries' + }); + + + var posted = false; + if ($('#toggle-two')[0].checked == true) { + posted = "posted" + } + + + rpc.query({ + model: "account.move", + method: "get_currency", + }) + .then(function(result) { + currency = result; + + }) + + + rpc.query({ + model: "account.move", + method: "get_income_this_month", + args: [posted], + }) + .then(function(result) { + + + var ctx = document.getElementById("canvas").getContext('2d'); + + // Define the data + var income = result.income; // Add data values to array + var expense = result.expense; + var profit = result.profit; + + var labels = result.date; // Add labels to array + // End Defining data + + // End Defining data + if (window.myCharts != undefined) + window.myCharts.destroy(); + window.myCharts = new Chart(ctx, { + //var myChart = new Chart(ctx, { + type: 'bar', + data: { + labels: labels, + datasets: [{ + label: 'Income', // Name the series + data: income, // Specify the data values array + backgroundColor: '#66aecf', + borderColor: '#66aecf', + + borderWidth: 1, // Specify bar border width + type: 'bar', // Set this data to a line chart + fill: false + }, + { + label: 'Expense', // Name the series + data: expense, // Specify the data values array + backgroundColor: '#6993d6', + borderColor: '#6993d6', + + borderWidth: 1, // Specify bar border width + type: 'bar', // Set this data to a line chart + fill: false + }, + { + label: 'Profit/Loss', // Name the series + data: profit, // Specify the data values array + backgroundColor: '#0bd465', + borderColor: '#0bd465', + + borderWidth: 1, // Specify bar border width + type: 'line', // Set this data to a line chart + fill: false + } + ] + }, + options: { + responsive: true, // Instruct chart js to respond nicely. + maintainAspectRatio: false, // Add to prevent default behaviour of full-width/height + } + }); + + }) + var arg = 'this_month'; + rpc.query({ + model: 'account.move', + method: 'get_overdues_this_month_and_year', + args: [posted, arg], + }).then(function(result) { + + // + }) + var arg = 'this_month'; + rpc.query({ + model: 'account.move', + method: 'get_overdues_this_month_and_year', + args: [posted, arg], + }) + .then(function(result) { + // Doughnut Chart + $(document).ready(function() { + var options = { + // legend: false, + responsive: true, + legend: { + position: 'bottom' + } + }; + if (window.donut != undefined) + window.donut.destroy(); + window.donut = new Chart($("#canvas1"), { + type: 'doughnut', + tooltipFillColor: "rgba(51, 51, 51, 0.55)", + data: { + labels: result.due_partner, + datasets: [{ + data: result.due_amount, + backgroundColor: [ + '#66aecf ', '#6993d6 ', '#666fcf', '#7c66cf', '#9c66cf', + '#bc66cf ', '#b75fcc', ' #cb5fbf ', ' #cc5f7f ', ' #cc6260', + '#cc815f', '#cca15f ', '#ccc25f', '#b9cf66', '#99cf66', + ' #75cb5f ', '#60cc6c', '#804D8000', '#80B33300', '#80CC80CC', '#f2552c', '#00cccc', + '#1f2e2e', '#993333', '#00cca3', '#1a1a00', '#3399ff', + '#8066664D', '#80991AFF', '#808E666FF', '#804DB3FF', '#801AB399', + '#80E666B3', '#8033991A', '#80CC9999', '#80B3B31A', '#8000E680', + '#804D8066', '#80809980', '#80E6FF80', '#801AFF33', '#80999933', + '#80FF3380', '#80CCCC00', '#8066E64D', '#804D80CC', '#809900B3', + '#80E64D66', '#804DB380', '#80FF4D4D', '#8099E6E6', '#806666FF' + ], + hoverBackgroundColor: [ + '#66aecf ', '#6993d6 ', '#666fcf', '#7c66cf', '#9c66cf', + '#bc66cf ', '#b75fcc', ' #cb5fbf ', ' #cc5f7f ', ' #cc6260', + '#cc815f', '#cca15f ', '#ccc25f', '#b9cf66', '#99cf66', + ' #75cb5f ', '#60cc6c', '#804D8000', '#80B33300', '#80CC80CC', '#f2552c', '#00cccc', + '#1f2e2e', '#993333', '#00cca3', '#1a1a00', '#3399ff', + '#8066664D', '#80991AFF', '#808E666FF', '#804DB3FF', '#801AB399', + '#80E666B3', '#8033991A', '#80CC9999', '#80B3B31A', '#8000E680', + '#804D8066', '#80809980', '#80E6FF80', '#801AFF33', '#80999933', + '#80FF3380', '#80CCCC00', '#8066E64D', '#804D80CC', '#809900B3', + '#80E64D66', '#804DB380', '#80FF4D4D', '#8099E6E6', '#806666FF' + ] + }] + }, + options: { + responsive: false + } + }); + }); + }) + rpc.query({ + model: "account.move", + method: "get_total_invoice_current_month", + args: [posted], + }).then(function(result) { + + $('#total_supplier_invoice_paid').hide(); + $('#total_supplier_invoice').hide(); + $('#total_customer_invoice_paid').hide(); + $('#total_customer_invoice').hide(); + $('#tot_invoice').hide(); + $('#tot_supplier_inv').hide(); + + $('#total_supplier_invoice_paid_current_month').empty(); + $('#total_supplier_invoice_current_month').empty(); + $('#total_customer_invoice_paid_current_month').empty(); + $('#total_customer_invoice_current_month').empty(); + $('#tot_invoice_current_month').empty(); + $('#tot_supplier_inv_current_month').empty(); + + $('#total_supplier_invoice_paid_current_year').hide(); + $('#total_supplier_invoice_current_year').hide(); + $('#total_customer_invoice_paid_current_year').hide(); + $('#total_customer_invoice_current_year').hide(); + $('#tot_invoice_current_year').hide(); + $('#tot_supplier_inv_current_year').hide(); + + + $('#total_supplier_invoice_paid_current_month').show(); + $('#total_supplier_invoice_current_month').show(); + $('#total_customer_invoice_paid_current_month').show(); + $('#total_customer_invoice_current_month').show(); + $('#tot_invoice_current_month').show(); + $('#tot_supplier_inv_current_month').show(); + + + var tot_invoice_current_month = result[0][0] + var tot_credit_current_month = result[1][0] + var tot_supplier_inv_current_month = result[2][0] + var tot_supplier_refund_current_month = result[3][0] + var tot_customer_invoice_paid_current_month = result[4][0] + var tot_supplier_invoice_paid_current_month = result[5][0] + var tot_customer_credit_paid_current_month = result[6][0] + var tot_supplier_refund_paid_current_month = result[7][0] + var customer_invoice_total_current_month = (tot_invoice_current_month - tot_credit_current_month).toFixed(2) + var customer_invoice_paid_current_month = (tot_customer_invoice_paid_current_month - tot_customer_credit_paid_current_month).toFixed(2) + var invoice_percentage_current_month = ((customer_invoice_total_current_month / customer_invoice_paid_current_month) * 100).toFixed(2) + var supplier_invoice_total_current_month = (tot_supplier_inv_current_month - tot_supplier_refund_current_month).toFixed(2) + var supplier_invoice_paid_current_month = (tot_supplier_invoice_paid_current_month - tot_supplier_refund_paid_current_month).toFixed(2) + var supplier_percentage_current_month = ((supplier_invoice_total_current_month / supplier_invoice_paid_current_month) * 100).toFixed(2) + + $('#tot_supplier_inv_current_month').attr("value", supplier_invoice_paid_current_month); + $('#tot_supplier_inv_current_month').attr("max", supplier_invoice_total_current_month); + + $('#tot_invoice_current_month').attr("value", customer_invoice_paid_current_month); + $('#tot_invoice_current_month').attr("max", customer_invoice_total_current_month); + currency = result[8] + customer_invoice_paid_current_month = self.format_currency(currency, customer_invoice_paid_current_month); + customer_invoice_total_current_month = self.format_currency(currency, customer_invoice_total_current_month); + supplier_invoice_paid_current_month = self.format_currency(currency, supplier_invoice_paid_current_month); + supplier_invoice_total_current_month = self.format_currency(currency, supplier_invoice_total_current_month); + + $('#total_customer_invoice_paid_current_month').append('<div class="logo">' + '<span>' + customer_invoice_paid_current_month + '</span><span>Total Paid<span></div>'); + $('#total_customer_invoice_current_month').append('<div" class="logo">' + '<span>' + customer_invoice_total_current_month + '</span><span>Total Invoice<span></div>'); + + $('#total_supplier_invoice_paid_current_month').append('<div" class="logo">' + '<span>' + supplier_invoice_paid_current_month + '</span><span>Total Paid<span></div>'); + $('#total_supplier_invoice_current_month').append('<div" class="logo">' + '<span>' + supplier_invoice_total_current_month + '</span><span>Total Invoice<span></div>'); + + }) + var arg = 'last_month' + rpc.query({ + model: 'account.move', + method: 'get_latebillss', + args: [posted, arg], + }) + .then(function(result) { + + $(document).ready(function() { + var options = { + // legend: false, + responsive: true, + legend: { + position: 'bottom' + } + }; + if (window.donuts != undefined) + window.donuts.destroy(); + window.donuts = new Chart($("#horizontalbarChart"), { + type: 'doughnut', + tooltipFillColor: "rgba(51, 51, 51, 0.55)", + data: { + labels: result.bill_partner, + datasets: [{ + data: result.bill_amount, + backgroundColor: [ + '#66aecf ', '#6993d6 ', '#666fcf', '#7c66cf', '#9c66cf', + '#bc66cf ', '#b75fcc', ' #cb5fbf ', ' #cc5f7f ', ' #cc6260', + '#cc815f', '#cca15f ', '#ccc25f', '#b9cf66', '#99cf66', + ' #75cb5f ', '#60cc6c', '#804D8000', '#80B33300', '#80CC80CC', '#f2552c', '#00cccc', + '#1f2e2e', '#993333', '#00cca3', '#1a1a00', '#3399ff', + '#8066664D', '#80991AFF', '#808E666FF', '#804DB3FF', '#801AB399', + '#80E666B3', '#8033991A', '#80CC9999', '#80B3B31A', '#8000E680', + '#804D8066', '#80809980', '#80E6FF80', '#801AFF33', '#80999933', + '#80FF3380', '#80CCCC00', '#8066E64D', '#804D80CC', '#809900B3', + '#80E64D66', '#804DB380', '#80FF4D4D', '#8099E6E6', '#806666FF' + ], + hoverBackgroundColor: [ + '#66aecf ', '#6993d6 ', '#666fcf', '#7c66cf', '#9c66cf', + '#bc66cf ', '#b75fcc', ' #cb5fbf ', ' #cc5f7f ', ' #cc6260', + '#cc815f', '#cca15f ', '#ccc25f', '#b9cf66', '#99cf66', + ' #75cb5f ', '#60cc6c', '#804D8000', '#80B33300', '#80CC80CC', '#f2552c', '#00cccc', + '#1f2e2e', '#993333', '#00cca3', '#1a1a00', '#3399ff', + '#8066664D', '#80991AFF', '#808E666FF', '#804DB3FF', '#801AB399', + '#80E666B3', '#8033991A', '#80CC9999', '#80B3B31A', '#8000E680', + '#804D8066', '#80809980', '#80E6FF80', '#801AFF33', '#80999933', + '#80FF3380', '#80CCCC00', '#8066E64D', '#804D80CC', '#809900B3', + '#80E64D66', '#804DB380', '#80FF4D4D', '#8099E6E6', '#806666FF' + ] + }] + }, + options: { + responsive: false + } + }); + }); + }) + rpc.query({ + model: "account.move", + method: "get_overdues", + + }).then(function(result) { + var due_count = 0; + _.forEach(result, function(x) { + due_count++; + $('#overdues').append('<li><a class="overdue_line_cust" href="#" id="line_' + x.parent + '" data-user-id="' + x.parent + '">' + x.due_partner + '</a>' + ' ' + '<span>' + x.due_amount + ' ' + currency + '</span>' + '</li>'); + + // $('#overdues_amounts').append('<li><a class="overdue_line_cust" href="#" id="line_' + x.parent + '" data-user-id="' + x.parent + '">' + x.amount + '</a>' + '<span>'+' '+currency+ '</span>' + '</li>' ); + + }); + + $('#due_count').append('<span class="badge badge-danger">' + due_count + ' Due(s)</span>'); + }) + var f = 'this_month' + rpc.query({ + model: "account.move", + method: "get_top_10_customers_month", + args: [posted, f] + }).then(function(result) { + var due_count = 0; + var amount; + $('#top_10_customers_this_month').empty(); + + _.forEach(result, function(x) { + $('#top_10_customers_this_month').show(); + due_count++; + amount = self.format_currency(currency, x.amount); + $('#top_10_customers_this_month').append('<li><div id="line_' + x.parent + '" data-user-id="' + x.parent + '">' + x.customers + '</div>' + '<div id="line_' + x.parent + '" data-user-id="' + x.parent + '">' + amount + '</div>' + '</li>'); + $('#line_' + x.parent).on("click", function() { + self.do_action({ + res_model: 'res.partner', + name: _t('Partner'), + views: [ + [false, 'form'] + ], + type: 'ir.actions.act_window', + res_id: x.parent, + }); + }); + + }); + }) + rpc.query({ + model: "account.move", + method: "bank_balance", + args: [posted] + }) + .then(function(result) { + var banks = result['banks']; + var amount; + var balance = result['banking']; + var bnk_ids = result['bank_ids']; + for (var k = 0; k < banks.length; k++) { + amount = self.format_currency(currency, balance[k]); + // $('#charts').append('<li><a ' + banks[k] + '" data-user-id="' + banks[k] + '">' + banks[k] + '</a>'+ ' ' + '<span>'+ balance[k] +'</span>' + '</li>' ); + $('#current_bank_balance').empty() + + $('#current_bank_balance').append('<li><div val="' + bnk_ids[k] + '"id="b_' + bnk_ids[k] + '">' + banks[k] + '</div><div>' + amount + '</div></li>'); + // $('#current_bank_balance').append('<li>' + banks[k] +' '+ balance[k] + '</li>' ); + $('#drop_charts_balance').append('<li>' + balance[k].toFixed(2) + '</li>'); + $('#b_' + bnk_ids[k]).on("click", function(ev) { + self.do_action({ + res_model: 'account.account', + name: _t('Account'), + views: [ + [false, 'form'] + ], + type: 'ir.actions.act_window', + res_id: parseInt(this.id.replace('b_', '')), + }); + }); + } + }) + + rpc.query({ + model: "account.move", + method: "get_latebills", + + }).then(function(result) { + var late_count = 0; + + _.forEach(result, function(x) { + late_count++; + $('#latebills').append('<li><a class="overdue_line_cust" href="#" id="line_' + x.parent + '" data-user-id="' + x.parent + '">' + x.partner + '<span>' + x.amount + ' ' + currency + '</span>' + '</a>' + '</li>'); + }); + $('#late_count').append('<span class="badge badge-danger">' + late_count + ' Late(s)</span>'); + }) + rpc.query({ + model: "account.move", + method: "get_total_invoice", + }) + .then(function(result) { + var total_invoice = result[0].sum; + total_invoice = total_invoice + $('#total_invoice').append('<span>' + total_invoice + ' ' + currency + '</span> ') + }) + rpc.query({ + model: "account.move", + method: "get_total_invoice_this_month", + args: [posted], + }) + .then(function(result) { + var invoice_this_month = result[0].sum; + if (invoice_this_month) { + var total_invoices_this_month = invoice_this_month.toFixed(2) + $('#total_invoices_').append('<span>' + total_invoices_this_month + ' ' + currency + '</span> <div class="title">This month</div>') + } + }) + + rpc.query({ + model: "account.move", + method: "get_total_invoice_last_month", + }) + .then(function(result) { + var invoice_last_month = result[0].sum; + var total_invoices_last_month = invoice_last_month + $('#total_invoices_last').append('<span>' + total_invoices_last_month + ' ' + currency + '</span><div class="title">Last month</div>') + }) + + rpc.query({ + model: "account.move", + method: "unreconcile_items" + }) + .then(function(result) { + + var unreconciled_count = result[0].count; + $('#unreconciled_items').append('<span>' + unreconciled_count + ' Item(s)</a></span> ') + }) + rpc.query({ + model: "account.move", + method: "unreconcile_items_this_month", + args: [posted], + }) + .then(function(result) { + var unreconciled_counts_ = result[0].count; + $('#unreconciled_items_').empty() + + $('#unreconciled_items_').append('<span>' + unreconciled_counts_ + ' Item(s)</span><div class="title">This month</div>') + }) + rpc.query({ + model: "account.move", + method: "unreconcile_items_this_year", + args: [posted], + }) + .then(function(result) { + + var unreconciled_counts_this_year = result[0].count; + $('#unreconciled_counts_this_year').empty() + + $('#unreconciled_counts_this_year').append('<span>' + unreconciled_counts_this_year + ' Item(s)</span><div class="title">This Year</div>') + // $('#unreconciled_counts_this_year').append('<span style= "color:#455e7b;">' + unreconciled_counts_this_year + ' Item(s)</span><div class="title">This Year</div>') + }) + + rpc.query({ + model: "account.move", + method: "unreconcile_items_last_year" + }) + .then(function(result) { + var unreconciled_counts_last_year = result[0].count; + $('#unreconciled_counts_last_year').empty() + + $('#unreconciled_counts_last_year').append('<span>' + unreconciled_counts_last_year + ' Item(s)</span><div class="title">Last Year</div>') + + }) + rpc.query({ + model: "account.move", + method: "month_income" + }) + .then(function(result) { + var income = result[0].debit - result[0].credit; + income = -income; + income = self.format_currency(currency, income); + $('#total_income').append('<span>' + income + '</span>') + }) + rpc.query({ + model: "account.move", + method: "month_income_this_month", + args: [posted], + }) + .then(function(result) { + var incomes_ = result[0].debit - result[0].credit; + if (incomes_) { + incomes_ = -incomes_; + incomes_ = self.format_currency(currency, incomes_); + $('#total_incomes_').empty() + + $('#total_incomes_').append('<span>' + incomes_ + '</span><div class="title">This month</div>') + + } else { + incomes_ = -incomes_; + incomes_ = self.format_currency(currency, incomes_); + $('#total_incomes_').empty() + + $('#total_incomes_').append('<span>' + incomes_ + '</span><div class="title">This month</div>') + } + }) + + rpc.query({ + model: "account.move", + method: "month_income_last_month" + }) + .then(function(result) { + var incomes_last = result[0].debit - result[0].credit; + incomes_last = -incomes_last; + incomes_last = self.format_currency(currency, incomes_last); + $('#total_incomes_last').append('<span>' + incomes_last + '</span><div class="title">Last month</div>') + }) + + rpc.query({ + model: "account.move", + method: "month_expense" + }) + .then(function(result) { + var expense = result[0].debit - result[0].credit; + var expenses = expense; + expenses = self.format_currency(currency, expenses); + + $('#total_expense').append('<span>' + expenses + '</span>') + }) + rpc.query({ + model: "account.move", + method: "month_expense_this_month", + args: [posted], + }).then(function(result) { + var expense_this_month = result[0].debit - result[0].credit; + if (expense_this_month) { + + var expenses_this_month_ = expense_this_month; + expenses_this_month_ = self.format_currency(currency, expenses_this_month_); + $('#total_expenses_').empty() + + $('#total_expenses_').append('<span>' + expenses_this_month_ + '</span><div class="title">This month</div>') + } else { + var expenses_this_month_ = expense_this_month; + expenses_this_month_ = self.format_currency(currency, expenses_this_month_); + $('#total_expenses_').empty() + + $('#total_expenses_').append('<span>' + expenses_this_month_ + '</span><div class="title">This month</div>') + + } + }) + rpc.query({ + model: "account.move", + method: "month_expense_this_year", + args: [posted], + }).then(function(result) { + var expense_this_year = result[0].debit - result[0].credit; + if (expense_this_year) { + + var expenses_this_year_ = expense_this_year; + expenses_this_year_ = self.format_currency(currency, expenses_this_year_); + $('#total_expense_this_year').empty(); + + $('#total_expense_this_year').append('<span >' + expenses_this_year_ + '</span><div class="title">This Year</div>') + } else { + var expenses_this_year_ = expense_this_year; + expenses_this_year_ = self.format_currency(currency, expenses_this_year_); + $('#total_expense_this_year').empty(); + + $('#total_expense_this_year').append('<span >' + expenses_this_year_ + '</span><div class="title">This Year</div>') + } + }) + rpc.query({ + model: "account.move", + method: "month_income_last_year" + }) + .then(function(result) { + var incomes_last_year = result[0].debit - result[0].credit; + incomes_last_year = -incomes_last_year + incomes_last_year = self.format_currency(currency, incomes_last_year); + $('#total_incomes_last_year').empty(); + + $('#total_incomes_last_year').append('<span>' + incomes_last_year + '</span><div class="title">Last Year</div>') + }) + rpc.query({ + model: "account.move", + method: "month_income_this_year", + args: [posted], + }) + .then(function(result) { + var incomes_this_year = result[0].debit - result[0].credit; + if (incomes_this_year) { + incomes_this_year = -incomes_this_year; + incomes_this_year = self.format_currency(currency, incomes_this_year); + $('#total_incomes_this_year').empty(); + + $('#total_incomes_this_year').append('<span>' + incomes_this_year + '</span><div class="title">This Year</div>') + } else { + incomes_this_year = -incomes_this_year; + incomes_this_year = self.format_currency(currency, incomes_this_year); + $('#total_incomes_this_year').empty(); + + $('#total_incomes_this_year').append('<span>' + incomes_this_year + '</span><div class="title">This Year</div>') + } + + }) + + rpc.query({ + model: "account.move", + method: "profit_income_this_month", + args: [posted], + }).then(function(result) { + var net_profit = true + if (result[1] == undefined) { + result[1] = 0; + if ((result[0]) > (result[1])) { + net_profit = result[1] - result[0] + } + + } + + if (result[0] == undefined) { + + result[0] = 0; + } + + if ((-result[1]) > (result[0])) { + net_profit = -result[1] - result[0] + } else if ((result[1]) > (result[0])) { + net_profit = -result[1] - result[0] + } else { + net_profit = -result[1] - result[0] + } + var profit_this_months = net_profit; + if (profit_this_months) { + var net_profit_this_months = profit_this_months; + net_profit_this_months = self.format_currency(currency, net_profit_this_months); + $('#net_profit_current_months').empty(); + // $('#net_profit_current_months').append('<div class="title">Net Profit/Loss </div><span>' + net_profit_this_months + '</span>') + $('#net_profit_current_months').append('<span>' + net_profit_this_months + '</span> <div class="title">This Month</div>') + + } else { + var net_profit_this_months = profit_this_months; + net_profit_this_months = self.format_currency(currency, net_profit_this_months); + $('#net_profit_current_months').empty(); + // $('#net_profit_current_months').append('<div class="title">Net Profit/Loss </div><span>' + net_profit_this_months + '</span>') + $('#net_profit_current_months').append('<span>' + net_profit_this_months + '</span> <div class="title">This Month</div>') + } + }) + + rpc.query({ + model: "account.move", + method: "profit_income_this_year", + args: [posted], + }) + .then(function(result) { + var net_profit = true + + + if (result[1] == undefined) { + result[1] = 0; + if ((result[0]) > (result[1])) { + net_profit = result[1] - result[0] + } + + } + + if (result[0] == undefined) { + + result[0] = 0; + } + + if ((-result[1]) > (result[0])) { + net_profit = -result[1] - result[0] + } else if ((result[1]) > (result[0])) { + net_profit = -result[1] - result[0] + } else { + net_profit = -result[1] - result[0] + } + var profit_this_year = net_profit; + if (profit_this_year) { + var net_profit_this_year = profit_this_year; + net_profit_this_year = self.format_currency(currency, net_profit_this_year); + $('#net_profit_current_year').empty(); + // $('#net_profit_this_year').append('<div class="title">Net Profit/Loss </div><span>' + net_profit_this_year + '</span>') + $('#net_profit_current_year').append('<span>' + net_profit_this_year + '</span> <div class="title">This Year</div>') + } else { + var net_profit_this_year = profit_this_year; + net_profit_this_year = self.format_currency(currency, net_profit_this_year); + $('#net_profit_current_year').empty(); + // $('#net_profit_this_year').append('<div class="title">Net Profit/Loss </div><span>' + net_profit_this_year + '</span>') + $('#net_profit_current_year').append('<span>' + net_profit_this_year + '</span> <div class="title">This Year</div>') + + } + }) + }); + }, + + format_currency: function(currency, amount) { + if (typeof(amount) != 'number') { + amount = parseFloat(amount); + } + var formatted_value = (parseInt(amount)).toLocaleString(currency.language, { + minimumFractionDigits: 2 + }) + if (currency.position === "after") { + return formatted_value += ' ' + currency.symbol; + } else { + return currency.symbol + ' ' + formatted_value; + } + }, + + willStart: function() { + var self = this; + self.drpdn_show = false; + return Promise.all([ajax.loadLibs(this), this._super()]); + }, + }); + core.action_registry.add('invoice_dashboard', ActionMenu); + +});
\ No newline at end of file diff --git a/base_accounting_kit/static/src/js/payment_matching.js b/base_accounting_kit/static/src/js/payment_matching.js new file mode 100644 index 0000000..80a1732 --- /dev/null +++ b/base_accounting_kit/static/src/js/payment_matching.js @@ -0,0 +1,505 @@ +odoo.define('base_accounting_kit.ReconciliationClientAction', function (require) { +"use strict"; + +var AbstractAction = require('web.AbstractAction'); +var ReconciliationModel = require('base_accounting_kit.ReconciliationModel'); +var ReconciliationRenderer = require('base_accounting_kit.ReconciliationRenderer'); +var core = require('web.core'); +var QWeb = core.qweb; + + +/** + * Widget used as action for 'account.bank.statement' reconciliation + */ +var StatementAction = AbstractAction.extend({ + hasControlPanel: true, + withSearchBar: true, + loadControlPanel: true, + title: core._t('Bank Reconciliation'), + contentTemplate: 'reconciliation', + custom_events: { + change_mode: '_onAction', + change_filter: '_onAction', + change_offset: '_onAction', + change_partner: '_onAction', + add_proposition: '_onAction', + remove_proposition: '_onAction', + update_proposition: '_onAction', + create_proposition: '_onAction', + getPartialAmount: '_onActionPartialAmount', + quick_create_proposition: '_onAction', + partial_reconcile: '_onAction', + validate: '_onValidate', + close_statement: '_onCloseStatement', + load_more: '_onLoadMore', + reload: 'reload', + search: '_onSearch', + navigation_move:'_onNavigationMove', + }, + config: _.extend({}, AbstractAction.prototype.config, { + // used to instantiate the model + Model: ReconciliationModel.StatementModel, + // used to instantiate the action interface + ActionRenderer: ReconciliationRenderer.StatementRenderer, + // used to instantiate each widget line + LineRenderer: ReconciliationRenderer.LineRenderer, + // used context params + params: ['statement_line_ids'], + // number of statements/partners/accounts to display + defaultDisplayQty: 10, + // number of moves lines displayed in 'match' mode + limitMoveLines: 15, + }), + + _onNavigationMove: function (ev) { + var non_reconciled_keys = _.keys(_.pick(this.model.lines, function(value, key, object) {return !value.reconciled})); + var currentIndex = _.indexOf(non_reconciled_keys, ev.data.handle); + var widget = false; + switch (ev.data.direction) { + case 'up': + ev.stopPropagation(); + widget = this._getWidget(non_reconciled_keys[currentIndex-1]); + break; + case 'down': + ev.stopPropagation(); + widget = this._getWidget(non_reconciled_keys[currentIndex+1]); + break; + case 'validate': + ev.stopPropagation(); + widget = this._getWidget(non_reconciled_keys[currentIndex]); + widget.$('caption .o_buttons button:visible').click(); + break; + } + if (widget) widget.$el.focus(); + }, + + /** + * @override + * @param {Object} params + * @param {Object} params.context + * + */ + init: function (parent, params) { + this._super.apply(this, arguments); + this.action_manager = parent; + this.params = params; + this.searchModelConfig.modelName = 'account.bank.statement.line'; + this.controlPanelProps.cp_content = {}; +// this.controlPanelParams.modelName = 'account.bank.statement.line'; + this.model = new this.config.Model(this, { + modelName: "account.reconciliation.widget", + defaultDisplayQty: params.params && params.params.defaultDisplayQty || this.config.defaultDisplayQty, + limitMoveLines: params.params && params.params.limitMoveLines || this.config.limitMoveLines, + }); + this.widgets = []; + // Adding values from the context is necessary to put this information in the url via the action manager so that + // you can retrieve it if the person shares his url or presses f5 + _.each(params.params, function (value, name) { + params.context[name] = name.indexOf('_ids') !== -1 ? _.map((value+'').split(','), parseFloat) : value; + }); + params.params = {}; + _.each(this.config.params, function (name) { + if (params.context[name]) { + params.params[name] = params.context[name]; + } + }); + }, + + /** + * instantiate the action renderer + * + * @override + */ + willStart: function () { + var self = this; + var def = this.model.load(this.params.context).then(this._super.bind(this)); + return def.then(function () { + if (!self.model.context || !self.model.context.active_id) { + self.model.context = {'active_id': self.params.context.active_id, + 'active_model': self.params.context.active_model}; + } + var journal_id = self.params.context.journal_id; + if (self.model.context.active_id && self.model.context.active_model === 'account.journal') { + journal_id = journal_id || self.model.context.active_id; + } + if (journal_id) { + var promise = self._rpc({ + model: 'account.journal', + method: 'read', + args: [journal_id, ['display_name']], + }); + } else { + var promise = Promise.resolve(); + } + return promise.then(function (result) { + var title = result && result[0] ? result[0]['display_name'] : self.params.display_name || '' + self._setTitle(title); + self.renderer = new self.config.ActionRenderer(self, self.model, { + 'bank_statement_line_id': self.model.bank_statement_line_id, + 'valuenow': self.model.valuenow, + 'valuemax': self.model.valuemax, + 'defaultDisplayQty': self.model.defaultDisplayQty, + 'title': title, + }); + }); + }); + }, + + reload: function() { + // On reload destroy all rendered line widget, reload data and then rerender widget + var self = this; + + self.$('.o_reconciliation_lines').addClass('d-none'); // prevent the browser from recomputing css after each destroy for HUGE perf improvement on a lot of lines + _.each(this.widgets, function(widget) { + widget.destroy(); + }); + this.widgets = []; + self.$('.o_reconciliation_lines').removeClass('d-none'); + return this.model.reload().then(function() { + return self._renderLinesOrRainbow(); + }); + }, + + _renderLinesOrRainbow: function() { + var self = this; + return self._renderLines().then(function() { + var initialState = self.renderer._initialState; + var valuenow = self.model.statement ? self.model.statement.value_min : initialState.valuenow; + var valuemax = self.model.statement ? self.model.statement.value_max : initialState.valuemax; + // No more lines to reconcile, trigger the rainbowman. + if(valuenow === valuemax){ + initialState.valuenow = valuenow; + initialState.context = self.model.getContext(); + self.renderer.showRainbowMan(initialState); + self.remove_cp(); + }else{ + // Create a notification if some lines have been reconciled automatically. + if(initialState.valuenow > 0) + self.renderer._renderNotifications(self.model.statement.notifications); + self._openFirstLine(); + self.renderer.$('[data-toggle="tooltip"]').tooltip(); + self.do_show(); + } + }); + }, + + /** + * append the renderer and instantiate the line renderers + * + * @override + */ + start: function () { + var self = this; + var args = arguments; + var sup = this._super; + + return this.renderer.prependTo(self.$('.o_form_sheet')).then(function() { + return self._renderLinesOrRainbow().then(function() { + self.do_show(); + return sup.apply(self, args); + }); + }); + }, + + /** + * update the control panel and breadcrumbs + * + * @override + */ + do_show: function () { + this._super.apply(this, arguments); + if (this.action_manager) { + this.$pager = $(QWeb.render('reconciliation.control.pager', {widget: this.renderer})); + this.controlPanelProps.cp_content = {$pager: this.$pager}; +// this.updateControlPanel({ +// clear: true, +// cp_content: { +// $pager: this.$pager, +// }, +// }); + this.renderer.$progress = this.$pager; + $(this.renderer.$progress).parent().css('width', '100%').css('padding-left', '0'); + } + }, + + remove_cp: function() { + this.controlPanelProps.cp_content = {}; +// this.updateControlPanel({ +// clear: true, +// }); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @param {string} handle + * @returns {Widget} widget line + */ + _getWidget: function (handle) { + return _.find(this.widgets, function (widget) {return widget.handle===handle;}); + }, + + /** + * + */ + _loadMore: function(qty) { + var self = this; + return this.model.loadMore(qty).then(function () { + return self._renderLines(); + }); + }, + /** + * sitch to 'match' the first available line + * + * @private + */ + _openFirstLine: function (previous_handle) { + var self = this; + previous_handle = previous_handle || 'rline0'; + var handle = _.compact(_.map(this.model.lines, function (line, handle) { + return (line.reconciled || (parseInt(handle.substr(5)) < parseInt(previous_handle.substr(5)))) ? null : handle; + }))[0]; + if (handle) { + var line = this.model.getLine(handle); + this.model.changeMode(handle, 'default').then(function () { + self._getWidget(handle).update(line); + }).guardedCatch(function(){ + self._getWidget(handle).update(line); + }).then(function() { + self._getWidget(handle).$el.focus(); + } + ); + } + return handle; + }, + + _forceUpdate: function() { + var self = this; + _.each(this.model.lines, function(handle) { + var widget = self._getWidget(handle['handle']); + if (widget && handle.need_update) { + widget.update(handle); + widget.need_update = false; + } + }) + }, + /** + * render line widget and append to view + * + * @private + */ + _renderLines: function () { + var self = this; + var linesToDisplay = this.model.getStatementLines(); + var linePromises = []; + _.each(linesToDisplay, function (line, handle) { + var widget = new self.config.LineRenderer(self, self.model, line); + widget.handle = handle; + self.widgets.push(widget); + linePromises.push(widget.appendTo(self.$('.o_reconciliation_lines'))); + }); + if (this.model.hasMoreLines() === false) { + this.renderer.hideLoadMoreButton(true); + } + else { + this.renderer.hideLoadMoreButton(false); + } + return Promise.all(linePromises); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * dispatch on the camelcased event name to model method then update the + * line renderer with the new state. If the mode was switched from 'inactive' + * to 'create' or 'match_rp' or 'match_other', the other lines switch to + * 'inactive' mode + * + * @private + * @param {OdooEvent} event + */ + _onAction: function (event) { + var self = this; + var handle = event.target.handle; + var current_line = this.model.getLine(handle); + this.model[_.str.camelize(event.name)](handle, event.data.data).then(function () { + var widget = self._getWidget(handle); + if (widget) { + widget.update(current_line); + } + if (current_line.mode !== 'inactive') { + _.each(self.model.lines, function (line, _handle) { + if (line.mode !== 'inactive' && _handle !== handle) { + self.model.changeMode(_handle, 'inactive'); + var widget = self._getWidget(_handle); + if (widget) { + widget.update(line); + } + } + }); + } + }); + }, + + /** + * @private + * @param {OdooEvent} ev + */ + _onSearch: function (ev) { + var self = this; + ev.stopPropagation(); + this.model.domain = ev.data.domain; + this.model.display_context = 'search'; + self.reload().then(function() { + self.renderer._updateProgressBar({ + 'valuenow': self.model.valuenow, + 'valuemax': self.model.valuemax, + }); + }); + }, + + _onActionPartialAmount: function(event) { + var self = this; + var handle = event.target.handle; + var line = this.model.getLine(handle); + var amount = this.model.getPartialReconcileAmount(handle, event.data); + self._getWidget(handle).updatePartialAmount(event.data.data, amount); + }, + + /** + * call 'closeStatement' model method + * + * @private + * @param {OdooEvent} event + */ + _onCloseStatement: function (event) { + var self = this; + return this.model.closeStatement().then(function (result) { + self.do_action({ + name: 'Bank Statements', + res_model: 'account.bank.statement.line', + res_id: result, + views: [[false, 'form']], + type: 'ir.actions.act_window', + view_mode: 'form', + }); + $('.o_reward').remove(); + }); + }, + /** + * Load more statement and render them + * + * @param {OdooEvent} event + */ + _onLoadMore: function (event) { + return this._loadMore(this.model.defaultDisplayQty); + }, + /** + * call 'validate' model method then destroy the + * validated lines and update the action renderer with the new status bar + * values and notifications then open the first available line + * + * @private + * @param {OdooEvent} event + */ + _onValidate: function (event) { + var self = this; + var handle = event.target.handle; + this.model.validate(handle).then(function (result) { + self.renderer.update({ + 'valuenow': self.model.valuenow, + 'valuemax': self.model.valuemax, + 'title': self.title, + 'time': Date.now()-self.time, + 'notifications': result.notifications, + 'context': self.model.getContext(), + }); + self._forceUpdate(); + _.each(result.handles, function (handle) { + var widget = self._getWidget(handle); + if (widget) { + widget.destroy(); + var index = _.findIndex(self.widgets, function (widget) {return widget.handle===handle;}); + self.widgets.splice(index, 1); + } + }); + // Get number of widget and if less than constant and if there are more to laod, load until constant + if (self.widgets.length < self.model.defaultDisplayQty + && self.model.valuemax - self.model.valuenow >= self.model.defaultDisplayQty) { + var toLoad = self.model.defaultDisplayQty - self.widgets.length; + self._loadMore(toLoad); + } + self._openFirstLine(handle); + }); + }, +}); + + +/** + * Widget used as action for 'account.move.line' and 'res.partner' for the + * manual reconciliation and mark data as reconciliate + */ +var ManualAction = StatementAction.extend({ + title: core._t('Journal Items to Reconcile'), + withSearchBar: false, + config: _.extend({}, StatementAction.prototype.config, { + Model: ReconciliationModel.ManualModel, + ActionRenderer: ReconciliationRenderer.ManualRenderer, + LineRenderer: ReconciliationRenderer.ManualLineRenderer, + params: ['company_ids', 'mode', 'partner_ids', 'account_ids'], + defaultDisplayQty: 30, + limitMoveLines: 15, + }), + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * call 'validate' model method then destroy the + * reconcilied lines, update the not reconcilied and update the action + * renderer with the new status bar values and notifications then open the + * first available line + * + * @private + * @param {OdooEvent} event + */ + _onValidate: function (event) { + var self = this; + var handle = event.target.handle; + var method = 'validate'; + this.model[method](handle).then(function (result) { + _.each(result.reconciled, function (handle) { + self._getWidget(handle).destroy(); + }); + _.each(result.updated, function (handle) { + self._getWidget(handle).update(self.model.getLine(handle)); + }); + self.renderer.update({ + valuenow: _.compact(_.invoke(self.widgets, 'isDestroyed')).length, + valuemax: self.widgets.length, + title: self.title, + time: Date.now()-self.time, + }); + if(!_.any(result.updated, function (handle) { + return self.model.getLine(handle).mode !== 'inactive'; + })) { + self._openFirstLine(handle); + } + }); + }, +}); + +core.action_registry.add('bank_statement_reconciliation_view', StatementAction); +core.action_registry.add('manual_reconciliation_view', ManualAction); + +return { + StatementAction: StatementAction, + ManualAction: ManualAction, +}; +}); diff --git a/base_accounting_kit/static/src/js/payment_model.js b/base_accounting_kit/static/src/js/payment_model.js new file mode 100644 index 0000000..07785c4 --- /dev/null +++ b/base_accounting_kit/static/src/js/payment_model.js @@ -0,0 +1,1881 @@ +odoo.define('base_accounting_kit.ReconciliationModel', function (require) { +"use strict"; + +var BasicModel = require('web.BasicModel'); +var field_utils = require('web.field_utils'); +var utils = require('web.utils'); +var session = require('web.session'); +var WarningDialog = require('web.CrashManager').WarningDialog; +var core = require('web.core'); +var _t = core._t; + + +/** + * Model use to fetch, format and update 'account.reconciliation.widget', + * datas allowing reconciliation + * + * The statement internal structure:: + * + * { + * valuenow: integer + * valuenow: valuemax + * [bank_statement_line_id]: { + * id: integer + * display_name: string + * } + * reconcileModels: [object] + * accounts: {id: code} + * } + * + * The internal structure of each line is:: + * + * { + * balance: { + * type: number - show/hide action button + * amount: number - real amount + * amount_str: string - formated amount + * account_code: string + * }, + * st_line: { + * partner_id: integer + * partner_name: string + * } + * mode: string ('inactive', 'match_rp', 'match_other', 'create') + * reconciliation_proposition: { + * id: number|string + * partial_amount: number + * invalid: boolean - through the invalid line (without account, label...) + * account_code: string + * date: string + * date_maturity: string + * label: string + * amount: number - real amount + * amount_str: string - formated amount + * [already_paid]: boolean + * [partner_id]: integer + * [partner_name]: string + * [account_code]: string + * [journal_id]: { + * id: integer + * display_name: string + * } + * [ref]: string + * [is_partially_reconciled]: boolean + * [to_check]: boolean + * [amount_currency_str]: string|false (amount in record currency) + * } + * mv_lines_match_rp: object - idem than reconciliation_proposition + * mv_lines_match_other: object - idem than reconciliation_proposition + * limitMoveLines: integer + * filter: string + * [createForm]: { + * account_id: { + * id: integer + * display_name: string + * } + * tax_ids: { + * id: integer + * display_name: string + * } + * analytic_account_id: { + * id: integer + * display_name: string + * } + * analytic_tag_ids: { + * } + * label: string + * amount: number, + * [journal_id]: { + * id: integer + * display_name: string + * } + * } + * } + */ +var StatementModel = BasicModel.extend({ + avoidCreate: false, + quickCreateFields: ['account_id', 'amount', 'analytic_account_id', 'label', 'tax_ids', 'force_tax_included', 'analytic_tag_ids', 'to_check'], + + // overridden in ManualModel + modes: ['create', 'match_rp', 'match_other'], + + /** + * @override + * + * @param {Widget} parent + * @param {object} options + */ + init: function (parent, options) { + this._super.apply(this, arguments); + this.reconcileModels = []; + this.lines = {}; + this.valuenow = 0; + this.valuemax = 0; + this.alreadyDisplayed = []; + this.domain = []; + this.defaultDisplayQty = options && options.defaultDisplayQty || 10; + this.limitMoveLines = options && options.limitMoveLines || 15; + this.display_context = 'init'; + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * add a reconciliation proposition from the matched lines + * We also display a warning if the user tries to add 2 line with different + * account type + * + * @param {string} handle + * @param {number} mv_line_id + * @returns {Promise} + */ + addProposition: function (handle, mv_line_id) { + var self = this; + var line = this.getLine(handle); + var prop = _.clone(_.find(line['mv_lines_'+line.mode], {'id': mv_line_id})); + this._addProposition(line, prop); + line['mv_lines_'+line.mode] = _.filter(line['mv_lines_'+line.mode], l => l['id'] != mv_line_id); + + // remove all non valid lines + line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (prop) {return prop && !prop.invalid;}); + + // Onchange the partner if not already set on the statement line. + if(!line.st_line.partner_id && line.reconciliation_proposition + && line.reconciliation_proposition.length == 1 && prop.partner_id && line.type === undefined){ + return this.changePartner(handle, {'id': prop.partner_id, 'display_name': prop.partner_name}, true); + } + + return Promise.all([ + this._computeLine(line), + this._performMoveLine(handle, 'match_rp', line.mode == 'match_rp'? 1 : 0), + this._performMoveLine(handle, 'match_other', line.mode == 'match_other'? 1 : 0) + ]); + }, + /** + * change the filter for the target line and fetch the new matched lines + * + * @param {string} handle + * @param {string} filter + * @returns {Promise} + */ + changeFilter: function (handle, filter) { + var line = this.getLine(handle); + line['filter_'+line.mode] = filter; + line['mv_lines_'+line.mode] = []; + return this._performMoveLine(handle, line.mode); + }, + /** + * change the mode line ('inactive', 'match_rp', 'match_other', 'create'), + * and fetch the new matched lines or prepare to create a new line + * + * ``match_rp`` + * display the matched lines from receivable/payable accounts, the user + * can select the lines to apply there as proposition + * ``match_other`` + * display the other matched lines, the user can select the lines to apply + * there as proposition + * ``create`` + * display fields and quick create button to create a new proposition + * for the reconciliation + * + * @param {string} handle + * @param {'inactive' | 'match_rp' | 'create'} mode + * @returns {Promise} + */ + changeMode: function (handle, mode) { + var self = this; + var line = this.getLine(handle); + if (mode === 'default') { + var match_requests = self.modes.filter(x => x.startsWith('match')).map(x => this._performMoveLine(handle, x)) + return Promise.all(match_requests).then(function() { + return self.changeMode(handle, self._getDefaultMode(handle)); + }); + } + if (mode === 'next') { + var available_modes = self._getAvailableModes(handle) + mode = available_modes[(available_modes.indexOf(line.mode) + 1) % available_modes.length]; + } + line.mode = mode; + if (['match_rp', 'match_other'].includes(line.mode)) { + if (!(line['mv_lines_' + line.mode] && line['mv_lines_' + line.mode].length)) { + return this._performMoveLine(handle, line.mode); + } else { + return this._formatMoveLine(handle, line.mode, []); + } + } + if (line.mode === 'create') { + return this.createProposition(handle); + } + return Promise.resolve(); + }, + /** + * fetch the more matched lines + * + * @param {string} handle + * @returns {Promise} + */ + changeOffset: function (handle) { + var line = this.getLine(handle); + return this._performMoveLine(handle, line.mode); + }, + /** + * change the partner on the line and fetch the new matched lines + * + * @param {string} handle + * @param {bool} preserveMode + * @param {Object} partner + * @param {string} partner.display_name + * @param {number} partner.id + * @returns {Promise} + */ + changePartner: function (handle, partner, preserveMode) { + var self = this; + var line = this.getLine(handle); + line.st_line.partner_id = partner && partner.id; + line.st_line.partner_name = partner && partner.display_name || ''; + line.mv_lines_match_rp = []; + line.mv_lines_match_other = []; + return Promise.resolve(partner && this._changePartner(handle, partner.id)) + .then(function() { + if(line.st_line.partner_id){ + _.each(line.reconciliation_proposition, function(prop){ + if(prop.partner_id != line.st_line.partner_id){ + line.reconciliation_proposition = []; + return false; + } + }); + } + return self._computeLine(line); + }) + .then(function () { + return self.changeMode(handle, preserveMode ? line.mode : 'default', true); + }) + + }, + /** + * close the statement + * @returns {Promise<number>} resolves to the res_id of the closed statements + */ + closeStatement: function () { + var self = this; + return this._rpc({ + model: 'account.bank.statement.line', + method: 'button_confirm_bank', + args: [self.bank_statement_line_id.id], + }) + .then(function () { + return self.bank_statement_line_id.id; + }); + }, + /** + * + * then open the first available line + * + * @param {string} handle + * @returns {Promise} + */ + createProposition: function (handle) { + var line = this.getLine(handle); + var prop = _.filter(line.reconciliation_proposition, '__focus'); + prop = this._formatQuickCreate(line); + line.reconciliation_proposition.push(prop); + line.createForm = _.pick(prop, this.quickCreateFields); + return this._computeLine(line); + }, + /** + * Return context information and journal_id + * @returns {Object} context + */ + getContext: function () { + return this.context; + }, + /** + * Return the lines that needs to be displayed by the widget + * + * @returns {Object} lines that are loaded and not yet displayed + */ + getStatementLines: function () { + var self = this; + var linesToDisplay = _.pick(this.lines, function(value, key, object) { + if (value.visible === true && self.alreadyDisplayed.indexOf(key) === -1) { + self.alreadyDisplayed.push(key); + return object; + } + }); + return linesToDisplay; + }, + /** + * Return a boolean telling if load button needs to be displayed or not + * overridden in ManualModel + * + * @returns {boolean} true if load more button needs to be displayed + */ + hasMoreLines: function () { + var notDisplayed = _.filter(this.lines, function(line) { return !line.visible; }); + if (notDisplayed.length > 0) { + return true; + } + return false; + }, + /** + * get the line data for this handle + * + * @param {Object} handle + * @returns {Object} + */ + getLine: function (handle) { + return this.lines[handle]; + }, + /** + * load data from + * + * - 'account.bank.statement' fetch the line id and bank_statement_id info + * - 'account.reconcile.model' fetch all reconcile model (for quick add) + * - 'account.account' fetch all account code + * - 'account.reconciliation.widget' fetch each line data + * + * overridden in ManualModel + * @param {Object} context + * @param {number[]} context.statement_line_ids + * @returns {Promise} + */ + load: function (context) { + var self = this; + this.context = context; + this.statement_line_ids = context.statement_line_ids; + if (this.statement_line_ids === undefined) { + // This could be undefined if the user pressed F5, take everything as fallback instead of rainbowman + return self._rpc({ + model: 'account.bank.statement.line', + method: 'search_read', + fields: ['id'], + domain: [['journal_id', '=?', context.active_id]], + }).then(function (result) { + self.statement_line_ids = result.map(r => r.id); + return self.reload() + }) + } else { + return self.reload(); + } + + }, + /** + * Load more bank statement line + * + * @param {integer} qty quantity to load + * @returns {Promise} + */ + loadMore: function(qty) { + if (qty === undefined) { + qty = this.defaultDisplayQty; + } + var ids = _.pluck(this.lines, 'id'); + ids = ids.splice(this.pagerIndex, qty); + this.pagerIndex += qty; + return this.loadData(ids, this._getExcludedIds()); + }, + /** + * RPC method to load informations on lines + * overridden in ManualModel + * + * @param {Array} ids ids of bank statement line passed to rpc call + * @param {Array} excluded_ids list of move_line ids that needs to be excluded from search + * @returns {Promise} + */ + loadData: function(ids) { + var self = this; + var excluded_ids = this._getExcludedIds(); + return self._rpc({ + model: 'account.reconciliation.widget', + method: 'get_bank_statement_line_data', + args: [ids, excluded_ids], + context: self.context, + }) + .then(function(res){ + return self._formatLine(res['lines']); + }) + }, + /** + * Reload all data + */ + reload: function() { + var self = this; + self.alreadyDisplayed = []; + self.lines = {}; + self.pagerIndex = 0; + var def_statement = this._rpc({ + model: 'account.reconciliation.widget', + method: 'get_bank_statement_data', + kwargs: {"bank_statement_line_ids":self.statement_line_ids, "srch_domain":self.domain}, + context: self.context, + }) + .then(function (statement) { + self.statement = statement; + self.bank_statement_line_id = self.statement_line_ids.length === 1 ? {id: self.statement_line_ids[0], display_name: statement.statement_name} : false; + self.valuenow = self.valuenow || statement.value_min; + self.valuemax = self.valuemax || statement.value_max; + self.context.journal_id = statement.journal_id; + _.each(statement.lines, function (res) { + var handle = _.uniqueId('rline'); + self.lines[handle] = { + id: res.st_line.id, + partner_id: res.st_line.partner_id, + handle: handle, + reconciled: false, + mode: 'inactive', + mv_lines_match_rp: [], + mv_lines_match_other: [], + filter_match_rp: "", + filter_match_other: "", + reconciliation_proposition: [], + reconcileModels: [], + }; + }); + }); + var domainReconcile = []; + if (self.context && self.context.company_ids) { + domainReconcile.push(['company_id', 'in', self.context.company_ids]); + } + if (self.context && self.context.active_model === 'account.journal' && self.context.active_ids) { + domainReconcile.push('|'); + domainReconcile.push(['match_journal_ids', '=', false]); + domainReconcile.push(['match_journal_ids', 'in', self.context.active_ids]); + } + var def_reconcileModel = this._loadReconciliationModel({domainReconcile: domainReconcile}); + var def_account = this._rpc({ + model: 'account.account', + method: 'search_read', + fields: ['code'], + }) + .then(function (accounts) { + self.accounts = _.object(_.pluck(accounts, 'id'), _.pluck(accounts, 'code')); + }); + var def_taxes = self._loadTaxes(); + return Promise.all([def_statement, def_reconcileModel, def_account, def_taxes]).then(function () { + _.each(self.lines, function (line) { + line.reconcileModels = self.reconcileModels; + }); + var ids = _.pluck(self.lines, 'id'); + ids = ids.splice(0, self.defaultDisplayQty); + self.pagerIndex = ids.length; + return self._formatLine(self.statement.lines); + }); + }, + _readAnalyticTags: function (params) { + var self = this; + this.analyticTags = {}; + if (!params || !params.res_ids || !params.res_ids.length) { + return $.when(); + } + var fields = (params && params.fields || []).concat(['id', 'display_name']); + return this._rpc({ + model: 'account.analytic.tag', + method: 'read', + args: [ + params.res_ids, + fields, + ], + }).then(function (tags) { + for (var i=0; i<tags.length; i++) { + var tag = tags[i]; + self.analyticTags[tag.id] = tag; + } + }); + }, + _loadReconciliationModel: function (params) { + var self = this; + return this._rpc({ + model: 'account.reconcile.model', + method: 'search_read', + domain: params.domainReconcile || [], + }) + .then(function (reconcileModels) { + var analyticTagIds = []; + for (var i=0; i<reconcileModels.length; i++) { + var modelTags = reconcileModels[i].analytic_tag_ids || []; + for (var j=0; j<modelTags.length; j++) { + if (analyticTagIds.indexOf(modelTags[j]) === -1) { + analyticTagIds.push(modelTags[j]); + } + } + } + return self._readAnalyticTags({res_ids: analyticTagIds}).then(function () { + for (var i=0; i<reconcileModels.length; i++) { + var recModel = reconcileModels[i]; + var analyticTagData = []; + var modelTags = reconcileModels[i].analytic_tag_ids || []; + for (var j=0; j<modelTags.length; j++) { + var tagId = modelTags[j]; + analyticTagData.push([tagId, self.analyticTags[tagId].display_name]) + } + recModel.analytic_tag_ids = analyticTagData; + } + self.reconcileModels = reconcileModels; + }); + }); + }, + _loadTaxes: function(){ + var self = this; + self.taxes = {}; + return this._rpc({ + model: 'account.tax', + method: 'search_read', + fields: ['price_include', 'name'], + }).then(function (taxes) { + _.each(taxes, function(tax){ + self.taxes[tax.id] = { + price_include: tax.price_include, + display_name: tax.name, + }; + }); + return taxes; + }); + }, + /** + * Add lines into the propositions from the reconcile model + * Can add 2 lines, and each with its taxes. The second line become editable + * in the create mode. + * + * @see 'updateProposition' method for more informations about the + * 'amount_type' + * + * @param {string} handle + * @param {integer} reconcileModelId + * @returns {Promise} + */ + quickCreateProposition: function (handle, reconcileModelId) { + var self = this; + var line = this.getLine(handle); + var reconcileModel = _.find(this.reconcileModels, function (r) {return r.id === reconcileModelId;}); + var fields = ['account_id', 'amount', 'amount_type', 'analytic_account_id', 'journal_id', 'label', 'force_tax_included', 'tax_ids', 'analytic_tag_ids', 'to_check', 'amount_from_label_regex', 'decimal_separator']; + this._blurProposition(handle); + var focus = this._formatQuickCreate(line, _.pick(reconcileModel, fields)); + focus.reconcileModelId = reconcileModelId; + line.reconciliation_proposition.push(focus); + var defs = []; + if (reconcileModel.has_second_line) { + defs.push(self._computeLine(line).then(function() { + var second = {}; + _.each(fields, function (key) { + second[key] = ("second_"+key) in reconcileModel ? reconcileModel["second_"+key] : reconcileModel[key]; + }); + var second_focus = self._formatQuickCreate(line, second); + second_focus.reconcileModelId = reconcileModelId; + line.reconciliation_proposition.push(second_focus); + self._computeReconcileModels(handle, reconcileModelId); + })) + } + return Promise.all(defs).then(function() { + line.createForm = _.pick(focus, self.quickCreateFields); + return self._computeLine(line); + }) + }, + /** + * Remove a proposition and switch to an active mode ('create' or 'match_rp' or 'match_other') + * overridden in ManualModel + * + * @param {string} handle + * @param {number} id (move line id) + * @returns {Promise} + */ + removeProposition: function (handle, id) { + var self = this; + var line = this.getLine(handle); + var defs = []; + var prop = _.find(line.reconciliation_proposition, {'id' : id}); + if (prop) { + line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (p) { + return p.id !== prop.id && p.id !== prop.link && p.link !== prop.id && (!p.link || p.link !== prop.link); + }); + if (prop['reconcileModelId'] === undefined) { + if (['receivable', 'payable', 'liquidity'].includes(prop.account_type)) { + line.mv_lines_match_rp.unshift(prop); + } else { + line.mv_lines_match_other.unshift(prop); + } + } + + // No proposition left and then, reset the st_line partner. + if(line.reconciliation_proposition.length == 0 && line.st_line.has_no_partner) + defs.push(self.changePartner(line.handle)); + } + line.mode = (id || line.mode !== "create") && isNaN(id) ? 'create' : 'match_rp'; + defs.push(this._computeLine(line)); + return Promise.all(defs).then(function() { + return self.changeMode(handle, line.mode, true); + }) + }, + getPartialReconcileAmount: function(handle, data) { + var line = this.getLine(handle); + var formatOptions = { + currency_id: line.st_line.currency_id, + noSymbol: true, + }; + var prop = _.find(line.reconciliation_proposition, {'id': data.data}); + if (prop) { + var amount = prop.partial_amount || prop.amount; + // Check if we can get a partial amount that would directly set balance to zero + var partial = Math.abs(line.balance.amount + amount); + if (Math.abs(line.balance.amount) >= Math.abs(amount)) { + amount = Math.abs(amount); + } else if (partial <= Math.abs(prop.amount) && partial >= 0) { + amount = partial; + } else { + amount = Math.abs(amount); + } + return field_utils.format.monetary(amount, {}, formatOptions); + } + }, + /** + * Force the partial reconciliation to display the reconciliate button. + * + * @param {string} handle + * @returns {Promise} + */ + partialReconcile: function(handle, data) { + var line = this.getLine(handle); + var prop = _.find(line.reconciliation_proposition, {'id' : data.mvLineId}); + if (prop) { + var amount = data.amount; + try { + amount = field_utils.parse.float(data.amount); + } + catch (err) { + amount = NaN; + } + // Amount can't be greater than line.amount and can not be negative and must be a number + // the amount we receive will be a string, so take sign of previous line amount in consideration in order to put + // the amount in the correct left or right column + if (amount >= Math.abs(prop.amount) || amount <= 0 || isNaN(amount)) { + delete prop.partial_amount_str; + delete prop.partial_amount; + if (isNaN(amount) || amount < 0) { + this.do_warn(_.str.sprintf(_t('The amount %s is not a valid partial amount'), data.amount)); + } + return this._computeLine(line); + } + else { + var format_options = { currency_id: line.st_line.currency_id }; + prop.partial_amount = (prop.amount > 0 ? 1 : -1)*amount; + prop.partial_amount_str = field_utils.format.monetary(Math.abs(prop.partial_amount), {}, format_options); + } + } + return this._computeLine(line); + }, + /** + * Change the value of the editable proposition line or create a new one. + * + * If the editable line comes from a reconcile model with 2 lines + * and their 'amount_type' is "percent" + * and their total equals 100% (this doesn't take into account the taxes + * who can be included or not) + * Then the total is recomputed to have 100%. + * + * @param {string} handle + * @param {*} values + * @returns {Promise} + */ + updateProposition: function (handle, values) { + var self = this; + var line = this.getLine(handle); + var prop = _.last(_.filter(line.reconciliation_proposition, '__focus')); + if ('to_check' in values && values.to_check === false) { + // check if we have another line with to_check and if yes don't change value of this proposition + prop.to_check = line.reconciliation_proposition.some(function(rec_prop, index) { + return rec_prop.id !== prop.id && rec_prop.to_check; + }); + } + if (!prop) { + prop = this._formatQuickCreate(line); + line.reconciliation_proposition.push(prop); + } + _.each(values, function (value, fieldName) { + if (fieldName === 'analytic_tag_ids') { + switch (value.operation) { + case "ADD_M2M": + // handle analytic_tag selection via drop down (single dict) and + // full widget (array of dict) + var vids = _.isArray(value.ids) ? value.ids : [value.ids]; + _.each(vids, function (val) { + if (!_.findWhere(prop.analytic_tag_ids, {id: val.id})) { + prop.analytic_tag_ids.push(val); + } + }); + break; + case "FORGET": + var id = self.localData[value.ids[0]].ref; + prop.analytic_tag_ids = _.filter(prop.analytic_tag_ids, function (val) { + return val.id !== id; + }); + break; + } + } + else if (fieldName === 'tax_ids') { + switch(value.operation) { + case "ADD_M2M": + prop.__tax_to_recompute = true; + var vids = _.isArray(value.ids) ? value.ids : [value.ids]; + _.each(vids, function(val){ + if (!_.findWhere(prop.tax_ids, {id: val.id})) { + value.ids.price_include = self.taxes[val.id] ? self.taxes[val.id].price_include : false; + prop.tax_ids.push(val); + } + }); + break; + case "FORGET": + prop.__tax_to_recompute = true; + var id = self.localData[value.ids[0]].ref; + prop.tax_ids = _.filter(prop.tax_ids, function (val) { + return val.id !== id; + }); + break; + } + } + else { + prop[fieldName] = values[fieldName]; + } + }); + if ('account_id' in values) { + prop.account_code = prop.account_id ? this.accounts[prop.account_id.id] : ''; + } + if ('amount' in values) { + prop.base_amount = values.amount; + if (prop.reconcileModelId) { + this._computeReconcileModels(handle, prop.reconcileModelId); + } + } + if ('force_tax_included' in values || 'amount' in values || 'account_id' in values) { + prop.__tax_to_recompute = true; + } + line.createForm = _.pick(prop, this.quickCreateFields); + // If you check/uncheck the force_tax_included box, reset the createForm amount. + if(prop.base_amount) + line.createForm.amount = prop.base_amount; + if (prop.tax_ids.length !== 1 ) { + // When we have 0 or more than 1 taxes, reset the base_amount and force_tax_included, otherwise weird behavior can happen + prop.amount = prop.base_amount; + line.createForm.force_tax_included = false; + } + return this._computeLine(line); + }, + /** + * Format the value and send it to 'account.reconciliation.widget' model + * Update the number of validated lines + * overridden in ManualModel + * + * @param {(string|string[])} handle + * @returns {Promise<Object>} resolved with an object who contains + * 'handles' key + */ + validate: function (handle) { + var self = this; + this.display_context = 'validate'; + var handles = []; + if (handle) { + handles = [handle]; + } else { + _.each(this.lines, function (line, handle) { + if (!line.reconciled && line.balance && !line.balance.amount && line.reconciliation_proposition.length) { + handles.push(handle); + } + }); + } + var ids = []; + var values = []; + var handlesPromises = []; + _.each(handles, function (handle) { + var line = self.getLine(handle); + var props = _.filter(line.reconciliation_proposition, function (prop) {return !prop.invalid;}); + var computeLinePromise; + if (props.length === 0) { + // Usability: if user has not chosen any lines and click validate, it has the same behavior + // as creating a write-off of the same amount. + props.push(self._formatQuickCreate(line, { + account_id: [line.st_line.open_balance_account_id, self.accounts[line.st_line.open_balance_account_id]], + })); + // update balance of line otherwise it won't be to zero and another line will be added + line.reconciliation_proposition.push(props[0]); + computeLinePromise = self._computeLine(line); + } + ids.push(line.id); + handlesPromises.push(Promise.resolve(computeLinePromise).then(function() { + var values_dict = { + "partner_id": line.st_line.partner_id, + "counterpart_aml_dicts": _.map(_.filter(props, function (prop) { + return !isNaN(prop.id) && !prop.already_paid; + }), self._formatToProcessReconciliation.bind(self, line)), + "payment_aml_ids": _.pluck(_.filter(props, function (prop) { + return !isNaN(prop.id) && prop.already_paid; + }), 'id'), + "new_aml_dicts": _.map(_.filter(props, function (prop) { + return isNaN(prop.id) && prop.display; + }), self._formatToProcessReconciliation.bind(self, line)), + "to_check": line.to_check, + }; + + // If the lines are not fully balanced, create an unreconciled amount. + // line.st_line.currency_id is never false here because its equivalent to + // statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.journal_id.company_id.currency_id (Python-side). + // see: get_statement_line_for_reconciliation_widget method in account/models/account_bank_statement.py for more details + var currency = session.get_currency(line.st_line.currency_id); + var balance = line.balance.amount; + if (!utils.float_is_zero(balance, currency.digits[1])) { + var unreconciled_amount_dict = { + 'account_id': line.st_line.open_balance_account_id, + 'credit': balance > 0 ? balance : 0, + 'debit': balance < 0 ? -balance : 0, + 'name': line.st_line.name + ' : ' + _t("Open balance"), + }; + values_dict['new_aml_dicts'].push(unreconciled_amount_dict); + } + values.push(values_dict); + line.reconciled = true; + })); + + _.each(self.lines, function(other_line) { + if (other_line != line) { + var filtered_prop = other_line.reconciliation_proposition.filter(p => !line.reconciliation_proposition.map(l => l.id).includes(p.id)); + if (filtered_prop.length != other_line.reconciliation_proposition.length) { + other_line.need_update = true; + other_line.reconciliation_proposition = filtered_prop; + } + self._computeLine(line); + } + }) + }); + + return Promise.all(handlesPromises).then(function() { + return self._rpc({ + model: 'account.reconciliation.widget', + method: 'process_bank_statement_line', + args: [ids, values], + context: self.context, + }) + .then(self._validatePostProcess.bind(self)) + .then(function () { + self.valuenow += handles.length; + return {handles: handles}; + }); + }); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * add a line proposition after checking receivable and payable accounts constraint + * + * @private + * @param {Object} line + * @param {Object} prop + */ + _addProposition: function (line, prop) { + line.reconciliation_proposition.push(prop); + }, + /** + * stop the editable proposition line and remove it if it's invalid then + * compute the line + * + * See :func:`_computeLine` + * + * @private + * @param {string} handle + * @returns {Promise} + */ + _blurProposition: function (handle) { + var line = this.getLine(handle); + line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (l) { + l.__focus = false; + return !l.invalid; + }); + }, + /** + * When changing partner, read property_account_receivable and payable + * of that partner because the counterpart account might cahnge depending + * on the partner + * + * @private + * @param {string} handle + * @param {integer} partner_id + * @returns {Promise} + */ + _changePartner: function (handle, partner_id) { + var self = this; + return this._rpc({ + model: 'res.partner', + method: 'read', + args: [partner_id, ["property_account_receivable_id", "property_account_payable_id"]], + }).then(function (result) { + if (result.length > 0) { + var line = self.getLine(handle); + self.lines[handle].st_line.open_balance_account_id = line.balance.amount < 0 ? result[0]['property_account_payable_id'][0] : result[0]['property_account_receivable_id'][0]; + } + }); + }, + /** + * Calculates the balance; format each proposition amount_str and mark as + * invalid the line with empty account_id, amount or label + * Check the taxes server side for each updated propositions with tax_ids + * extended by ManualModel + * + * @private + * @param {Object} line + * @returns {Promise} + */ + _computeLine: function (line) { + //balance_type + var self = this; + + // compute taxes + var tax_defs = []; + var reconciliation_proposition = []; + var formatOptions = { + currency_id: line.st_line.currency_id, + }; + line.to_check = false; + _.each(line.reconciliation_proposition, function (prop) { + if (prop.to_check) { + // If one of the proposition is to_check, set the global to_check flag to true + line.to_check = true; + } + if (prop.tax_repartition_line_id) { + if (!_.find(line.reconciliation_proposition, {'id': prop.link}).__tax_to_recompute) { + reconciliation_proposition.push(prop); + } + return; + } + if (!prop.already_paid && parseInt(prop.id)) { + prop.is_move_line = true; + } + reconciliation_proposition.push(prop); + + if (prop.tax_ids && prop.tax_ids.length && prop.__tax_to_recompute && prop.base_amount) { + reconciliation_proposition = _.filter(reconciliation_proposition, function (p) { + return !p.tax_repartition_line_id || p.link !== prop.id; + }); + var args = [prop.tax_ids.map(function(el){return el.id;}), prop.base_amount, formatOptions.currency_id]; + var add_context = {'round': true}; + if(prop.tax_ids.length === 1 && line.createForm && line.createForm.force_tax_included) + add_context.force_price_include = true; + tax_defs.push(self._rpc({ + model: 'account.tax', + method: 'json_friendly_compute_all', + args: args, + context: $.extend({}, self.context || {}, add_context), + }) + .then(function (result) { + _.each(result.taxes, function(tax){ + var tax_prop = self._formatQuickCreate(line, { + 'link': prop.id, + 'tax_ids': tax.tax_ids, + 'tax_repartition_line_id': tax.tax_repartition_line_id, + 'tag_ids': tax.tag_ids, + 'amount': tax.amount, + 'label': prop.label ? prop.label + " " + tax.name : tax.name, + 'date': prop.date, + 'account_id': tax.account_id ? [tax.account_id, null] : prop.account_id, + 'analytic': tax.analytic, + '__focus': false + }); + + prop.tax_exigible = tax.tax_exigibility === 'on_payment' ? true : undefined; + prop.amount = tax.base; + prop.amount_str = field_utils.format.monetary(Math.abs(prop.amount), {}, formatOptions); + prop.invalid = !self._isValid(prop); + + tax_prop.amount_str = field_utils.format.monetary(Math.abs(tax_prop.amount), {}, formatOptions); + tax_prop.invalid = prop.invalid; + + reconciliation_proposition.push(tax_prop); + }); + + prop.tag_ids = result.base_tags; + })); + } else { + prop.amount_str = field_utils.format.monetary(Math.abs(prop.amount), {}, formatOptions); + prop.display = self._isDisplayedProposition(prop); + prop.invalid = !self._isValid(prop); + } + }); + + return Promise.all(tax_defs).then(function () { + _.each(reconciliation_proposition, function (prop) { + prop.__tax_to_recompute = false; + }); + line.reconciliation_proposition = reconciliation_proposition; + + var amount_currency = 0; + var total = line.st_line.amount || 0; + var isOtherCurrencyId = _.uniq(_.pluck(_.reject(reconciliation_proposition, 'invalid'), 'currency_id')); + isOtherCurrencyId = isOtherCurrencyId.length === 1 && !total && isOtherCurrencyId[0] !== formatOptions.currency_id ? isOtherCurrencyId[0] : false; + + _.each(reconciliation_proposition, function (prop) { + if (!prop.invalid) { + total -= prop.partial_amount || prop.amount; + if (isOtherCurrencyId) { + amount_currency -= (prop.amount < 0 ? -1 : 1) * Math.abs(prop.amount_currency); + } + } + }); + var company_currency = session.get_currency(line.st_line.currency_id); + var company_precision = company_currency && company_currency.digits[1] || 2; + total = utils.round_decimals(total, company_precision) || 0; + if(isOtherCurrencyId){ + var other_currency = session.get_currency(isOtherCurrencyId); + var other_precision = other_currency && other_currency.digits[1] || 2; + amount_currency = utils.round_decimals(amount_currency, other_precision); + } + line.balance = { + amount: total, + amount_str: field_utils.format.monetary(Math.abs(total), {}, formatOptions), + currency_id: isOtherCurrencyId, + amount_currency: isOtherCurrencyId ? amount_currency : total, + amount_currency_str: isOtherCurrencyId ? field_utils.format.monetary(Math.abs(amount_currency), {}, { + currency_id: isOtherCurrencyId + }) : false, + account_code: self.accounts[line.st_line.open_balance_account_id], + }; + line.balance.show_balance = line.balance.amount_currency != 0; + line.balance.type = line.balance.amount_currency ? (line.st_line.partner_id ? 0 : -1) : 1; + }); + }, + /** + * + * + * @private + * @param {string} handle + * @param {integer} reconcileModelId + */ + _computeReconcileModels: function (handle, reconcileModelId) { + var line = this.getLine(handle); + // if quick create with 2 lines who use 100%, change the both values in same time + var props = _.filter(line.reconciliation_proposition, {'reconcileModelId': reconcileModelId, '__focus': true}); + if (props.length === 2 && props[0].percent && props[1].percent) { + if (props[0].percent + props[1].percent === 100) { + props[0].base_amount = props[0].amount = line.st_line.amount - props[1].base_amount; + props[0].__tax_to_recompute = true; + } + } + }, + /** + * format a name_get into an object {id, display_name}, idempotent + * + * @private + * @param {Object|Array} [value] data or name_get + */ + _formatNameGet: function (value) { + return value ? (value.id ? value : {'id': value[0], 'display_name': value[1]}) : false; + }, + _formatMany2ManyTags: function (value) { + var res = []; + for (var i=0, len=value.length; i<len; i++) { + res[i] = {'id': value[i][0], 'display_name': value[i][1]}; + } + return res; + }, + _formatMany2ManyTagsTax: function(value) { + var res = []; + for (var i=0; i<value.length; i++) { + res.push({id: value[i], display_name: this.taxes[value[i]] ? this.taxes[value[i]].display_name : ''}); + } + return res; + }, + /** + * Format each propositions (amount, label, account_id) + * extended in ManualModel + * + * @private + * @param {Object} line + * @param {Object[]} props + */ + _formatLineProposition: function (line, props) { + var self = this; + if (props.length) { + _.each(props, function (prop) { + prop.amount = prop.debit || -prop.credit; + prop.label = prop.name; + prop.account_id = self._formatNameGet(prop.account_id || line.account_id); + prop.is_partially_reconciled = prop.amount_str !== prop.total_amount_str; + prop.to_check = !!prop.to_check; + }); + } + }, + /** + * Format each server lines and propositions and compute all lines + * overridden in ManualModel + * + * @see '_computeLine' + * + * @private + * @param {Object[]} lines + * @returns {Promise} + */ + _formatLine: function (lines) { + var self = this; + var defs = []; + _.each(lines, function (data) { + var line = _.find(self.lines, function (l) { + return l.id === data.st_line.id; + }); + line.visible = true; + line.limitMoveLines = self.limitMoveLines; + _.extend(line, data); + self._formatLineProposition(line, line.reconciliation_proposition); + if (!line.reconciliation_proposition.length) { + delete line.reconciliation_proposition; + } + + // No partner set on st_line and all matching amls have the same one: set it on the st_line. + defs.push( + self._computeLine(line) + .then(function(){ + if(!line.st_line.partner_id && line.reconciliation_proposition.length > 0){ + var hasDifferentPartners = function(prop){ + return !prop.partner_id || prop.partner_id != line.reconciliation_proposition[0].partner_id; + }; + + if(!_.any(line.reconciliation_proposition, hasDifferentPartners)){ + return self.changePartner(line.handle, { + 'id': line.reconciliation_proposition[0].partner_id, + 'display_name': line.reconciliation_proposition[0].partner_name, + }, true); + } + }else if(!line.st_line.partner_id && line.partner_id && line.partner_name){ + return self.changePartner(line.handle, { + 'id': line.partner_id, + 'display_name': line.partner_name, + }, true); + } + return true; + }) + .then(function(){ + return data.write_off ? self.quickCreateProposition(line.handle, data.model_id) : true; + }) + .then(function() { + // If still no partner set, take the one from context, if it exists + if (!line.st_line.partner_id && self.context.partner_id && self.context.partner_name) { + return self.changePartner(line.handle, { + 'id': self.context.partner_id, + 'display_name': self.context.partner_name, + }, true); + } + return true; + }) + ); + }); + return Promise.all(defs); + }, + /** + * Format the server value then compute the line + * overridden in ManualModel + * + * @see '_computeLine' + * + * @private + * @param {string} handle + * @param {Object[]} mv_lines + * @returns {Promise} + */ + _formatMoveLine: function (handle, mode, mv_lines) { + var self = this; + var line = this.getLine(handle); + line['mv_lines_'+mode] = _.uniq(line['mv_lines_'+mode].concat(mv_lines), l => l.id); + if (mv_lines[0]){ + line['remaining_'+mode] = mv_lines[0].recs_count - mv_lines.length; + } else if (line['mv_lines_'+mode].lenght == 0) { + line['remaining_'+mode] = 0; + } + this._formatLineProposition(line, mv_lines); + + if ((line.mode == 'match_other' || line.mode == "match_rp") && !line['mv_lines_'+mode].length && !line['filter_'+mode].length) { + line.mode = self._getDefaultMode(handle); + if (line.mode !== 'match_rp' && line.mode !== 'match_other' && line.mode !== 'inactive') { + return this._computeLine(line).then(function () { + return self.createProposition(handle); + }); + } + } else { + return this._computeLine(line); + } + }, + /** + * overridden in ManualModel + */ + _getDefaultMode: function(handle) { + var line = this.getLine(handle); + if (line.balance.amount === 0 + && (!line.st_line.mv_lines_match_rp || line.st_line.mv_lines_match_rp.length === 0) + && (!line.st_line.mv_lines_match_other || line.st_line.mv_lines_match_other.length === 0)) { + return 'inactive'; + } + if (line.mv_lines_match_rp && line.mv_lines_match_rp.length) { + return 'match_rp'; + } + if (line.mv_lines_match_other && line.mv_lines_match_other.length) { + return 'match_other'; + } + return 'create'; + }, + _getAvailableModes: function(handle) { + var line = this.getLine(handle); + var modes = [] + if (line.mv_lines_match_rp && line.mv_lines_match_rp.length) { + modes.push('match_rp') + } + if (line.mv_lines_match_other && line.mv_lines_match_other.length) { + modes.push('match_other') + } + modes.push('create') + return modes + }, + /** + * Apply default values for the proposition, format datas and format the + * base_amount with the decimal number from the currency + * extended in ManualModel + * + * @private + * @param {Object} line + * @param {Object} values + * @returns {Object} + */ + _formatQuickCreate: function (line, values) { + values = values || {}; + var today = new moment().utc().format(); + var account = this._formatNameGet(values.account_id); + var formatOptions = { + currency_id: line.st_line.currency_id, + }; + var amount; + switch(values.amount_type) { + case 'percentage': + amount = line.balance.amount * values.amount / 100; + break; + case 'regex': + var matching = line.st_line.name.match(new RegExp(values.amount_from_label_regex)) + amount = 0; + if (matching && matching.length == 2) { + matching = matching[1].replace(new RegExp('\\D' + values.decimal_separator, 'g'), ''); + matching = matching.replace(values.decimal_separator, '.'); + amount = parseFloat(matching) || 0; + amount = line.balance.amount > 0 ? amount : -amount; + } + break; + case 'fixed': + amount = values.amount; + break; + default: + amount = values.amount !== undefined ? values.amount : line.balance.amount; + } + + + var prop = { + 'id': _.uniqueId('createLine'), + 'label': values.label || line.st_line.name, + 'account_id': account, + 'account_code': account ? this.accounts[account.id] : '', + 'analytic_account_id': this._formatNameGet(values.analytic_account_id), + 'analytic_tag_ids': this._formatMany2ManyTags(values.analytic_tag_ids || []), + 'journal_id': this._formatNameGet(values.journal_id), + 'tax_ids': this._formatMany2ManyTagsTax(values.tax_ids || []), + 'tag_ids': values.tag_ids, + 'tax_repartition_line_id': values.tax_repartition_line_id, + 'debit': 0, + 'credit': 0, + 'date': values.date ? values.date : field_utils.parse.date(today, {}, {isUTC: true}), + 'force_tax_included': values.force_tax_included || false, + 'base_amount': amount, + 'percent': values.amount_type === "percentage" ? values.amount : null, + 'link': values.link, + 'display': true, + 'invalid': true, + 'to_check': !!values.to_check, + '__tax_to_recompute': true, + '__focus': '__focus' in values ? values.__focus : true, + }; + if (prop.base_amount) { + // Call to format and parse needed to round the value to the currency precision + var sign = prop.base_amount < 0 ? -1 : 1; + var amount = field_utils.format.monetary(Math.abs(prop.base_amount), {}, formatOptions); + prop.base_amount = sign * field_utils.parse.monetary(amount, {}, formatOptions); + } + + prop.amount = prop.base_amount; + return prop; + }, + /** + * Return list of account_move_line that has been selected and needs to be removed + * from other calls. + * + * @private + * @returns {Array} list of excluded ids + */ + _getExcludedIds: function () { + var excludedIds = []; + _.each(this.lines, function(line) { + if (line.reconciliation_proposition) { + _.each(line.reconciliation_proposition, function(prop) { + if (parseInt(prop['id'])) { + excludedIds.push(prop['id']); + } + }); + } + }); + return excludedIds; + }, + /** + * Defined whether the line is to be displayed or not. Here, we only display + * the line if it comes from the server or if an account is defined when it + * is created + * extended in ManualModel + * + * @private + * @param {object} prop + * @returns {Boolean} + */ + _isDisplayedProposition: function (prop) { + return !isNaN(prop.id) || !!prop.account_id; + }, + /** + * extended in ManualModel + * @private + * @param {object} prop + * @returns {Boolean} + */ + _isValid: function (prop) { + return !isNaN(prop.id) || prop.account_id && prop.amount && prop.label && !!prop.label.length; + }, + /** + * Fetch 'account.reconciliation.widget' propositions. + * overridden in ManualModel + * + * @see '_formatMoveLine' + * + * @private + * @param {string} handle + * @returns {Promise} + */ + _performMoveLine: function (handle, mode, limit) { + limit = limit || this.limitMoveLines; + var line = this.getLine(handle); + var excluded_ids = _.map(_.union(line.reconciliation_proposition, line.mv_lines_match_rp, line.mv_lines_match_other), function (prop) { + return _.isNumber(prop.id) ? prop.id : null; + }).filter(id => id != null); + var filter = line['filter_'+mode] || ""; + return this._rpc({ + model: 'account.reconciliation.widget', + method: 'get_move_lines_for_bank_statement_line', + args: [line.id, line.st_line.partner_id, excluded_ids, filter, 0, limit, mode === 'match_rp' ? 'rp' : 'other'], + context: this.context, + }) + .then(this._formatMoveLine.bind(this, handle, mode)); + }, + /** + * format the proposition to send information server side + * extended in ManualModel + * + * @private + * @param {object} line + * @param {object} prop + * @returns {object} + */ + _formatToProcessReconciliation: function (line, prop) { + var amount = -prop.amount; + if (prop.partial_amount) { + amount = -prop.partial_amount; + } + + var result = { + name : prop.label, + debit : amount > 0 ? amount : 0, + credit : amount < 0 ? -amount : 0, + tax_exigible: prop.tax_exigible, + analytic_tag_ids: [[6, null, _.pluck(prop.analytic_tag_ids, 'id')]] + }; + if (!isNaN(prop.id)) { + result.counterpart_aml_id = prop.id; + } else { + result.account_id = prop.account_id.id; + if (prop.journal_id) { + result.journal_id = prop.journal_id.id; + } + } + if (!isNaN(prop.id)) result.counterpart_aml_id = prop.id; + if (prop.analytic_account_id) result.analytic_account_id = prop.analytic_account_id.id; + if (prop.tax_ids && prop.tax_ids.length) result.tax_ids = [[6, null, _.pluck(prop.tax_ids, 'id')]]; + + if (prop.tag_ids && prop.tag_ids.length) result.tag_ids = [[6, null, prop.tag_ids]]; + if (prop.tax_repartition_line_id) result.tax_repartition_line_id = prop.tax_repartition_line_id; + if (prop.reconcileModelId) result.reconcile_model_id = prop.reconcileModelId + return result; + }, + /** + * Hook to handle return values of the validate's line process. + * + * @private + * @param {Object} data + * @param {Object[]} data.moves list of processed account.move + * @returns {Deferred} + */ + _validatePostProcess: function (data) { + var self = this; + return Promise.resolve(); + }, +}); + + +/** + * Model use to fetch, format and update 'account.move.line' and 'res.partner' + * datas allowing manual reconciliation + */ +var ManualModel = StatementModel.extend({ + quickCreateFields: ['account_id', 'journal_id', 'amount', 'analytic_account_id', 'label', 'tax_ids', 'force_tax_included', 'analytic_tag_ids', 'date', 'to_check'], + + modes: ['create', 'match'], + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Return a boolean telling if load button needs to be displayed or not + * + * @returns {boolean} true if load more button needs to be displayed + */ + hasMoreLines: function () { + if (this.manualLines.length > this.pagerIndex) { + return true; + } + return false; + }, + /** + * load data from + * - 'account.reconciliation.widget' fetch the lines to reconciliate + * - 'account.account' fetch all account code + * + * @param {Object} context + * @param {string} [context.mode] 'customers', 'suppliers' or 'accounts' + * @param {integer[]} [context.company_ids] + * @param {integer[]} [context.partner_ids] used for 'customers' and + * 'suppliers' mode + * @returns {Promise} + */ + load: function (context) { + var self = this; + this.context = context; + + var domain_account_id = []; + if (context && context.company_ids) { + domain_account_id.push(['company_id', 'in', context.company_ids]); + } + + var def_account = this._rpc({ + model: 'account.account', + method: 'search_read', + domain: domain_account_id, + fields: ['code'], + }) + .then(function (accounts) { + self.account_ids = _.pluck(accounts, 'id'); + self.accounts = _.object(self.account_ids, _.pluck(accounts, 'code')); + }); + + var domainReconcile = []; + var session_allowed_company_ids = session.user_context.allowed_company_ids || [] + var company_ids = context && context.company_ids || session_allowed_company_ids.slice(0, 1); + + if (company_ids) { + domainReconcile.push(['company_id', 'in', company_ids]); + } + var def_reconcileModel = this._loadReconciliationModel({domainReconcile: domainReconcile}); + var def_taxes = this._loadTaxes(); + + return Promise.all([def_reconcileModel, def_account, def_taxes]).then(function () { + switch(context.mode) { + case 'customers': + case 'suppliers': + var mode = context.mode === 'customers' ? 'receivable' : 'payable'; + var args = ['partner', context.partner_ids || null, mode]; + return self._rpc({ + model: 'account.reconciliation.widget', + method: 'get_data_for_manual_reconciliation', + args: args, + context: context, + }) + .then(function (result) { + self.manualLines = result; + self.valuenow = 0; + self.valuemax = Object.keys(self.manualLines).length; + var lines = self.manualLines.slice(0, self.defaultDisplayQty); + self.pagerIndex = lines.length; + return self.loadData(lines); + }); + case 'accounts': + return self._rpc({ + model: 'account.reconciliation.widget', + method: 'get_data_for_manual_reconciliation', + args: ['account', context.account_ids || self.account_ids], + context: context, + }) + .then(function (result) { + self.manualLines = result; + self.valuenow = 0; + self.valuemax = Object.keys(self.manualLines).length; + var lines = self.manualLines.slice(0, self.defaultDisplayQty); + self.pagerIndex = lines.length; + return self.loadData(lines); + }); + default: + var partner_ids = context.partner_ids || null; + var account_ids = context.account_ids || self.account_ids || null; + return self._rpc({ + model: 'account.reconciliation.widget', + method: 'get_all_data_for_manual_reconciliation', + args: [partner_ids, account_ids], + context: context, + }) + .then(function (result) { + // Flatten the result + self.manualLines = [].concat(result.accounts, result.customers, result.suppliers); + self.valuenow = 0; + self.valuemax = Object.keys(self.manualLines).length; + var lines = self.manualLines.slice(0, self.defaultDisplayQty); + self.pagerIndex = lines.length; + return self.loadData(lines); + }); + } + }); + }, + + /** + * Reload data by calling load + * It overrides super.reload() because + * it is not adapted for this model. + * + * Use case: coming back to manual reconcilation + * in breadcrumb + */ + reload: function () { + this.lines = {}; + return this.load(this.context); + }, + + /** + * Load more partners/accounts + * overridden in ManualModel + * + * @param {integer} qty quantity to load + * @returns {Promise} + */ + loadMore: function(qty) { + if (qty === undefined) { + qty = this.defaultDisplayQty; + } + var lines = this.manualLines.slice(this.pagerIndex, this.pagerIndex + qty); + this.pagerIndex += qty; + return this.loadData(lines); + }, + /** + * Method to load informations on lines + * + * @param {Array} lines manualLines to load + * @returns {Promise} + */ + loadData: function(lines) { + var self = this; + var defs = []; + _.each(lines, function (l) { + defs.push(self._formatLine(l.mode, l)); + }); + return Promise.all(defs); + + }, + /** + * Mark the account or the partner as reconciled + * + * @param {(string|string[])} handle + * @returns {Promise<Array>} resolved with the handle array + */ + validate: function (handle) { + var self = this; + var handles = []; + if (handle) { + handles = [handle]; + } else { + _.each(this.lines, function (line, handle) { + if (!line.reconciled && !line.balance.amount && line.reconciliation_proposition.length) { + handles.push(handle); + } + }); + } + + var def = Promise.resolve(); + var process_reconciliations = []; + var reconciled = []; + _.each(handles, function (handle) { + var line = self.getLine(handle); + if(line.reconciled) { + return; + } + var props = line.reconciliation_proposition; + if (!props.length) { + self.valuenow++; + reconciled.push(handle); + line.reconciled = true; + process_reconciliations.push({ + id: line.type === 'accounts' ? line.account_id : line.partner_id, + type: line.type, + mv_line_ids: [], + new_mv_line_dicts: [], + }); + } else { + var mv_line_ids = _.pluck(_.filter(props, function (prop) {return !isNaN(prop.id);}), 'id'); + var new_mv_line_dicts = _.map(_.filter(props, function (prop) {return isNaN(prop.id) && prop.display;}), self._formatToProcessReconciliation.bind(self, line)); + process_reconciliations.push({ + id: null, + type: null, + mv_line_ids: mv_line_ids, + new_mv_line_dicts: new_mv_line_dicts + }); + } + line.reconciliation_proposition = []; + }); + if (process_reconciliations.length) { + def = self._rpc({ + model: 'account.reconciliation.widget', + method: 'process_move_lines', + args: [process_reconciliations], + }); + } + + return def.then(function() { + var defs = []; + var account_ids = []; + var partner_ids = []; + _.each(handles, function (handle) { + var line = self.getLine(handle); + if (line.reconciled) { + return; + } + line.filter_match = ""; + defs.push(self._performMoveLine(handle, 'match').then(function () { + if(!line.mv_lines_match.length) { + self.valuenow++; + reconciled.push(handle); + line.reconciled = true; + if (line.type === 'accounts') { + account_ids.push(line.account_id.id); + } else { + partner_ids.push(line.partner_id); + } + } + })); + }); + return Promise.all(defs).then(function () { + if (partner_ids.length) { + self._rpc({ + model: 'res.partner', + method: 'mark_as_reconciled', + args: [partner_ids], + }); + } + return {reconciled: reconciled, updated: _.difference(handles, reconciled)}; + }); + }); + }, + removeProposition: function (handle, id) { + var self = this; + var line = this.getLine(handle); + var defs = []; + var prop = _.find(line.reconciliation_proposition, {'id' : id}); + if (prop) { + line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (p) { + return p.id !== prop.id && p.id !== prop.link && p.link !== prop.id && (!p.link || p.link !== prop.link); + }); + line.mv_lines_match = line.mv_lines_match || []; + line.mv_lines_match.unshift(prop); + + // No proposition left and then, reset the st_line partner. + if(line.reconciliation_proposition.length == 0 && line.st_line.has_no_partner) + defs.push(self.changePartner(line.handle)); + } + line.mode = (id || line.mode !== "create") && isNaN(id) ? 'create' : 'match'; + defs.push(this._computeLine(line)); + return Promise.all(defs).then(function() { + return self.changeMode(handle, line.mode, true); + }) + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * override change the balance type to display or not the reconcile button + * + * @override + * @private + * @param {Object} line + * @returns {Promise} + */ + _computeLine: function (line) { + return this._super(line).then(function () { + var props = _.reject(line.reconciliation_proposition, 'invalid'); + _.each(line.reconciliation_proposition, function(p) { + delete p.is_move_line; + }); + line.balance.type = -1; + if (!line.balance.amount_currency && props.length) { + line.balance.type = 1; + } else if(_.any(props, function (prop) {return prop.amount > 0;}) && + _.any(props, function (prop) {return prop.amount < 0;})) { + line.balance.type = 0; + } + }); + }, + /** + * Format each server lines and propositions and compute all lines + * + * @see '_computeLine' + * + * @private + * @param {'customers' | 'suppliers' | 'accounts'} type + * @param {Object} data + * @returns {Promise} + */ + _formatLine: function (type, data) { + var line = this.lines[_.uniqueId('rline')] = _.extend(data, { + type: type, + reconciled: false, + mode: 'inactive', + limitMoveLines: this.limitMoveLines, + filter_match: "", + reconcileModels: this.reconcileModels, + account_id: this._formatNameGet([data.account_id, data.account_name]), + st_line: data, + visible: true + }); + this._formatLineProposition(line, line.reconciliation_proposition); + if (!line.reconciliation_proposition.length) { + delete line.reconciliation_proposition; + } + return this._computeLine(line); + }, + /** + * override to add journal_id + * + * @override + * @private + * @param {Object} line + * @param {Object} props + */ + _formatLineProposition: function (line, props) { + var self = this; + this._super(line, props); + if (props.length) { + _.each(props, function (prop) { + var tmp_value = prop.debit || prop.credit; + prop.credit = prop.credit !== 0 ? 0 : tmp_value; + prop.debit = prop.debit !== 0 ? 0 : tmp_value; + prop.amount = -prop.amount; + prop.journal_id = self._formatNameGet(prop.journal_id || line.journal_id); + prop.to_check = !!prop.to_check; + }); + } + }, + /** + * override to add journal_id on tax_created_line + * + * @private + * @param {Object} line + * @param {Object} values + * @returns {Object} + */ + _formatQuickCreate: function (line, values) { + // Add journal to created line + if (values && values.journal_id === undefined && line && line.createForm && line.createForm.journal_id) { + values.journal_id = line.createForm.journal_id; + } + return this._super(line, values); + }, + /** + * @override + * @param {object} prop + * @returns {Boolean} + */ + _isDisplayedProposition: function (prop) { + return !!prop.journal_id && this._super(prop); + }, + /** + * @override + * @param {object} prop + * @returns {Boolean} + */ + _isValid: function (prop) { + return prop.journal_id && this._super(prop); + }, + /** + * Fetch 'account.move.line' propositions. + * + * @see '_formatMoveLine' + * + * @override + * @private + * @param {string} handle + * @returns {Promise} + */ + _performMoveLine: function (handle, mode, limit) { + limit = limit || this.limitMoveLines; + var line = this.getLine(handle); + var excluded_ids = _.map(_.union(line.reconciliation_proposition, line.mv_lines_match), function (prop) { + return _.isNumber(prop.id) ? prop.id : null; + }).filter(id => id != null); + var filter = line.filter_match || ""; + var args = [line.account_id.id, line.partner_id, excluded_ids, filter, 0, limit]; + return this._rpc({ + model: 'account.reconciliation.widget', + method: 'get_move_lines_for_manual_reconciliation', + args: args, + context: this.context, + }) + .then(this._formatMoveLine.bind(this, handle, '')); + }, + + _formatToProcessReconciliation: function (line, prop) { + var result = this._super(line, prop); + result['date'] = prop.date; + return result; + }, + _getDefaultMode: function(handle) { + var line = this.getLine(handle); + if (line.balance.amount === 0 && (!line.st_line.mv_lines_match || line.st_line.mv_lines_match.length === 0)) { + return 'inactive'; + } + return line.mv_lines_match.length > 0 ? 'match' : 'create'; + }, + _formatMoveLine: function (handle, mode, mv_lines) { + var self = this; + var line = this.getLine(handle); + line.mv_lines_match = _.uniq((line.mv_lines_match || []).concat(mv_lines), l => l.id); + this._formatLineProposition(line, mv_lines); + + if (line.mode !== 'create' && !line.mv_lines_match.length && !line.filter_match.length) { + line.mode = this.avoidCreate || !line.balance.amount ? 'inactive' : 'create'; + if (line.mode === 'create') { + return this._computeLine(line).then(function () { + return self.createProposition(handle); + }); + } + } else { + return this._computeLine(line); + } + }, +}); + +return { + StatementModel: StatementModel, + ManualModel: ManualModel, +}; +}); diff --git a/base_accounting_kit/static/src/js/payment_render.js b/base_accounting_kit/static/src/js/payment_render.js new file mode 100644 index 0000000..96266ce --- /dev/null +++ b/base_accounting_kit/static/src/js/payment_render.js @@ -0,0 +1,929 @@ +odoo.define('base_accounting_kit.ReconciliationRenderer', function (require) { +"use strict"; + +var Widget = require('web.Widget'); +var FieldManagerMixin = require('web.FieldManagerMixin'); +var relational_fields = require('web.relational_fields'); +var basic_fields = require('web.basic_fields'); +var core = require('web.core'); +var time = require('web.time'); +var session = require('web.session'); +var qweb = core.qweb; +var _t = core._t; + + +/** + * rendering of the bank statement action contains progress bar, title and + * auto reconciliation button + */ +var StatementRenderer = Widget.extend(FieldManagerMixin, { + template: 'reconciliation.statement', + events: { + 'click *[rel="do_action"]': '_onDoAction', + 'click button.js_load_more': '_onLoadMore', + }, + /** + * @override + */ + init: function (parent, model, state) { + this._super(parent); + this.model = model; + this._initialState = state; + }, + /** + * display iniial state and create the name statement field + * + * @override + */ + start: function () { + var self = this; + var defs = [this._super.apply(this, arguments)]; + this.time = Date.now(); + this.$progress = $(''); + + return Promise.all(defs); + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + /* + * hide the button to load more statement line + */ + hideLoadMoreButton: function (show) { + if (!show) { + this.$('.js_load_more').show(); + } + else { + this.$('.js_load_more').hide(); + } + }, + showRainbowMan: function (state) { + if (this.model.display_context !== 'validate') { + return + } + var dt = Date.now()-this.time; + var $done = $(qweb.render("reconciliation.done", { + 'duration': moment(dt).utc().format(time.getLangTimeFormat()), + 'number': state.valuenow, + 'timePerTransaction': Math.round(dt/1000/state.valuemax), + 'context': state.context, + })); + $done.find('*').addClass('o_reward_subcontent'); + $done.find('.button_close_statement').click(this._onCloseBankStatement.bind(this)); + $done.find('.button_back_to_statement').click(this._onGoToBankStatement.bind(this)); + // display rainbowman after full reconciliation + if (session.show_effect) { + this.trigger_up('show_effect', { + type: 'rainbow_man', + fadeout: 'no', + message: $done, + }); + this.$el.css('min-height', '450px'); + } else { + $done.appendTo(this.$el); + } + }, + /** + * update the statement rendering + * + * @param {object} state - statement data + * @param {integer} state.valuenow - for the progress bar + * @param {integer} state.valuemax - for the progress bar + * @param {string} state.title - for the progress bar + * @param {[object]} [state.notifications] + */ + update: function (state) { + var self = this; + this._updateProgressBar(state); + + if (state.valuenow === state.valuemax && !this.$('.done_message').length) { + this.showRainbowMan(state); + } + + if (state.notifications) { + this._renderNotifications(state.notifications); + } + }, + _updateProgressBar: function(state) { + this.$progress.find('.valuenow').text(state.valuenow); + this.$progress.find('.valuemax').text(state.valuemax); + this.$progress.find('.progress-bar') + .attr('aria-valuenow', state.valuenow) + .attr('aria-valuemax', state.valuemax) + .css('width', (state.valuenow/state.valuemax*100) + '%'); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + /** + * render the notifications + * + * @param {[object]} notifications + */ + _renderNotifications: function(notifications) { + this.$(".notification_area").empty(); + for (var i=0; i<notifications.length; i++) { + var $notification = $(qweb.render("reconciliation.notification", notifications[i])).hide(); + $notification.appendTo(this.$(".notification_area")).slideDown(300); + } + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * Click on close bank statement button, this will + * close and then open form view of bank statement + * @param {MouseEvent} event + */ + _onCloseBankStatement: function (e) { + this.trigger_up('close_statement'); + }, + /** + * @private + * @param {MouseEvent} event + */ + _onDoAction: function(e) { + e.preventDefault(); + var name = e.currentTarget.dataset.action_name; + var model = e.currentTarget.dataset.model; + if (e.currentTarget.dataset.ids) { + var ids = e.currentTarget.dataset.ids.split(",").map(Number); + var domain = [['id', 'in', ids]]; + } else { + var domain = e.currentTarget.dataset.domain; + } + var context = e.currentTarget.dataset.context; + var tag = e.currentTarget.dataset.tag; + if (tag) { + this.do_action({ + type: 'ir.actions.client', + tag: tag, + context: context, + }) + } else { + this.do_action({ + name: name, + res_model: model, + domain: domain, + context: context, + views: [[false, 'list'], [false, 'form']], + type: 'ir.actions.act_window', + view_mode: "list" + }); + } + }, + /** + * Open the list view for account.bank.statement model + * @private + * @param {MouseEvent} event + */ + _onGoToBankStatement: function (e) { + var journalId = $(e.target).attr('data_journal_id'); + if (journalId) { + journalId = parseInt(journalId); + } + $('.o_reward').remove(); + this.do_action({ + name: 'Bank Statements', + res_model: 'account.bank.statement', + views: [[false, 'list'], [false, 'form']], + type: 'ir.actions.act_window', + context: {search_default_journal_id: journalId, 'journal_type':'bank'}, + view_mode: 'form', + }); + }, + /** + * Load more statement lines for reconciliation + * @private + * @param {MouseEvent} event + */ + _onLoadMore: function (e) { + this.trigger_up('load_more'); + }, +}); + + +/** + * rendering of the bank statement line, contains line data, proposition and + * view for 'match' and 'create' mode + */ +var LineRenderer = Widget.extend(FieldManagerMixin, { + template: "reconciliation.line", + events: { + 'click .accounting_view caption .o_buttons button': '_onValidate', + 'click .accounting_view tfoot': '_onChangeTab', + 'click': '_onTogglePanel', + 'click .o_field_widget': '_onStopPropagation', + 'keydown .o_input, .edit_amount_input': '_onStopPropagation', + 'click .o_notebook li a': '_onChangeTab', + 'click .cell': '_onEditAmount', + 'change input.filter': '_onFilterChange', + 'click .match .load-more a': '_onLoadMore', + 'click .match .mv_line td': '_onSelectMoveLine', + 'click .accounting_view tbody .mv_line td': '_onSelectProposition', + 'click .o_reconcile_models button': '_onQuickCreateProposition', + 'click .create .add_line': '_onCreateProposition', + 'click .reconcile_model_create': '_onCreateReconcileModel', + 'click .reconcile_model_edit': '_onEditReconcileModel', + 'keyup input': '_onInputKeyup', + 'blur input': '_onInputKeyup', + 'keydown': '_onKeydown', + }, + custom_events: _.extend({}, FieldManagerMixin.custom_events, { + 'field_changed': '_onFieldChanged', + }), + _avoidFieldUpdate: {}, + MV_LINE_DEBOUNCE: 200, + + _onKeydown: function (ev) { + switch (ev.which) { + case $.ui.keyCode.ENTER: + this.trigger_up('navigation_move', {direction: 'validate', handle: this.handle}); + break; + case $.ui.keyCode.UP: + ev.stopPropagation(); + ev.preventDefault(); + this.trigger_up('navigation_move', {direction: 'up', handle: this.handle}); + break; + case $.ui.keyCode.DOWN: + ev.stopPropagation(); + ev.preventDefault(); + this.trigger_up('navigation_move', {direction: 'down', handle: this.handle}); + break; + } + }, + + /** + * create partner_id field in editable mode + * + * @override + */ + init: function (parent, model, state) { + this._super(parent); + FieldManagerMixin.init.call(this); + + this.model = model; + this._initialState = state; + if (this.MV_LINE_DEBOUNCE) { + this._onSelectMoveLine = _.debounce(this._onSelectMoveLine, this.MV_LINE_DEBOUNCE, true); + } else { + this._onSelectMoveLine = this._onSelectMoveLine; + } + }, + /** + * @override + */ + start: function () { + var self = this; + var def1 = this._makePartnerRecord(this._initialState.st_line.partner_id, this._initialState.st_line.partner_name).then(function (recordID) { + self.fields = { + partner_id : new relational_fields.FieldMany2One(self, + 'partner_id', + self.model.get(recordID), { + mode: 'edit', + attrs: { + placeholder: self._initialState.st_line.communication_partner_name || _t('Select Partner'), + } + } + ) + }; + self.fields.partner_id.insertAfter(self.$('.accounting_view caption .o_buttons')); + }); + var def3 = session.user_has_group('analytic.group_analytic_tags').then(function(has_group) { + self.group_tags = has_group; + }); + var def4 = session.user_has_group('analytic.group_analytic_accounting').then(function(has_group) { + self.group_acc = has_group; + }); + $('<span class="line_info_button fa fa-info-circle"/>') + .appendTo(this.$('thead .cell_info_popover')) + .attr("data-content", qweb.render('reconciliation.line.statement_line.details', {'state': this._initialState})); + this.$el.popover({ + 'selector': '.line_info_button', + 'placement': 'left', + 'container': this.$el, + 'html': true, + // disable bootstrap sanitizer because we use a table that has been + // rendered using qweb.render so it is safe and also because sanitizer escape table by default. + 'sanitize': false, + 'trigger': 'hover', + 'animation': false, + 'toggle': 'popover' + }); + var def2 = this._super.apply(this, arguments); + return Promise.all([def1, def2, def3, def4]); + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * update the statement line rendering + * + * @param {object} state - statement line + */ + update: function (state) { + var self = this; + // isValid + var to_check_checked = !!(state.to_check); + this.$('caption .o_buttons button.o_validate').toggleClass('d-none', !!state.balance.type && !to_check_checked); + this.$('caption .o_buttons button.o_reconcile').toggleClass('d-none', state.balance.type <= 0 || to_check_checked); + this.$('caption .o_buttons .o_no_valid').toggleClass('d-none', state.balance.type >= 0 || to_check_checked); + self.$('caption .o_buttons button.o_validate').toggleClass('text-warning', to_check_checked); + + // partner_id + this._makePartnerRecord(state.st_line.partner_id, state.st_line.partner_name).then(function (recordID) { + self.fields.partner_id.reset(self.model.get(recordID)); + self.$el.attr('data-partner', state.st_line.partner_id); + }); + + // mode + this.$el.data('mode', state.mode).attr('data-mode', state.mode); + this.$('.o_notebook li a').attr('aria-selected', false); + this.$('.o_notebook li a').removeClass('active'); + this.$('.o_notebook .tab-content .tab-pane').removeClass('active'); + this.$('.o_notebook li a[href*="notebook_page_' + state.mode + '"]').attr('aria-selected', true); + this.$('.o_notebook li a[href*="notebook_page_' + state.mode + '"]').addClass('active'); + this.$('.o_notebook .tab-content .tab-pane[id*="notebook_page_' + state.mode + '"]').addClass('active'); + this.$('.create, .match').each(function () { + $(this).removeAttr('style'); + }); + + // reconciliation_proposition + var $props = this.$('.accounting_view tbody').empty(); + + // Search propositions that could be a partial credit/debit. + var props = []; + var balance = state.balance.amount_currency; + _.each(state.reconciliation_proposition, function (prop) { + if (prop.display) { + props.push(prop); + } + }); + + _.each(props, function (line) { + var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state, 'proposition': true})); + if (!isNaN(line.id)) { + $('<span class="line_info_button fa fa-info-circle"/>') + .appendTo($line.find('.cell_info_popover')) + .attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line})); + } + $props.append($line); + }); + + // mv_lines + var matching_modes = self.model.modes.filter(x => x.startsWith('match')); + for (let i = 0; i < matching_modes.length; i++) { + var stateMvLines = state['mv_lines_'+matching_modes[i]] || []; + var recs_count = stateMvLines.length > 0 ? stateMvLines[0].recs_count : 0; + var remaining = state['remaining_' + matching_modes[i]]; + var $mv_lines = this.$('div[id*="notebook_page_' + matching_modes[i] + '"] .match table tbody').empty(); + this.$('.o_notebook li a[href*="notebook_page_' + matching_modes[i] + '"]').parent().toggleClass('d-none', stateMvLines.length === 0 && !state['filter_'+matching_modes[i]]); + + _.each(stateMvLines, function (line) { + var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state})); + if (!isNaN(line.id)) { + $('<span class="line_info_button fa fa-info-circle"/>') + .appendTo($line.find('.cell_info_popover')) + .attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line})); + } + $mv_lines.append($line); + }); + this.$('div[id*="notebook_page_' + matching_modes[i] + '"] .match div.load-more').toggle(remaining > 0); + this.$('div[id*="notebook_page_' + matching_modes[i] + '"] .match div.load-more span').text(remaining); + } + + // balance + this.$('.popover').remove(); + this.$('table tfoot').html(qweb.render("reconciliation.line.balance", {'state': state})); + + // create form + if (state.createForm) { + var createPromise; + if (!this.fields.account_id) { + createPromise = this._renderCreate(state); + } + Promise.resolve(createPromise).then(function(){ + var data = self.model.get(self.handleCreateRecord).data; + return self.model.notifyChanges(self.handleCreateRecord, state.createForm) + .then(function () { + // FIXME can't it directly written REPLACE_WITH ids=state.createForm.analytic_tag_ids + return self.model.notifyChanges(self.handleCreateRecord, {analytic_tag_ids: {operation: 'REPLACE_WITH', ids: []}}) + }) + .then(function (){ + var defs = []; + _.each(state.createForm.analytic_tag_ids, function (tag) { + defs.push(self.model.notifyChanges(self.handleCreateRecord, {analytic_tag_ids: {operation: 'ADD_M2M', ids: tag}})); + }); + return Promise.all(defs); + }) + .then(function () { + return self.model.notifyChanges(self.handleCreateRecord, {tax_ids: {operation: 'REPLACE_WITH', ids: []}}) + }) + .then(function (){ + var defs = []; + _.each(state.createForm.tax_ids, function (tag) { + defs.push(self.model.notifyChanges(self.handleCreateRecord, {tax_ids: {operation: 'ADD_M2M', ids: tag}})); + }); + return Promise.all(defs); + }) + .then(function () { + var record = self.model.get(self.handleCreateRecord); + _.each(self.fields, function (field, fieldName) { + if (self._avoidFieldUpdate[fieldName]) return; + if (fieldName === "partner_id") return; + if ((data[fieldName] || state.createForm[fieldName]) && !_.isEqual(state.createForm[fieldName], data[fieldName])) { + field.reset(record); + } + if (fieldName === 'tax_ids') { + if (!state.createForm[fieldName].length || state.createForm[fieldName].length > 1) { + $('.create_force_tax_included').addClass('d-none'); + } + else { + $('.create_force_tax_included').removeClass('d-none'); + var price_include = state.createForm[fieldName][0].price_include; + var force_tax_included = state.createForm[fieldName][0].force_tax_included; + self.$('.create_force_tax_included input').prop('checked', force_tax_included); + self.$('.create_force_tax_included input').prop('disabled', price_include); + } + } + }); + if (state.to_check) { + // Set the to_check field to true if global to_check is set + self.$('.create_to_check input').prop('checked', state.to_check).change(); + } + return true; + }); + }); + } + this.$('.create .add_line').toggle(!!state.balance.amount_currency); + }, + + updatePartialAmount: function(line_id, amount) { + var $line = this.$('.mv_line[data-line-id='+line_id+']'); + $line.find('.edit_amount').addClass('d-none'); + $line.find('.edit_amount_input').removeClass('d-none'); + $line.find('.edit_amount_input').focus(); + $line.find('.edit_amount_input').val(amount); + $line.find('.line_amount').addClass('d-none'); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @param {jQueryElement} $el + */ + _destroyPopover: function ($el) { + var popover = $el.data('bs.popover'); + if (popover) { + popover.dispose(); + } + }, + /** + * @private + * @param {integer} partnerID + * @param {string} partnerName + * @returns {string} local id of the dataPoint + */ + _makePartnerRecord: function (partnerID, partnerName) { + var field = { + relation: 'res.partner', + type: 'many2one', + name: 'partner_id', + }; + if (partnerID) { + field.value = [partnerID, partnerName]; + } + return this.model.makeRecord('account.bank.statement.line', [field], { + partner_id: { + domain: ["|", ["is_company", "=", true], ["parent_id", "=", false]], + options: { + no_open: true + } + } + }); + }, + + /** + * create account_id, tax_ids, analytic_account_id, analytic_tag_ids, label and amount fields + * + * @private + * @param {object} state - statement line + * @returns {Promise} + */ + _renderCreate: function (state) { + var self = this; + return this.model.makeRecord('account.bank.statement.line', [{ + relation: 'account.account', + type: 'many2one', + name: 'account_id', + domain: [['company_id', '=', state.st_line.company_id], ['deprecated', '=', false]], + }, { + relation: 'account.journal', + type: 'many2one', + name: 'journal_id', + domain: [['company_id', '=', state.st_line.company_id]], + }, { + relation: 'account.tax', + type: 'many2many', + name: 'tax_ids', + domain: [['company_id', '=', state.st_line.company_id]], + }, { + relation: 'account.analytic.account', + type: 'many2one', + name: 'analytic_account_id', + }, { + relation: 'account.analytic.tag', + type: 'many2many', + name: 'analytic_tag_ids', + }, { + type: 'boolean', + name: 'force_tax_included', + }, { + type: 'char', + name: 'label', + }, { + type: 'float', + name: 'amount', + }, { + type: 'char', //TODO is it a bug or a feature when type date exists ? + name: 'date', + }, { + type: 'boolean', + name: 'to_check', + }], { + account_id: { + string: _t("Account"), + }, + label: {string: _t("Label")}, + amount: {string: _t("Account")}, + }).then(function (recordID) { + self.handleCreateRecord = recordID; + var record = self.model.get(self.handleCreateRecord); + + self.fields.account_id = new relational_fields.FieldMany2One(self, + 'account_id', record, {mode: 'edit', attrs: {can_create:false}}); + + self.fields.journal_id = new relational_fields.FieldMany2One(self, + 'journal_id', record, {mode: 'edit'}); + + self.fields.tax_ids = new relational_fields.FieldMany2ManyTags(self, + 'tax_ids', record, {mode: 'edit', additionalContext: {append_type_to_tax_name: true}}); + + self.fields.analytic_account_id = new relational_fields.FieldMany2One(self, + 'analytic_account_id', record, {mode: 'edit'}); + + self.fields.analytic_tag_ids = new relational_fields.FieldMany2ManyTags(self, + 'analytic_tag_ids', record, {mode: 'edit'}); + + self.fields.force_tax_included = new basic_fields.FieldBoolean(self, + 'force_tax_included', record, {mode: 'edit'}); + + self.fields.label = new basic_fields.FieldChar(self, + 'label', record, {mode: 'edit'}); + + self.fields.amount = new basic_fields.FieldFloat(self, + 'amount', record, {mode: 'edit'}); + + self.fields.date = new basic_fields.FieldDate(self, + 'date', record, {mode: 'edit'}); + + self.fields.to_check = new basic_fields.FieldBoolean(self, + 'to_check', record, {mode: 'edit'}); + + var $create = $(qweb.render("reconciliation.line.create", {'state': state, 'group_tags': self.group_tags, 'group_acc': self.group_acc})); + self.fields.account_id.appendTo($create.find('.create_account_id .o_td_field')) + .then(addRequiredStyle.bind(self, self.fields.account_id)); + self.fields.journal_id.appendTo($create.find('.create_journal_id .o_td_field')); + self.fields.tax_ids.appendTo($create.find('.create_tax_id .o_td_field')); + self.fields.analytic_account_id.appendTo($create.find('.create_analytic_account_id .o_td_field')); + self.fields.analytic_tag_ids.appendTo($create.find('.create_analytic_tag_ids .o_td_field')); + self.fields.force_tax_included.appendTo($create.find('.create_force_tax_included .o_td_field')); + self.fields.label.appendTo($create.find('.create_label .o_td_field')) + .then(addRequiredStyle.bind(self, self.fields.label)); + self.fields.amount.appendTo($create.find('.create_amount .o_td_field')) + .then(addRequiredStyle.bind(self, self.fields.amount)); + self.fields.date.appendTo($create.find('.create_date .o_td_field')); + self.fields.to_check.appendTo($create.find('.create_to_check .o_td_field')); + self.$('.create').append($create); + + function addRequiredStyle(widget) { + widget.$el.addClass('o_required_modifier'); + } + }); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + /** + * The event on the partner m2o widget was propagated to the bank statement + * line widget, causing it to expand and the others to collapse. This caused + * the dropdown to be poorly placed and an unwanted update of this widget. + * + * @private + */ + _onStopPropagation: function(ev) { + ev.stopPropagation(); + }, + + /** + * @private + * @param {MouseEvent} event + */ + _onCreateReconcileModel: function (event) { + event.preventDefault(); + var self = this; + this.do_action({ + type: 'ir.actions.act_window', + res_model: 'account.reconcile.model', + views: [[false, 'form']], + target: 'current' + }, + { + on_reverse_breadcrumb: function() {self.trigger_up('reload');}, + }); + }, + _editAmount: function (event) { + event.stopPropagation(); + var $line = $(event.target); + var moveLineId = $line.closest('.mv_line').data('line-id'); + this.trigger_up('partial_reconcile', {'data': {mvLineId: moveLineId, 'amount': $line.val()}}); + }, + _onEditAmount: function (event) { + event.preventDefault(); + event.stopPropagation(); + // Don't call when clicking inside the input field + if (! $(event.target).hasClass('edit_amount_input')){ + var $line = $(event.target); + this.trigger_up('getPartialAmount', {'data': $line.closest('.mv_line').data('line-id')}); + } + }, + /** + * @private + * @param {MouseEvent} event + */ + _onEditReconcileModel: function (event) { + event.preventDefault(); + var self = this; + this.do_action({ + type: 'ir.actions.act_window', + res_model: 'account.reconcile.model', + views: [[false, 'list'], [false, 'form']], + view_mode: "list", + target: 'current' + }, + { + on_reverse_breadcrumb: function() {self.trigger_up('reload');}, + }); + }, + /** + * @private + * @param {OdooEvent} event + */ + _onFieldChanged: function (event) { + event.stopPropagation(); + var fieldName = event.target.name; + if (fieldName === 'partner_id') { + var partner_id = event.data.changes.partner_id; + this.trigger_up('change_partner', {'data': partner_id}); + } else { + if (event.data.changes.amount && isNaN(event.data.changes.amount)) { + return; + } + this.trigger_up('update_proposition', {'data': event.data.changes}); + } + }, + /** + * @private + */ + _onTogglePanel: function () { + if (this.$el[0].getAttribute('data-mode') == 'inactive') + this.trigger_up('change_mode', {'data': 'default'}); + }, + /** + * @private + */ + _onChangeTab: function(event) { + if (event.currentTarget.nodeName === 'TFOOT') { + this.trigger_up('change_mode', {'data': 'next'}); + } else { + var modes = this.model.modes; + var selected_mode = modes.find(function(e) {return event.target.getAttribute('href').includes(e)}); + if (selected_mode) { + this.trigger_up('change_mode', {'data': selected_mode}); + } + } + }, + /** + * @private + * @param {input event} event + */ + _onFilterChange: function (event) { + this.trigger_up('change_filter', {'data': _.str.strip($(event.target).val())}); + }, + /** + * @private + * @param {keyup event} event + */ + _onInputKeyup: function (event) { + var target_partner_id = $(event.target).parents('[name="partner_id"]'); + if (target_partner_id.length === 1) { + return; + } + if(event.keyCode === 13) { + if ($(event.target).hasClass('edit_amount_input')) { + $(event.target).blur(); + return; + } + var created_lines = _.findWhere(this.model.lines, {mode: 'create'}); + if (created_lines && created_lines.balance.amount) { + this._onCreateProposition(); + } + return; + } + if ($(event.target).hasClass('edit_amount_input')) { + if (event.type === 'keyup') { + return; + } + else { + return this._editAmount(event); + } + } + + var self = this; + for (var fieldName in this.fields) { + var field = this.fields[fieldName]; + if (!field.$el.is(event.target)) { + continue; + } + this._avoidFieldUpdate[field.name] = event.type !== 'focusout'; + field.value = false; + field._setValue($(event.target).val()).then(function () { + self._avoidFieldUpdate[field.name] = false; + }); + break; + } + }, + /** + * @private + */ + _onLoadMore: function (ev) { + ev.preventDefault(); + this.trigger_up('change_offset'); + }, + /** + * @private + * @param {MouseEvent} event + */ + _onSelectMoveLine: function (event) { + var $el = $(event.target); + $el.prop('disabled', true); + this._destroyPopover($el); + var moveLineId = $el.closest('.mv_line').data('line-id'); + this.trigger_up('add_proposition', {'data': moveLineId}); + }, + /** + * @private + * @param {MouseEvent} event + */ + _onSelectProposition: function (event) { + var $el = $(event.target); + this._destroyPopover($el); + var moveLineId = $el.closest('.mv_line').data('line-id'); + this.trigger_up('remove_proposition', {'data': moveLineId}); + }, + /** + * @private + * @param {MouseEvent} event + */ + _onQuickCreateProposition: function (event) { + document.activeElement && document.activeElement.blur(); + this.trigger_up('quick_create_proposition', {'data': $(event.target).data('reconcile-model-id')}); + }, + /** + * @private + */ + _onCreateProposition: function () { + document.activeElement && document.activeElement.blur(); + var invalid = []; + _.each(this.fields, function (field) { + if (!field.isValid()) { + invalid.push(field.string); + } + }); + if (invalid.length) { + this.do_warn(_t("Some fields are undefined"), invalid.join(', ')); + return; + } + this.trigger_up('create_proposition'); + }, + /** + * @private + */ + _onValidate: function () { + this.trigger_up('validate'); + } +}); + + +/** + * rendering of the manual reconciliation action contains progress bar, title + * and auto reconciliation button + */ +var ManualRenderer = StatementRenderer.extend({ + template: "reconciliation.manual.statement", + +}); + + +/** + * rendering of the manual reconciliation, contains line data, proposition and + * view for 'match' mode + */ +var ManualLineRenderer = LineRenderer.extend({ + template: "reconciliation.manual.line", + /** + * @override + * @param {string} handle + * @param {number} proposition id (move line id) + * @returns {Promise} + */ + removeProposition: function (handle, id) { + if (!id) { + return Promise.resolve(); + } + return this._super(handle, id); + }, + /** + * move the partner field + * + * @override + */ + start: function () { + var self = this; + return this._super.apply(this, arguments).then(function () { + return self.model.makeRecord('account.move.line', [{ + relation: 'account.account', + type: 'many2one', + name: 'account_id', + value: [self._initialState.account_id.id, self._initialState.account_id.display_name], + }]).then(function (recordID) { + self.fields.title_account_id = new relational_fields.FieldMany2One(self, + 'account_id', + self.model.get(recordID), + {mode: 'readonly'} + ); + }).then(function () { + return self.fields.title_account_id.appendTo(self.$('.accounting_view thead td:eq(0) span:first')); + }); + }); + }, + /** + * @override + */ + update: function (state) { + this._super(state); + var props = _.filter(state.reconciliation_proposition, {'display': true}); + if (!props.length) { + var $line = $(qweb.render("reconciliation.line.mv_line", {'line': {}, 'state': state})); + this.$('.accounting_view tbody').append($line); + } + }, + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + /** + * display journal_id field + * + * @override + */ + _renderCreate: function (state) { + var self = this; + var parentPromise = this._super(state).then(function() { + self.$('.create .create_journal_id').show(); + self.$('.create .create_date').removeClass('d-none'); + self.$('.create .create_journal_id .o_input').addClass('o_required_modifier'); + }); + return parentPromise; + }, + +}); + + +return { + StatementRenderer: StatementRenderer, + ManualRenderer: ManualRenderer, + LineRenderer: LineRenderer, + ManualLineRenderer: ManualLineRenderer, +}; +}); diff --git a/base_accounting_kit/static/src/scss/account_asset.scss b/base_accounting_kit/static/src/scss/account_asset.scss new file mode 100644 index 0000000..672317f --- /dev/null +++ b/base_accounting_kit/static/src/scss/account_asset.scss @@ -0,0 +1,9 @@ +.o_web_client .o_deprec_lines_toggler { + color: #b52121; + &.o_is_posted { + color: #6f7370; + } + &.o_unposted { + color: #178230; + } +} diff --git a/base_accounting_kit/static/src/scss/style.scss b/base_accounting_kit/static/src/scss/style.scss new file mode 100644 index 0000000..db807ed --- /dev/null +++ b/base_accounting_kit/static/src/scss/style.scss @@ -0,0 +1,1164 @@ +.accounts-dashboard-wrap svg.ct-chart-bar, +.accounts-dashboard-wrap svg.ct-chart-line { + overflow: visible; +} + +.accounts-dashboard-wrap .ct-label.ct-vertical.ct-start { + color: black !important; +} + +.accounts-dashboard-wrap .ct-label.ct-label.ct-horizontal.ct-end { + position: relative; + justify-content: flex-end; + text-align: right; + transform-origin: 100%; + color: black; + transform: translate(-100%) rotate(-45deg); + white-space: nowrap; +} + + + + +.accounts-dashboard-wrap .ct-series-e .ct-slice-pie { + fill: #7b2138 !important; +} + +.accounts-dashboard-wrap .ct-series-a .ct-bar, +.accounts-dashboard-wrap .ct-series-a .ct-line, +.accounts-dashboard-wrap .ct-series-a .ct-point, +.accounts-dashboard-wrap .ct-series-a .ct-slice-donut, +.accounts-dashboard-wrap .ct-series-a .ct-slice-pie { + stroke: #009f9d !important; +} + + +.accounts-dashboard-wrap h4 { + padding-left: 20px !important; + padding-top: 10px !important; +} + + + + +.accounts-dashboard-wrap .users-list>li img { + border-radius: 50%; + height: auto; + max-width: 100%; +} + +.accounts-dashboard-wrap .badge-danger { + width: 50px; + height: 20px; + color: #fff; + background-color: #dc3545; +} + +.accounts-dashboard-wrap .card-header>.card-tools { + float: right; + margin-right: -.625rem; +} + + +.accounts-dashboard-wrap .card { + box-shadow: 0 0 1px rgba(0, 0, 0, .125), 0 1px 3px rgba(0, 0, 0, .2); + margin-bottom: 1rem; +} + +.accounts-dashboard-wrap .card-title { + float: left; + font-size: 1.1rem; + font-weight: 400; + margin: 0; + text-transform: uppercase; +} + +.accounts-dashboard-wrap .col, +.accounts-dashboard-wrap .col-1, +.accounts-dashboard-wrap .col-10, +.accounts-dashboard-wrap .col-11, +.accounts-dashboard-wrap .col-12, +.accounts-dashboard-wrap .col-2, +.accounts-dashboard-wrap .col-3, +.accounts-dashboard-wrap .col-4, +.accounts-dashboard-wrap .col-5, +.accounts-dashboard-wrap .col-6, +.accounts-dashboard-wrap .col-7, +.accounts-dashboard-wrap .col-8, +.accounts-dashboard-wrap .col-9, +.accounts-dashboard-wrap .col-auto, +.accounts-dashboard-wrap .col-lg, +.accounts-dashboard-wrap .col-lg-1, +.accounts-dashboard-wrap .col-lg-10, +.accounts-dashboard-wrap .col-lg-11, +.accounts-dashboard-wrap .col-lg-12, +.accounts-dashboard-wrap .col-lg-2, +.accounts-dashboard-wrap .col-lg-3, +.accounts-dashboard-wrap .col-lg-4, +.accounts-dashboard-wrap .col-lg-5, +.accounts-dashboard-wrap .col-lg-6, +.col-lg-7, +.col-lg-8, +.col-lg-9, +.col-lg-auto, +.col-md, +.col-md-1, +.col-md-10, +.col-md-11, +.col-md-2, +.col-md-3, +.col-md-4, +.col-md-5, +.col-md-6, +.col-md-7, +.col-md-8, +.col-md-9, +.col-md-auto, +.col-sm, +.col-sm-1, +.col-sm-10, +.col-sm-11, +.col-sm-12, +.col-sm-2, +.col-sm-3, +.col-sm-4, +.col-sm-5, +.col-sm-6, +.col-sm-7, +.col-sm-8, +.col-sm-9, +.col-sm-auto, +.col-xl, +.col-xl-1, +.col-xl-10, +.col-xl-11, +.col-xl-12, +.col-xl-2, +.col-xl-3, +.col-xl-4, +.col-xl-5, +.col-xl-6, +.col-xl-7, +.col-xl-8, +.col-xl-9, +.col-xl-auto { + position: relative; + width: 100%; + padding-right: 7.5px; + padding-left: 7.5px; +} + + +.accounts-dashboard-wrap .card-header { + background-color: + transparent; + border-bottom: 1px solid rgba(0, 0, 0, .125); + padding: .75rem 1.25rem; + position: relative; + border-top-left-radius: .25rem; + border-top-right-radius: .25rem; +} + + +.accounts-dashboard-wrap .fa:hover { + -ms-transform: scale(1.5); + /* IE 9 */ + -webkit-transform: scale(1.5); + /* Safari 3-8 */ + transform: scale(1.5); +} + +.accounts-dashboard-wrap .card-header>.card-tools { + float: right; + margin-right: -.625rem; +} + +.accounts-dashboard-wrap .right { + float: left; +} + +.accounts-dashboard-wrap .tooltip:hover .tooltiptext { + visibility: visible; +} + + +.accounts-dashboard-wrap .col-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.accounts-dashboard-wrap .fa-cog { + content: "\f013" +} + +.accounts-dashboard-wrap .fa, +.fas { + font-weight: 900; +} + +.accounts-dashboard-wrap .fa, +.accounts-dashboard-wrap .fab, +.accounts-dashboard-wrap .fad, +.accounts-dashboard-wrap .fal, +.accounts-dashboard-wrap .far, +.accounts-dashboard-wrap .fas { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-style: normal; + font-variant: normal; + text-rendering: auto; + line-height: 1; +} + +.accounts-dashboard-wrap .info-box .info-box-icon { + + border-radius: .25rem; + -ms-flex-align: center; + align-items: center; + display: -ms-flexbox; + display: flex; + font-size: 1.875rem; + -ms-flex-pack: center; + justify-content: center; + text-align: center; + width: 70px; +} + +.accounts-dashboard-wrap .info-box { + + + box-shadow: 0 0 1px rgba(0, 0, 0, .125), 0 1px 3px rgba(0, 0, 0, .2); + border-radius: .25rem; + background: #fff; + display: -ms-flexbox; + display: flex; + margin-bottom: 1rem; + min-height: 80px; + padding: .5rem; + position: relative; +} + +.accounts-dashboard-wrap .o_datepicker .o_datepicker_input { + width: 100%; + cursor: pointer; +} + +.accounts-dashboard-wrap #overdue { + width: 100%; + cursor: pointer; +} + +.accounts-dashboard-wrap .o_input { + border: 1px solid #cfcfcf; + border-top-style: none; + border-right-style: none; + border-left-style: none; +} + + +.accounts-dashboard-wrap .in_graph { + padding-left: 90px; + height: auto; + padding-bottom: 65px; + text-align: center !important; +} + + +.accounts-dashboard-wrap .oh_dashboards { + padding-top: 15px; + background-color: #f8faff !important; +} + +.accounts-dashboard-wrap .container-fluid.o_in_dashboard { + padding: 0px !important; +} + +.accounts-dashboard-wrap .o_action_manager { + overflow-y: scroll !important; + max-width: 100%; +} + +// new tile + +body { + background-color: #ececec; +} + +.accounts-dashboard-wrap .container { + margin: 50px 0 0 100px; +} + +.accounts-dashboard-wrap .o_dashboards { + color: #2a2a2a; + background-color: #f2f2f2 !important; +} + +.accounts-dashboard-wrap .dash-header { + + margin: 15px 0px 12px 0 !important; + display: block; + padding: 7px 25px 7px 0; + color: #0e1319; + font-size: 2rem; + font-weight: 400; + background-color: + rgba(255, 255, 255, 0.9) !important; + color: #212529; + padding: 1.5rem; + border-radius: 3px; + box-shadow: 0 0px 10px 0px rgba(0, 0, 0, 0.05) !important; + display: flex; + justify-content: space-between; + align-items: center; + +} + +.accounts-dashboard-wrap .dashboard-h1 { + + display: block; + padding: 7px 25px 7px 0; + color: #0e1319; + font-size: 2rem; + font-weight: 400; + color: + + #212529; + float: left; + margin-bottom: 0; + +} + +.accounts-dashboard-wrap .card { + position: relative !important; + border-top: 0 !important; + margin-bottom: 30px !important; + width: 100% !important; + background-color: #ffffff !important; + border-radius: 0.25rem !important; + padding: 0px !important; + -webkit-transition: .5s !important; + transition: .5s !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + box-shadow: 0 0px 10px 0px rgba(0, 0, 0, 0.05) !important; + border-radius: 0.25rem; +} + +.accounts-dashboard-wrap .card-header { + border: 0; + padding: 0; +} + +.accounts-dashboard-wrap .card-header>.card-tools { + float: right; + margin-right: 0.375rem; + margin-top: 5px; + margin-bottom: 10px; +} + +.accounts-dashboard-wrap .card-header i.fa { + font-size: 1.3rem; + display: inline-block; + padding: 0 0px; + margin: 0 0px; + color: #57769c; + opacity: .8; + -webkit-transition: 0.3s linear; + transition: 0.3s linear; +} + +.accounts-dashboard-wrap .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; + color: #7891af; +} + +.accounts-dashboard-wrap .account-details { + display: flex; + justify-content: space-evenly; +} + +.main-title { + color: #a3a3a3; + display: block; + margin-bottom: 5px; + font-size: 20px; + font-weight: 400; +} + +.accounts-dashboard-wrap .main-title { + display: block; + margin-bottom: 5px; + font-size: 13px; + font-weight: 600; + color: #fff !important; + text-transform: uppercase; + padding: 1rem; + border-radius: 5px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.accounts-dashboard-wrap .card-body { + background-color: rgba(255, 255, 255, 0.9) !important; + color: #212529; + padding-top: 0; +} + +.accounts-dashboard-wrap .tile.wide.invoice { + margin-bottom: 27px; + -webkit-box-shadow: 1px 5px 24px 0 rgba(68, 102, 242, 0.05); + box-shadow: 1px 5px 24px 0 rgba(68, 102, 242, 0); + background-color: #ffffff; + border-radius: 5px; + position: relative; + width: 100%; + padding: 0rem 0rem; + border: 1px solid rgba(0, 0, 0, 0.07); +} + +.accounts-dashboard-wrap .box-1 .main-title { + background: #67b7dc; + color: #fff; +} + +.accounts-dashboard-wrap .box-2 .main-title { + background: #6794dc !important; + color: #fff; +} + +.accounts-dashboard-wrap .box-3 .main-title { + background: #8067dc; + color: #fff; +} + +.accounts-dashboard-wrap .box-4 .main-title { + background: #c767dc; + color: #fff; +} + +.accounts-dashboard-wrap .count { + margin-bottom: 1rem; +} + +.accounts-dashboard-wrap span#total_invoices_ span, +.accounts-dashboard-wrap span#total_invoices_last span, +.accounts-dashboard-wrap span#total_incomes_ span, +.accounts-dashboard-wrap span#total_incomes_last span, +.accounts-dashboard-wrap span#total_expenses_ span, +.accounts-dashboard-wrap span#total_expense_last span, +.accounts-dashboard-wrap span#unreconciled_items_ span, +.accounts-dashboard-wrap span#unreconciled_items_last span, +.accounts-dashboard-wrap span#unreconciled_counts_last_year span, +.accounts-dashboard-wrap span#unreconciled_counts_this_year span, +.accounts-dashboard-wrap span#total_expense_last_year span, +.accounts-dashboard-wrap span#total_expense_this_year span, +.accounts-dashboard-wrap span#total_incomes_last_year span, +.accounts-dashboard-wrap span#total_incomes_this_year span, +.accounts-dashboard-wrap span#total_invoices_last_year span, +.accounts-dashboard-wrap span#total_invoices_this_year span, +.accounts-dashboard-wrap span#net_profit_current_months span, +.accounts-dashboard-wrap span#net_profit_current_year span { + padding-right: 8px; + font-size: 16px; + font-weight: 600; +} + +.accounts-dashboard-wrap span#total_invoices_, +.accounts-dashboard-wrap span#total_invoices_last, +.accounts-dashboard-wrap span#total_incomes_, +.accounts-dashboard-wrap span#total_incomes_last, +.accounts-dashboard-wrap span#total_expenses_, +.accounts-dashboard-wrap span#total_expense_last, +.accounts-dashboard-wrap span#unreconciled_items_, +.accounts-dashboard-wrap span#unreconciled_items_last, +.accounts-dashboard-wrap span#unreconciled_counts_last_year, +.accounts-dashboard-wrap span#unreconciled_counts_this_year, +.accounts-dashboard-wrap span#total_expense_last_year, +.accounts-dashboard-wrap span#total_expense_this_year, +.accounts-dashboard-wrap span#total_incomes_last_year, +.accounts-dashboard-wrap span#total_incomes_this_year, +.accounts-dashboard-wrap span#total_invoices_last_year, +.accounts-dashboard-wrap span#total_invoices_this_year, +.accounts-dashboard-wrap span#net_profit_current_months, +.accounts-dashboard-wrap span#net_profit_current_year { + display: -webkit-box; + display: -webkit-flex; + display: flex; + flex-direction: column; + color: #455e7b !important; +} + +.accounts-dashboard-wrap .main-title~div { + display: flex; + justify-content: space-between; + margin-top: 1rem; + padding: 1rem; + background: #fff; +} + +.accounts-dashboard-wrap .card-header { + color: #0e1319 !important; + display: block !important; + padding: 1.5rem 1.5rem !important; + position: relative !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.07) !important; + border-top-left-radius: 0.25rem !important; + border-top-right-radius: 0.25rem !important; +} + +.accounts-dashboard-wrap .card-header i.fa { + font-size: 1rem; + display: inline-block; + padding: 0 0px; + margin: 0 0px; + color: #57769c; + opacity: .8; + -webkit-transition: 0.3s linear; + transition: 0.3s linear; +} + +.accounts-dashboard-wrap .card-header>.card-tools { + float: right; + margin-right: 0; + margin-top: 0px !important; + margin-bottom: 0; +} + +.accounts-dashboard-wrap .card-tools .btn { + padding: 0 10px; + margin: 0; + line-height: normal !important; +} + +.accounts-dashboard-wrap .ct-series-a .ct-bar, +.accounts-dashboard-wrap .ct-series-a .ct-line, +.accounts-dashboard-wrap .ct-series-a .ct-point, +.accounts-dashboard-wrap .ct-series-a .ct-slice-donut, +.accounts-dashboard-wrap .ct-series-a .ct-slice-pie { + stroke: rgb(132, 60, 247) !important; +} + +.accounts-dashboard-wrap canvas#salesChart, +.accounts-dashboard-wrap canvas#exChart { + display: none; +} + +.accounts-dashboard-wrap ul#overdues li, +.accounts-dashboard-wrap ul#latebills li { + list-style: none; + padding-top: 6px; + padding-bottom: 6px; + font-size: 13px; + color: #455e7b !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.07) !important; +} + +.accounts-dashboard-wrap ul#overdues, +.accounts-dashboard-wrap ul#latebills { + padding: 0; + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.accounts-dashboard-wrap ul#overdues li a, +.accounts-dashboard-wrap ul#latebills li a { + color: #455e7b !important; +} + +.accounts-dashboard-wrap .badge-danger { + width: auto; + height: 20px; + color: #fff; + background-color: #843cf7 !important; + display: flex; + justify-content: center; + align-items: center; +} + +.accounts-dashboard-wrap .ct-label { + fill: rgba(0, 0, 0, .4); + color: rgba(0, 0, 0, .4); + font-size: 1.1rem !important; + line-height: 1; + font-weight: 600; +} + +.accounts-dashboard-wrap .ct-label { + fill: rgb(255, 255, 255) !important; + color: rgb(255, 255, 255) !important; +} + +.accounts-dashboard-wrap .chart { + .ct-legend { + position: relative; + z-index: 10; + + li { + position: relative; + padding-left: 23px; + margin-bottom: 3px; + } + + li:before { + width: 12px; + height: 12px; + position: absolute; + left: 0; + content: ''; + border: 3px solid transparent; + border-radius: 2px; + } + + li.inactive:before { + background: transparent; + } + + &.ct-legend-inside { + position: absolute; + top: 0; + right: 0; + } + + // @for $i from 0 to length($ct-series) { + // .ct-series-#{$i}:before { + // background-color: nth($ct-series, $i + 1); + // border-color: nth($ct-series, $i + 1); + // } + // } + } +} + +.accounts-dashboard-wrap #chartdiv { + width: 100%; + height: 400px; +} + +.accounts-dashboard-wrap #chartdiv_ex { + width: 100%; + height: 500px; +} + +.accounts-dashboard-wrap #barChart { + display: block; + width: 595px; + height: 396px; + // pointer-events: none; + +} + +.accounts-dashboard-wrap .canvas-con { + display: flex; + align-items: center; + justify-content: center; + min-height: 365px; + position: relative; +} + +/*p { + position: relative; + left: 194px; + margin-top: 64px; +}*/ + +.accounts-dashboard-wrap .canvas-con-inner { + height: 100%; +} + +.accounts-dashboard-wrap .canvas-con-inner, +.legend-con { + display: inline-block; +} + +.accounts-dashboard-wrap .legend-con { + font-family: Roboto; + display: inline-block; + + ul { + list-style: none; + } + + li { + display: flex; + align-items: center; + margin-bottom: 4px; + + span { + display: inline-block; + } + + span.chart-legend { + width: 25px; + height: 25px; + margin-right: 10px; + } + } +} + +html, +body { + margin: 0; +} + +.accounts-dashboard-wrap #canvas { + height: 277px !important; + width: 100% !important; +} + +.accounts-dashboard-wrap #net_profit_this_year .title { + float: left; +} + +.accounts-dashboard-wrap #net_profit_this_year { + display: flex; + justify-content: center; + align-content: center; + padding: .3rem; + background: #843cf7; + color: #fff; + border-radius: 10px; + width: auto !important; + font-weight: 600; + margin-bottom: 2rem; + margin-top: 1rem; + display: none !important; +} + +.accounts-dashboard-wrap #net_profit_last_year .title { + float: left; +} + +.accounts-dashboard-wrap #net_profit_last_year { + display: flex; + justify-content: center; + align-content: center; + padding: .3rem; + background: + #843cf7; + color: + #fff; + border-radius: 10px; + width: auto !important; + font-weight: 600; + margin-bottom: 2rem; + margin-top: 1rem; +} + +.accounts-dashboard-wrap #net_profit_last_month .title { + float: left; +} + +.accounts-dashboard-wrap #net_profit_last_month { + display: flex; + justify-content: center; + align-content: center; + padding: .3rem; + background: + #843cf7; + color: + #fff; + border-radius: 10px; + width: auto !important; + font-weight: 600; + margin-bottom: 2rem; + margin-top: 1rem; +} + + +.accounts-dashboard-wrap #net_profit_this_months .title { + float: left; +} + +.accounts-dashboard-wrap #net_profit_this_months { + display: flex; + justify-content: center; + align-content: center; + padding: .3rem; + background: + #843cf7; + color: + #fff; + border-radius: 10px; + width: auto !important; + font-weight: 600; + margin-bottom: 2rem; + margin-top: 1rem; +} + + + +.accounts-dashboard-wrap #col-graph .card { + height: 366px; +} + +.accounts-dashboard-wrap #top_10_customers { + padding: 0; +} + +.accounts-dashboard-wrap #top_10_customers li { + list-style: none; + padding-top: 6px; + padding-bottom: 6px; + font-size: 13px; + color: #455e7b !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.07) !important; + padding-left: 2rem; + display: flex; + justify-content: space-between; + padding-right: 2rem; +} + +.accounts-dashboard-wrap #top_10_customers li a { + color: + #455e7b !important; +} + +.accounts-dashboard-wrap #top_10_customers_this_month { + padding: 0; +} + +.accounts-dashboard-wrap #top_10_customers_this_month li { + list-style: none; + padding-top: 6px; + padding-bottom: 6px; + font-size: 13px; + color: #455e7b !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.07) !important; + padding-left: 2rem; + display: flex; + justify-content: space-between; + padding-right: 2rem; +} + +.accounts-dashboard-wrap #top_10_customers_this_month li a { + color: + #455e7b !important; +} + +.accounts-dashboard-wrap #top_10_customers_last_month { + padding: 0; +} + +.accounts-dashboard-wrap #top_10_customers_last_month li { + list-style: none; + padding-top: 6px; + padding-bottom: 6px; + font-size: 13px; + color: #455e7b !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.07) !important; + padding-left: 2rem; + display: flex; + justify-content: space-between; + padding-right: 2rem; +} + +.accounts-dashboard-wrap #top_10_customers_last_month li a { + color: + #455e7b !important; +} + +.accounts-dashboard-wrap #current_bank_balance { + padding: 0; +} + +.accounts-dashboard-wrap #current_bank_balance li { + + list-style: none; + padding-top: 6px; + padding-bottom: 6px; + font-size: 13px; + color: #455e7b !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.07) !important; + padding-left: 2rem; + display: flex; + justify-content: space-between; + padding-right: 2rem; + +} + +.accounts-dashboard-wrap #current_bank_balance li a { + color: + #455e7b !important; +} + +.accounts-dashboard-wrap #current_cash_balance { + padding: 0; +} + +.accounts-dashboard-wrap #current_cash_balance li { + list-style: none; + padding-top: 6px; + padding-bottom: 6px; + font-size: 13px; + color: #455e7b !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.07) !important; + padding-left: 2rem; + display: flex; + justify-content: space-between; + padding-right: 2rem; +} + +.accounts-dashboard-wrap #current_cash_balance li a { + color: #455e7b !important; + +} + + +.accounts-dashboard-wrap .custom-h1 { + font-size: 1em; + margin-bottom: 0rem; +} + +.accounts-dashboard-wrap .custom-h3 { + font-size: 1em; + margin: 0; +} + +// Progress Bars +.accounts-dashboard-wrap progress, +.accounts-dashboard-wrap progress[role] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border: none; + background-size: auto; + height: 20px; + width: 100%; + background-color: #8067dc; +} + +// The unordered list +.accounts-dashboard-wrap .skill-list { + list-style: none; + margin: 0; + padding: 1em; +} + +// The list item +.accounts-dashboard-wrap .skill { + margin-bottom: 1em; + position: relative; + + h3 { + color: #000; + left: 1em; + line-height: 1; + position: absolute; + top: 1em; + } + + ::-webkit-progress-value { + -webkit-animation: bar-fill 2s; + width: 0px; + } +} + +// Style the bar colors +.accounts-dashboard-wrap .skill-1::-webkit-progress-value { + background: #c767dc; +} + +.accounts-dashboard-wrap .skill-1::-moz-progress-bar { + background: #c767dc; +} + +// Animation Keyframes +@-webkit-keyframes bar-fill { + 0% { + width: 0; + } +} + +@keyframes bar-fill { + 0% { + width: 0; + } +} + +.accounts-dashboard-wrap #total_supplier_invoice { + color: #696969; +} + +.accounts-dashboard-wrap #total_customer_invoice_names { + color: #696969; +} + +.accounts-dashboard-wrap #total_customer_invoice { + color: #696969; +} + +.accounts-dashboard-wrap #total_invoice_difference, +.accounts-dashboard-wrap #total_supplier_difference { + color: #4ecdc4; +} + +progress { + border: 0; + border-radius: 20px; +} + +progress::-webkit-progress-bar { + border: 0; + border-radius: 20px; +} + +progress::-webkit-progress-value { + border: 0; + border-radius: 20px; +} + +progress::-moz-progress-bar { + border: 0; + border-radius: 20px; +} + +.accounts-dashboard-wrap #total_customer_invoice_paid .logo, +.accounts-dashboard-wrap #total_customer_invoice .logo, +.accounts-dashboard-wrap #total_supplier_invoice_paid .logo, +.accounts-dashboard-wrap #total_supplier_invoice .logo, +.accounts-dashboard-wrap #total_customer_invoice_paid_current_year .logo, +.accounts-dashboard-wrap #total_customer_invoice_current_year .logo, +.accounts-dashboard-wrap #total_supplier_invoice_paid_current_year .logo, +.accounts-dashboard-wrap #total_supplier_invoice_current_year .logo, +.accounts-dashboard-wrap #total_customer_invoice_paid_current_month .logo, +.accounts-dashboard-wrap #total_customer_invoice_current_month .logo, +.accounts-dashboard-wrap #total_supplier_invoice_paid_current_month .logo, +.accounts-dashboard-wrap #total_supplier_invoice_current_month .logo { + + display: -webkit-box; + display: -webkit-flex; + display: flex; + justify-content: left; + flex-direction: column-reverse; + color: #455e7b !important; + +} + +.accounts-dashboard-wrap #total_customer_invoice_paid .logo span:nth-child(2), +.accounts-dashboard-wrap #total_customer_invoice .logo span:nth-child(2), +.accounts-dashboard-wrap #total_supplier_invoice_paid .logo span:nth-child(2), +.accounts-dashboard-wrap #total_supplier_invoice .logo span:nth-child(2), +.accounts-dashboard-wrap #total_customer_invoice_paid_current_year .logo span:nth-child(2), +.accounts-dashboard-wrap #total_customer_invoice_current_year .logo span:nth-child(2), +.accounts-dashboard-wrap #total_supplier_invoice_paid_current_year .logo span:nth-child(2), +.accounts-dashboard-wrap #total_supplier_invoice_current_year .logo span:nth-child(2), +.accounts-dashboard-wrap #total_customer_invoice_paid_current_month .logo span:nth-child(2), +.accounts-dashboard-wrap #total_customer_invoice_current_month .logo span:nth-child(2), +.accounts-dashboard-wrap #total_supplier_invoice_paid_current_month .logo span:nth-child(2), +.accounts-dashboard-wrap #total_supplier_invoice_current_month .logo span:nth-child(2) { + + font-weight: 600; + +} + +.accounts-dashboard-wrap .switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; +} + +.accounts-dashboard-wrap .switch input { + opacity: 0; + width: 0; + height: 0; +} + +.accounts-dashboard-wrap .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.accounts-dashboard-wrap .slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +.accounts-dashboard-wrap input:checked+.slider { + background-color: #2196F3; +} + +.accounts-dashboard-wrap input:focus+.slider { + box-shadow: 0 0 1px #2196F3; +} + +.accounts-dashboard-wrap input:checked+.slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); +} + +/* Rounded sliders */ +.accounts-dashboard-wrap .slider.round { + border-radius: 34px; +} + +.accounts-dashboard-wrap .slider.round:before { + border-radius: 50%; +} + +.accounts-dashboard-wrap .btn-primary { + + color: #FFFFFF; + background-color: #7C7BAD; + border-color: #7C7BAD; + +} + +.accounts-dashboard-wrap .toggle-on.btn { + + padding-right: 18px !important; + right: 50%; + + +} + +.accounts-dashboard-wrap .toggle.btn.btn-default.off { + + border: 1px solid #aaa; + + background: #fff; + font-weight: 600 !important; + +} + +.accounts-dashboard-wrap .toggle-off.btn { + + padding-left: 9px !important; + +} + +.accounts-dashboard-wrap .toggle { + + width: 160px !important; + height: auto !important; + +} + +html .o_web_client>.o_action_manager { + overflow: auto !important; +}
\ No newline at end of file diff --git a/base_accounting_kit/static/src/xml/payment_matching.xml b/base_accounting_kit/static/src/xml/payment_matching.xml new file mode 100644 index 0000000..e96b3b9 --- /dev/null +++ b/base_accounting_kit/static/src/xml/payment_matching.xml @@ -0,0 +1,402 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<templates xml:space="preserve"> + +<div t-name="reconciliation" class="o_reconciliation"> + <div class="o_form_view"> + <div class="o_form_sheet_bg"> + <div class="o_form_sheet"/> + </div> + </div> +</div> + +<t t-name="reconciliation.control.pager"> + <div class="progress progress-reconciliation"> + <div aria-valuemin="0" t-att-aria-valuenow="widget._initialState.valuenow" t-att-aria-valuemax="widget._initialState.valuemax" class="progress-bar" role="progressbar" style="width: 0%;"><span class="valuenow"><t t-esc="widget._initialState.valuenow"/></span> / <span class="valuemax"><t t-esc="widget._initialState.valuemax"/></span></div> + </div> +</t> + +<t t-name="reconciliation.statement"> + <div t-if="widget._initialState.valuemax"> + <div class="notification_area"/> + <div class="o_reconciliation_lines"/> + <div t-if="widget._initialState.valuemax > widget._initialState.defaultDisplayQty"> + <button class="btn btn-secondary js_load_more">Load more</button> + </div> + </div> + <div t-else="" class="o_view_noreconciliation"> + <p>Nothing to do!</p> + <p>This page displays all the bank transactions that are to be reconciled and provides with a neat interface to do so.</p> + </div> +</t> + +<t t-name="reconciliation.manual.statement" t-extend="reconciliation.statement"> + <t t-jquery="div:first" t-operation="attributes"> + <attribute name="class" value="o_manual_statement" /> + </t> + <t t-jquery=".o_view_noreconciliation p" t-operation="replace"></t> +<!-- <t t-jquery=".o_filter_input_wrapper" t-operation="replace"></t>--> + <t t-jquery=".o_view_noreconciliation" t-operation="append"> + <p><b>Good Job!</b> There is nothing to reconcile.</p> + <p>All invoices and payments have been matched, your accounts' balances are clean.</p> + <p> + From now on, you may want to: + <ul> + <li>Check that you have no bank statement lines to <a href="#" + rel="do_action" + data-tag="bank_statement_reconciliation_view">reconcile</a></li> + <li>Verify <a href="#" + rel="do_action" + data-action_name="Unpaid Customer Invoices" + data-model="account.move" + data-domain="[('move_type', 'in', ('out_invoice', 'out_refund'))]" + data-context="{'search_default_unpaid': 1}">unpaid invoices</a> and follow-up customers</li> + <li>Pay your <a href="#" + rel="do_action" + data-action_name="Unpaid Vendor Bills" + data-model="account.move" + data-domain="[('move_type', 'in', ('in_invoice', 'in_refund'))]" + data-context="{'search_default_unpaid': 1}">vendor bills</a></li> + <li>Check all <a href="#" + rel="do_action" + data-action_name="Unreconciled Entries" + data-model="account.move.line" + data-context="{'search_default_unreconciled': 1}">unreconciled entries</a></li> + </ul> + </p> + </t> +</t> + +<div t-name="reconciliation.done" class="done_message"> + <h2>Congrats, you're all done!</h2> + <p>You reconciled <strong><t t-esc="number"/></strong> transactions in <strong><t t-esc="duration"/></strong>. + <t t-if="number > 1"> + <br/>That's on average <t t-esc="timePerTransaction"/> seconds per transaction. + </t> + </p> + <t t-if="context && context.active_model"> + <p t-if="context['active_model'] === 'account.journal' || context['active_model'] === 'account.bank.statement' || context['active_model'] === 'account.bank.statement.import'" class="actions_buttons"> + <t t-if="context.journal_id"> + <button class="button_back_to_statement btn btn-secondary" t-att-data_journal_id='context.journal_id'>Go to bank statement(s)</button> + </t> + <t t-if="context['active_model'] === 'account.bank.statement'"> + <button class="button_close_statement btn btn-primary" style="display: inline-block;">Close statement</button> + </t> + </p> + </t> +</div> + +<t t-name="reconciliation.line"> + <t t-set="state" t-value="widget._initialState"/> + <div class="o_reconciliation_line" t-att-data-mode="state.mode" tabindex="0"> + <table class="accounting_view"> + <caption style="caption-side: top;"> + <div class="float-right o_buttons"> + <button t-attf-class="o_no_valid btn btn-secondary #{state.balance.type < 0 ? '' : 'd-none'}" disabled="disabled" data-toggle="tooltip" title="Select a partner or choose a counterpart" accesskey="">Validate</button> + <button t-attf-class="o_validate btn btn-secondary #{!state.balance.type ? '' : 'd-none'}">Validate</button> + <button t-attf-class="o_reconcile btn btn-primary #{state.balance.type > 0 ? '' : 'd-none'}">Validate</button> + </div> + </caption> + <thead> + <tr> + <td class="cell_account_code"><t t-esc="state.st_line.account_code"/></td> + <td class="cell_due_date"><t t-esc="state.st_line.date"/></td> + <td class="cell_label"><t t-if="state.st_line.name" t-esc="state.st_line.name"/> <t t-if="state.st_line.amount_currency_str"> (<t t-esc="state.st_line.amount_currency_str"/>)</t></td> + <td class="cell_left"><t t-if="state.st_line.amount > 0"><t t-raw="state.st_line.amount_str"/></t></td> + <td class="cell_right"><t t-if="state.st_line.amount < 0"><t t-raw="state.st_line.amount_str"/></t></td> + <td class="cell_info_popover"></td> + </tr> + </thead> + <tbody> + <t t-foreach="state.reconciliation_proposition" t-as="line"><t t-call="reconciliation.line.mv_line"/></t> + </tbody> + <tfoot> + <t t-call="reconciliation.line.balance"/> + </tfoot> + </table> + <div class="o_notebook"> + <div class="o_notebook_headers"> + <ul class="nav nav-tabs ml-0 mr-0"> + <li class="nav-item" t-attf-title="{{'Match statement with existing lines on receivable/payable accounts<br>* Black line: existing journal entry that should be matched<br>* Blue lines: existing payment that should be matched'}}" data-toggle="tooltip"><a data-toggle="tab" disable_anchor="true" t-attf-href="#notebook_page_match_rp_#{state.st_line.id}" class="nav-link active nav-match_rp" role="tab" aria-selected="true">Customer/Vendor Matching</a></li> + <li class="nav-item" title="Match with entries that are not from receivable/payable accounts" data-toggle="tooltip"><a data-toggle="tab" disable_anchor="true" t-attf-href="#notebook_page_match_other_#{state.st_line.id}" class="nav-link nav-match_other" role="tab" aria-selected="false">Miscellaneous Matching</a></li> + <li class="nav-item" title="Create a counterpart" data-toggle="tooltip"><a data-toggle="tab" disable_anchor="true" t-attf-href="#notebook_page_create_#{state.st_line.id}" class="nav-link nav-create" role="tab" aria-selected="false">Manual Operations</a></li> + </ul> + </div> + <div class="tab-content"> + <div class="tab-pane active" t-attf-id="notebook_page_match_rp_#{state.st_line.id}"> + <div class="match"> + <t t-call="reconciliation.line.match"/> + </div> + </div> + <div class="tab-pane" t-attf-id="notebook_page_match_other_#{state.st_line.id}"> + <div class="match"> + <t t-call="reconciliation.line.match"/> + </div> + </div> + <div class="tab-pane" t-attf-id="notebook_page_create_#{state.st_line.id}"> + <div class="create"></div> + </div> + </div> + </div> + </div> +</t> + +<t t-name="reconciliation.manual.line" t-extend="reconciliation.line"> + <t t-jquery=".o_buttons" t-operation="replace"> + <div class="float-right o_buttons"> + <button t-attf-class="o_validate btn btn-secondary #{!state.balance.type ? '' : 'd-none'}">Reconcile</button> + <button t-attf-class="o_reconcile btn btn-primary #{state.balance.type > 0 ? '' : 'd-none'}">Reconcile</button> + <button t-attf-class="o_no_valid btn btn-secondary #{state.balance.type < 0 ? '' : 'd-none'}">Skip</button> + </div> + </t> + <t t-jquery=".accounting_view tbody" t-operation="append"> + <t t-if='!_.filter(state.reconciliation_proposition, {"display": true}).length'> + <t t-set="line" t-value='{}'/> + <t t-call="reconciliation.line.mv_line"/> + </t> + </t> + <t t-jquery=".accounting_view thead tr" t-operation="replace"> + <tr> + <td colspan="3"><span/><span t-if="state.last_time_entries_checked">Last Reconciliation: <t t-esc="state.last_time_entries_checked"/></span></td> + <td colspan="2"><t t-esc="state.st_line.account_code"/></td> + <td class="cell_info_popover"></td> + </tr> + </t> + <t t-jquery='div[t-attf-id*="notebook_page_match_rp"]' t-operation="replace"/> + <t t-jquery='a[t-attf-href*="notebook_page_match_rp"]' t-operation="replace"/> +</t> + +<t t-name="reconciliation.line.balance"> + <tr t-if="state.balance.show_balance"> + <td class="cell_account_code"><t t-esc="state.balance.account_code"/></td> + <td class="cell_due_date"></td> + <td class="cell_label"><t t-if="state.st_line.partner_id">Open balance</t><t t-else="">Choose counterpart or Create Write-off</t></td> + <td class="cell_left"><t t-if="state.balance.amount_currency < 0"><span role="img" t-if="state.balance.amount_currency_str" t-attf-class="o_multi_currency o_multi_currency_color_#{state.balance.currency_id%8} line_info_button fa fa-money" t-att-data-content="state.balance.amount_currency_str" t-att-aria-label="state.balance.amount_currency_str" t-att-title="state.balance.amount_currency_str"/><t t-raw="state.balance.amount_str"/></t></td> + <td class="cell_right"><t t-if="state.balance.amount_currency > 0"><span role="img" t-if="state.balance.amount_currency_str" t-attf-class="o_multi_currency o_multi_currency_color_#{state.balance.currency_id%8} line_info_button fa fa-money" t-att-data-content="state.balance.amount_currency_str" t-att-aria-label="state.balance.amount_currency_str" t-att-title="state.balance.amount_currency_str"/><t t-raw="state.balance.amount_str"/></t></td> + <td class="cell_info_popover"></td> + </tr> +</t> + + +<div t-name="reconciliation.line.match"> + <div class="match_controls"> + <span><input class="filter o_input" placeholder="Filter on account, label, partner, amount,..." type="text" t-att-value="state['filter_{{state.mode}}']"/></span> + <button class="btn btn-secondary btn-sm fa fa-search" type="button"></button> + </div> + <table> + <tbody> + </tbody> + </table> + <div class="load-more text-center"> + <a href="#">Load more... (<span></span> remaining)</a> + </div> +</div> + + +<div t-name="reconciliation.line.create"> + <div class="quick_add"> + <div class="btn-group o_reconcile_models" t-if="state.reconcileModels"> + <t t-foreach="state.reconcileModels" t-as="reconcileModel"> + <button class="btn btn-primary" + t-if="reconcileModel.rule_type === 'writeoff_button' && (reconcileModel.match_journal_ids.length == 0 || reconcileModel.match_journal_ids.includes(state.st_line.journal_id) || state.st_line.journal_id === undefined)" + t-att-data-reconcile-model-id="reconcileModel.id"> + <t t-esc="reconcileModel.name"/> + </button> + </t> + <p t-if="!state.reconcileModels.length" style="color: #bbb;">To speed up reconciliation, define <a style="cursor: pointer;" class="reconcile_model_create">reconciliation models</a>.</p> + </div> + <div class="dropdown float-right"> + <a data-toggle="dropdown" href="#"><span class="fa fa-cog" role="img" aria-label="Settings"/></a> + <div class="dropdown-menu dropdown-menu-right" role="menu" aria-label="Presets config"> + <a role="menuitem" class="dropdown-item reconcile_model_create" href="#">Create model</a> + <a role="menuitem" class="dropdown-item reconcile_model_edit" href="#">Modify models</a> + </div> + </div> + </div> + <div class="clearfix o_form_sheet"> + <div class="o_group"> + <table class="o_group o_inner_group o_group_col_6"> + <tbody> + <tr class="create_account_id"> + <td class="o_td_label"><label class="o_form_label">Account</label></td> + <td class="o_td_field"></td> + </tr> + <tr class="create_tax_id"> + <td class="o_td_label"><label class="o_form_label">Taxes</label></td> + <td class="o_td_field"></td> + </tr> + <tr class="create_analytic_account_id" t-if="group_acc"> + <td class="o_td_label"><label class="o_form_label">Analytic Acc.</label></td> + <td class="o_td_field"></td> + </tr> + <tr class="create_analytic_tag_ids" t-if="group_tags"> + <td class="o_td_label"><label class="o_form_label">Analytic Tags.</label></td> + <td class="o_td_field"></td> + </tr> + </tbody> + </table> + <table class="o_group o_inner_group o_group_col_6"> + <tbody> + <tr class="create_journal_id" style="display: none;"> + <td class="o_td_label"><label class="o_form_label">Journal</label></td> + <td class="o_td_field"></td> + </tr> + <tr class="create_label"> + <td class="o_td_label"><label class="o_form_label">Label</label></td> + <td class="o_td_field"></td> + </tr> + <tr class="create_amount"> + <td class="o_td_label"><label class="o_form_label">Amount</label></td> + <td class="o_td_field"></td> + </tr> + <tr class="create_force_tax_included d-none"> + <td class="o_td_label"><label class="o_form_label">Tax Included in Price</label></td> + <td class="o_td_field"></td> + </tr> + <tr class="create_date d-none"> + <td class="o_td_label"><label class="o_form_label">Writeoff Date</label></td> + <td class="o_td_field"></td> + </tr> + <tr class="create_to_check"> + <td class="o_td_label"><label class="o_form_label">To Check</label></td> + <td class="o_td_field"></td> + </tr> + </tbody> + </table> + </div> +</div> + <div class="add_line_container"> + <a class="add_line" t-att-style="!state.balance.amout ? 'display: none;' : null"><i class="fa fa-plus-circle"/> Save and New</a> + </div> +</div> + + +<t t-name="reconciliation.line.mv_line.amount"> + <span t-att-class="(line.is_move_line && proposition == true) ? 'cell' : ''"> + <span class="line_amount"> + <span t-if="line.amount_currency_str" + t-attf-class="o_multi_currency o_multi_currency_color_#{line.currency_id%8} line_info_button fa fa-money" + t-att-data-content="line.amount_currency_str"/> + <span t-if="line.partial_amount && line.partial_amount != line.amount" class="strike_amount text-muted"> + <t t-raw="line.amount_str"/> + <br/> + </span> + </span> + <t t-if="line.is_move_line && proposition == true"> + <i class="fa fa-pencil edit_amount"></i> + <input class="edit_amount_input text-right d-none"/> + </t> + <span class="line_amount"> + <t t-if="!line.partial_amount_str" t-raw="line.amount_str"/> + <t t-if="line.partial_amount_str && line.partial_amount != line.amount" t-raw="line.partial_amount_str"/> + </span> + </span> +</t> + + +<t t-name="reconciliation.line.mv_line"> + <tr t-if="line.display !== false" t-attf-class="mv_line #{line.already_paid ? ' already_reconciled' : ''} #{line.__invalid ? 'invalid' : ''} #{line.is_tax ? 'is_tax' : ''}" t-att-data-line-id="line.id" t-att-data-selected="selected"> + <td class="cell_account_code"><t t-esc="line.account_code"/>​</td> <!-- zero width space to make empty lines the height of the text --> + <td class="cell_due_date"> + <t t-if="typeof(line.id) != 'number' && line.id"> + <span class="badge badge-secondary">New</span> + </t> + <t t-else="" t-esc="line.date_maturity || line.date"/> + </td> + <td class="cell_label"> + <t t-if="line.partner_id && line.partner_id !== state.st_line.partner_id"> + <t t-if="line.partner_name.length"> + <span class="font-weight-bold" t-esc="line.partner_name"/>: + </t> + </t> + <t t-esc="line.label || line.name"/> + <t t-if="line.ref && line.ref.length"> : </t> + <t t-esc="line.ref"/> + </td> + <td class="cell_left"> + <t t-if="line.amount < 0"> + <t t-call="reconciliation.line.mv_line.amount"/> + </t> + </td> + <td class="cell_right"> + <t t-if="line.amount > 0"> + <t t-call="reconciliation.line.mv_line.amount"/> + </t> + </td> + <td class="cell_info_popover"></td> + </tr> +</t> + + +<t t-name="reconciliation.line.mv_line.details"> + <table class='details'> + <tr t-if="line.account_code"><td>Account</td><td><t t-esc="line.account_code"/> <t t-esc="line.account_name"/></td></tr> + <tr><td>Date</td><td><t t-esc="line.date"/></td></tr> + <tr><td>Due Date</td><td><t t-esc="line.date_maturity || line.date"/></td></tr> + <tr><td>Journal</td><td><t t-esc="line.journal_id.display_name"/></td></tr> + <tr t-if="line.partner_id"><td>Partner</td><td><t t-esc="line.partner_name"/></td></tr> + <tr><td>Label</td><td><t t-esc="line.label"/></td></tr> + <tr t-if="line.ref"><td>Ref</td><td><t t-esc="line.ref"/></td></tr> + <tr><td>Amount</td><td><t t-raw="line.total_amount_str"/><t t-if="line.total_amount_currency_str"> (<t t-esc="line.total_amount_currency_str"/>)</t></td></tr> + <tr t-if="line.is_partially_reconciled"><td>Residual</td><td> + <t t-raw="line.amount_str"/><t t-if="line.amount_currency_str"> (<t t-esc="line.amount_currency_str"/>)</t> + </td></tr> + <tr class="one_line_info" t-if='line.already_paid'> + <td colspan="2">This payment is registered but not reconciled.</td> + </tr> + </table> +</t> + + +<t t-name="reconciliation.line.statement_line.details"> + <table class='details'> + <tr><td>Date</td><td><t t-esc="state.st_line.date"/></td></tr> + <tr t-if="state.st_line.partner_name"><td>Partner</td><td><t t-esc="state.st_line.partner_name"/></td></tr> + <tr t-if="state.st_line.ref"><td>Transaction</td><td><t t-esc="state.st_line.ref"/></td></tr> + <tr><td>Description</td><td><t t-esc="state.st_line.name"/></td></tr> + <tr><td>Amount</td><td><t t-raw="state.st_line.amount_str"/><t t-if="state.st_line.amount_currency_str"> (<t t-esc="state.st_line.amount_currency_str"/>)</t></td></tr> + <tr><td>Account</td><td><t t-esc="state.st_line.account_code"/> <t t-esc="state.st_line.account_name"/></td></tr> + <tr t-if="state.st_line.note"><td>Note</td><td style="white-space: pre;"><t t-esc="state.st_line.note"/></td></tr> + </table> +</t> + + +<t t-name="reconciliation.notification.reconciled"> + <t t-if="details !== undefined"> + <a rel="do_action" href="#" aria-label="External link" title="External link" + t-att-data-action_name="details.name" + t-att-data-model="details.model" + t-att-data-ids="details.ids"> + <t t-esc="nb_reconciled_lines"/> + statement lines + </a> + have been reconciled automatically. + </t> +</t> + + +<t t-name="reconciliation.notification.default"> + <t t-esc="message"/> + <t t-if="details !== undefined"> + <a class="fa fa-external-link" rel="do_action" href="#" aria-label="External link" title="External link" + t-att-data-action_name="details.name" + t-att-data-model="details.model" + t-att-data-ids="details.ids"> + </a> + </t> +</t> + + +<t t-name="reconciliation.notification"> + <div t-att-class="'notification alert-dismissible alert alert-' + type" role="alert"> + <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span title="Close" class="fa fa-times"></span></button> + <t t-if="template"> + <t t-call="{{template}}"/> + </t> + <t t-else=""> + <t t-call="reconciliation.notification.default"/> + </t> + </div> +</t> + +</templates> diff --git a/base_accounting_kit/static/src/xml/template.xml b/base_accounting_kit/static/src/xml/template.xml new file mode 100644 index 0000000..d468f00 --- /dev/null +++ b/base_accounting_kit/static/src/xml/template.xml @@ -0,0 +1,324 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<templates id="template" xml:space="preserve"> + <t t-name="Invoicedashboard"> + <div class="accounts-dashboard-wrap"> + <div class="o_dashboards col-xs-12 col-sm-12 col-lg-12 col-md-12" style="background-color: #e1e1e1;overflow: scroll; !important; "> + <div class="content-header"> + <div class="container-fluid"> + <div class="row mb-2"> + <div class="col-sm-12"> + <div class="dash-header"> + <h1 class="custom-h1 dashboard-h1">Dashboard </h1> + <input type="checkbox" style="display:none" data-toggle="toggle" data-on="" data-off=""> + <input type="checkbox" id="toggle-two"></input> + </input> + </div> + </div> + </div> + </div> + </div> + </div> + <div class="row" style="margin:0px"> + <div class="col-xs-12 col-sm-12 col-lg-12 col-md-12"> + <div class=""> + <div class="row account-details" style="margin:0px"> + <div class="col-md-3"> + <!-- Net Profit or Loss --> + <div class="tile wide invoice box-1"> + <div class="headers"> + <div class="main-title">Net Profit or Loss</div> + <div id="monthly_invoice"> + <div class="left"> + <div class="count"> + <span id="net_profit_current_year" /> + </div> + </div> + <div class="right"> + <div class="count"> + <span id="net_profit_current_months" /> + </div> + </div> + </div> + </div> + </div> + </div> + <!-- Total Income --> + <div class="col-md-3"> + <div class="tile wide invoice box-2"> + <div class="header"> + <div class="main-title">Total Income</div> + <div id="monthly_income"> + <div class="left"> + <div class="count"> + <span id="total_incomes_this_year" /> + </div> + </div> + <div class="right"> + <div class="count"> + <span id="total_incomes_" /> + </div> + </div> + </div> + </div> + </div> + </div> + <!-- Total Expense --> + <div class="col-md-3"> + <div class="tile wide invoice box-3"> + <div class="header"> + <div class="main-title">Total Expenses</div> + <div id="monthly_expense"> + <div class="left"> + <div class="count"> + <span id="total_expense_this_year" /> + </div> + </div> + <div class="right"> + <div class="count"> + <span id="total_expenses_" /> + </div> + </div> + </div> + </div> + </div> + </div> + <!-- Unreconciled items --> + <div class="col-md-3"> + <div class="tile wide invoice box-4"> + <div class="header"> + <div class="main-title">Unreconciled items</div> + <div id="monthly_unreconciled"> + <div class="left"> + <div class="count"> + <span id="unreconciled_counts_this_year" /> + </div> + </div> + <div class="right"> + <div class="count"> + <span id="unreconciled_items_" /> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + <!-- <div class="row" style="margin:0px">--> + <div class="col-xs-12 col-sm-12 col-lg-12 col-md-12"> + <div class="row" style="margin:0px"> + <div class="col-md-4" id="col-graph"> + <div class="card"> + <div class="card-header"> + <div class="card-title"> + <b> + <h3 class="custom-h3">Income/Expense</h3> + </b> + </div> + <div class="card-tools"> + <select id="income_expense_values"> + <option id="income_this_year" value="income_this_year">This Year</option> + <option id="income_this_month" value="income_this_month">This Month</option> + <div role="separator" class="dropdown-divider" /> + <option id="income_last_month" value="income_last_month">Last Month</option> + <option id="income_last_year" value="income_this_year">Last Year</option> + </select> + </div> + </div> + <div class="card-body mt-3" id="in_ex_body_hide"> + <div class="row"> + <div class="col-md-12"> + <p id="myelement1"> </p> + <div class="chart"> + <canvas id="canvas" width="300" height="200"> </canvas> + </div> + </div> + </div> + </div> + </div> + </div> + <div class="col-md-4" id="col-graph"> + <div class="card"> + <div class="card-header"> + <div class="card-title"> + <b> + <h3 class="custom-h3">INVOICES</h3> + </b> + </div> + <div class="card-tools"> + <select id="invoice_values"> + <option id="invoice_this_month" value="this_month">This Month</option> + <option id="invoice_this_year" value="this_year">This Year</option> + </select> + </div> + </div> + <!-- /.card-header --> + <div class="card-body" id=""> + <div class="row"> + <div class="col-md-12 mt-3"> + <h1 class="custom-h1" style="margin-bottom: 0;">Customer Invoice</h1> + <ul class="skill-list"> + <li class="skill" style="display: flex;justify-content: space-between;color: #000;"> + <p id="total_customer_invoice_paid" /> + <p id="total_customer_invoice" /> + <p id="total_customer_invoice_paid_current_year" /> + <p id="total_customer_invoice_current_year" /> + <p id="total_customer_invoice_paid_current_month" /> + <p id="total_customer_invoice_current_month" /> + </li> + <li> + <progress id="tot_invoice" class="skill-1" max="" value=""> + <strong>Skill Level: 50%</strong> + </progress> + </li> + <li> + <progress id="tot_invoice_current_year" class="skill-1" max="" value=""> + <strong>Skill Level: 50%</strong> + </progress> + </li> + <li> + <progress id="tot_invoice_current_month" class="skill-1" max="" value=""> + <strong>Skill Level: 50%</strong> + </progress> + </li> + </ul> + <div role="separator" class="dropdown-divider" /> + <h1 class="custom-h1" style="margin-bottom: 0;">Supplier Invoice</h1> + <ul class="skill-list"> + <li class="skill" style="display: flex;justify-content: space-between;color: #000;"> + <p id="total_supplier_invoice_paid"/> + <p id="total_supplier_invoice" /> + <p id="total_supplier_invoice_paid_current_year"/> + <p id="total_supplier_invoice_current_year" /> + <p id="total_supplier_invoice_paid_current_month"/> + <p id="total_supplier_invoice_current_month" /> + </li> + <li> + <progress id="tot_supplier_inv" class="skill-1" max="" value=""> + <strong>Skill Level: 50%</strong> + </progress> + </li> + <li> + <progress id="tot_supplier_inv_current_year" class="skill-1" max="" value=""> + <strong>Skill Level: 50%</strong> + </progress> + </li> + <li> + <progress id="tot_supplier_inv_current_month" class="skill-1" max="" value=""> + <strong>Skill Level: 50%</strong> + </progress> + </li> + </ul> + </div> + </div> + </div> + </div> + </div> + <div class="col-md-4" id="col-graph"> + <div class="card"> + <div class="card-body p-0" style=" height: 287px; overflow-y: auto; "> + <div class="card-header" style=" padding: 17px 1.5rem !important; display: flex !IMPORTANT; justify-content: space-between; align-items: center; "> + <h3 class="custom-h3 card-title"> + <b>BANK AND CASH BALANCE</b> + </h3> + </div> + <div class="card-body p-0" style=" height: 100px; " id="bank_balance_body_hide"> + <ul id="current_bank_balance"></ul> + </div> + </div> + </div> + </div> + <div class="col-md-4" id="col-graph"> + <div class="card"> + <div class="card-header"> + <div class="card-title"> + <b> + <h3 class="custom-h3">Aged Receivable</h3> + </b> + </div> + <div class="card-tools"> + <select id="aged_receivable_values"> + <option id="aged_payable_this_month" value="this_month">This Month</option> + <option id="aged_payable_this_year" value="this_year">This Year</option> + </select> + </div> + </div> + <!-- /.card-header --> + <div class="card-body" id="ex_body"> + <div class="row"> + <div class="col-md-12"> + <div> + <canvas id="canvas1" height="250px" width="400px"></canvas> + </div> + </div> + </div> + </div> + </div> + </div> + <div class="col-md-4" id="col-graph"> + <div class="card"> + <div class="card-header"> + <div class="card-title"> + <b> + <h3 class="custom-h3">Aged Payable</h3> + </b> + </div> + <div class="card-tools"> + <select id="aged_payable_value"> + <option id="aged_receivable_this_month" value="this_month">This Month</option> + <option id="aged_receivable_this_year" value="this_year">This Year</option> + </select> + </div> + </div> + <!-- /.card-header --> + <div class="card-body" id="aged_payable_body_hide"> + <div class="row"> + <div class="col-md-12"> + <div id="chart"> + <canvas id="horizontalbarChart" width="400" height="250"></canvas> + </div> + </div> + </div> + </div> + </div> + </div> + <div class="col-md-4"> + <div class="card" style="height:366px;"> + <div class="card-header" style=" padding: 17px 1.5rem !important; display: flex !IMPORTANT; justify-content: space-between; align-items: center; "> + <h3 class="custom-h3 card-title"> + <b>TOP 10 CUSTOMERS</b> + </h3> + + <div class="card-tools"> + <select id="top_10_customer_value"> +<!-- <option id="null" value="null"></option>--> + <option id="top_10_customer_this_month" value="this_month">This Month</option> + <div role="separator" class="dropdown-divider" /> + <option id="top_10_customer_last_month" value="last_month">Last Month</option> + + </select> + </div> + </div> + + <div class="card-body p-0" style=" height: 287px; overflow-y: auto; " id="top_10_body"> +<!-- <ul class="users-list clearfix" id="top_10_customers"></ul>--> + <ul class="users-list clearfix" id="top_10_customers_this_month"></ul> +<!-- <ul class="users-list clearfix" id="top_10_customers_last_month"></ul>--> + </div> + </div> + </div> + </div> + </div> + <!-- </div>--> + <div class="container-fluid o_hr_dashboard"> + <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3" id="invoice_grapg" /> + <div class="dashboard-header-filter"> + <div class="manager_filter_class" /> + </div> + </div> + <div id="chart-container"></div> + </div> + </t> +</templates>
\ No newline at end of file |
