summaryrefslogtreecommitdiff
path: root/base_accounting_kit/static/src
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 17:14:58 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 17:14:58 +0700
commit1ca3b3df3421961caec3b747a364071c80f5c7da (patch)
tree6778a1f0f3f9b4c6e26d6d87ccde16e24da6c9d6 /base_accounting_kit/static/src
parentb57188be371d36d96caac4b8d65a40745c0e972c (diff)
initial commit
Diffstat (limited to 'base_accounting_kit/static/src')
-rw-r--r--base_accounting_kit/static/src/js/account_asset.js87
-rw-r--r--base_accounting_kit/static/src/js/account_dashboard.js1844
-rw-r--r--base_accounting_kit/static/src/js/payment_matching.js505
-rw-r--r--base_accounting_kit/static/src/js/payment_model.js1881
-rw-r--r--base_accounting_kit/static/src/js/payment_render.js929
-rw-r--r--base_accounting_kit/static/src/scss/account_asset.scss9
-rw-r--r--base_accounting_kit/static/src/scss/style.scss1164
-rw-r--r--base_accounting_kit/static/src/xml/payment_matching.xml402
-rw-r--r--base_accounting_kit/static/src/xml/template.xml324
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>' + '&nbsp;&nbsp;&nbsp;&nbsp;' + '<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>'+ '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + '<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] +'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'+ 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 &nbsp;&nbsp;&nbsp;</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 &nbsp;&nbsp;&nbsp;</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 &nbsp;&nbsp;&nbsp;</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 &nbsp;&nbsp;&nbsp;</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 &gt; 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 &amp;&amp; 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 &lt; 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 &gt; 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 &gt; 0"><t t-raw="state.st_line.amount_str"/></t></td>
+ <td class="cell_right"><t t-if="state.st_line.amount &lt; 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&lt;br&gt;* Black line: existing journal entry that should be matched&lt;br&gt;* 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 &gt; 0 ? '' : 'd-none'}">Reconcile</button>
+ <button t-attf-class="o_no_valid btn btn-secondary #{state.balance.type &lt; 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 &lt; 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 &gt; 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' &amp;&amp; (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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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"/>&#8203;</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' &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; line.ref.length"> : </t>
+ <t t-esc="line.ref"/>
+ </td>
+ <td class="cell_left">
+ <t t-if="line.amount &lt; 0">
+ <t t-call="reconciliation.line.mv_line.amount"/>
+ </t>
+ </td>
+ <td class="cell_right">
+ <t t-if="line.amount &gt; 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