From a6d250a05364f1115d7f74ddd0da05446dc77cfb Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Tue, 18 Sep 2018 15:42:51 +0000 Subject: [PATCH] [IMP] sale: notify matching SO in reconciliation widget We now display a message informing the user that the statement line is matching a sent/to_invoice SO. The user can thus decide to follow the link and confirm the SO, create the invoice and come back on the reconciliation widget to reconcile the prepayment with the validated invoice (automatically detected). Was task #1879015 closes odoo/odoo#29312 --- .../reconciliation/reconciliation_action.js | 21 +++- .../js/reconciliation/reconciliation_model.js | 99 ++++++++++--------- .../reconciliation/reconciliation_renderer.js | 19 +++- addons/sale/__manifest__.py | 3 + addons/sale/models/__init__.py | 1 + .../models/account_reconciliation_widget.py | 42 ++++++++ .../static/src/js/account_reconciliation.js | 68 +++++++++++++ .../static/src/xml/account_reconciliation.xml | 15 +++ addons/sale/views/assets.xml | 1 + 9 files changed, 220 insertions(+), 49 deletions(-) create mode 100644 addons/sale/models/account_reconciliation_widget.py create mode 100644 addons/sale/static/src/js/account_reconciliation.js create mode 100644 addons/sale/static/src/xml/account_reconciliation.xml diff --git a/addons/account/static/src/js/reconciliation/reconciliation_action.js b/addons/account/static/src/js/reconciliation/reconciliation_action.js index b0bb71abdbe..4b097c79d5b 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_action.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_action.js @@ -32,6 +32,7 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, { change_name: '_onChangeName', close_statement: '_onCloseStatement', load_more: '_onLoadMore', + reload: 'reload', }, config: { // used to instantiate the model @@ -100,6 +101,21 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, { }); }, + reload: function() { + // On reload destroy all rendered line widget, reload data and then rerender widget + var self = this; + _.each(this.widgets, function(widget) { + widget.destroy(); + }) + this.widgets = []; + this.model.reload() + .then(function() { + self.$('.o_reconciliation_lines').html(''); + self._renderLines(); + self._openFirstLine(); + }); + }, + /** * append the renderer and instantiate the line renderers * @@ -199,7 +215,10 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, { widget.appendTo(self.$('.o_reconciliation_lines')); }); if (this.model.hasMoreLines() === false) { - this.renderer.hideLoadMoreButton(); + this.renderer.hideLoadMoreButton(true); + } + else { + this.renderer.hideLoadMoreButton(false); } }, diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index bc16bd73afa..a192b2210af 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -344,22 +344,65 @@ var StatementModel = BasicModel.extend({ */ load: function (context) { var self = this; - var statement_ids = context.statement_ids; - if (!statement_ids) { + // var statement_ids = context.statement_ids; + this.statement_ids = context.statement_ids; + if (!this.statement_ids) { return $.when(); } this.context = context; - + return self.reload(); + + }, + /** + * Load more bank statement line + * + * @param {integer} qty quantity to load + * @returns {Deferred} + */ + 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 + * + * @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 {Deferred} + */ + loadData: function(ids, excluded_ids) { + var self = this; + return self._rpc({ + model: 'account.reconciliation.widget', + method: 'get_bank_statement_line_data', + args: [ids, excluded_ids], + context: self.context, + }) + .then(self._formatLine.bind(self)); + }, + /** + * 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', - args: [statement_ids], + args: [self.statement_ids], }) .then(function (statement) { self.statement = statement; - self.bank_statement_id = statement_ids.length === 1 ? {id: statement_ids[0], display_name: statement.statement_name} : false; - self.valuenow = statement.value_min; - self.valuemax = statement.value_max; + self.bank_statement_id = self.statement_ids.length === 1 ? {id: self.statement_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'); @@ -378,11 +421,11 @@ var StatementModel = BasicModel.extend({ }); }); var domainReconcile = []; - if (context && context.company_ids) { - domainReconcile.push(['company_id', 'in', context.company_ids]); + if (self.context && self.context.company_ids) { + domainReconcile.push(['company_id', 'in', self.context.company_ids]); } - if (context && context.active_model === 'account.journal' && context.active_ids) { - domainReconcile.push(['journal_id', 'in', [false].concat(context.active_ids)]); + if (self.context && self.context.active_model === 'account.journal' && self.context.active_ids) { + domainReconcile.push(['journal_id', 'in', [false].concat(self.context.active_ids)]); } var def_reconcileModel = this._rpc({ model: 'account.reconcile.model', @@ -428,40 +471,6 @@ var StatementModel = BasicModel.extend({ }) }); }, - /** - * Load more bank statement line - * - * @param {integer} qty quantity to load - * @returns {Deferred} - */ - 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 - * - * @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 {Deferred} - */ - loadData: function(ids, excluded_ids) { - var self = this; - 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']); - }) - }, /** * Add lines into the propositions from the reconcile model * Can add 2 lines, and each with its taxes. The second line become editable diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js index 1f2eaea2432..f0641eae409 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js @@ -82,8 +82,13 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, { /* * hide the button to load more statement line */ - hideLoadMoreButton: function () { - this.$('.js_load_more').hide(); + hideLoadMoreButton: function (show) { + if (!show) { + this.$('.js_load_more').show(); + } + else { + this.$('.js_load_more').hide(); + } }, showRainbowMan: function (state) { var dt = Date.now()-this.time; @@ -300,7 +305,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { } ) }; - self.fields.partner_id.appendTo(self.$('.accounting_view caption')); + self.fields.partner_id.insertAfter(self.$('.accounting_view caption .o_buttons')); }); $('') .appendTo(this.$('thead .cell_info_popover')) @@ -599,11 +604,15 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { */ _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) { @@ -627,6 +636,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { */ _onEditReconcileModel: function (event) { event.preventDefault(); + var self = this; this.do_action({ type: 'ir.actions.act_window', res_model: 'account.reconcile.model', @@ -634,6 +644,9 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { view_type: "list", view_mode: "list", target: 'current' + }, + { + on_reverse_breadcrumb: function() {self.trigger_up('reload');}, }); }, /** diff --git a/addons/sale/__manifest__.py b/addons/sale/__manifest__.py index ec9faf38bd2..3c316a4ccea 100644 --- a/addons/sale/__manifest__.py +++ b/addons/sale/__manifest__.py @@ -39,6 +39,9 @@ This module contains all the common features of Sales Management and eCommerce. 'data/sale_demo.xml', 'data/product_product_demo.xml', ], + 'qweb': [ + "static/src/xml/account_reconciliation.xml", + ], 'uninstall_hook': "uninstall_hook", 'installable': True, 'auto_install': False diff --git a/addons/sale/models/__init__.py b/addons/sale/models/__init__.py index e2e2d1b5c66..de4ebb6bff9 100644 --- a/addons/sale/models/__init__.py +++ b/addons/sale/models/__init__.py @@ -3,6 +3,7 @@ from . import analytic from . import account_invoice +from . import account_reconciliation_widget from . import product_product from . import product_template from . import res_company diff --git a/addons/sale/models/account_reconciliation_widget.py b/addons/sale/models/account_reconciliation_widget.py new file mode 100644 index 00000000000..ff6612a0891 --- /dev/null +++ b/addons/sale/models/account_reconciliation_widget.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +from odoo import api, models + + +class AccountReconciliation(models.AbstractModel): + _inherit = 'account.reconciliation.widget' + + def _get_sales_order(self, res): + stl_ids = [l.get('st_line', {}).get('id') for l in res.get('lines', [])] + if not stl_ids: + return res + # Search if we can find a sale order line that match the statement reference + sql_query = """ + SELECT stl.id, array_agg(o.id) AS order_id + FROM sale_order o, + account_bank_statement_line stl + WHERE + lower(concat(o.name,o.reference)) ~ lower(stl.name) + AND stl.id IN %s + AND (stl.partner_id is null OR stl.partner_id = o.partner_id) + AND (o.invoice_status = 'to invoice' OR o.state = 'sent') + AND o.company_id = %s + GROUP BY stl.id + ORDER BY stl.id + """ + company_id = res.get('lines')[0].get('st_line', {}).get('company_id') + self.env.cr.execute(sql_query, (tuple(stl_ids), company_id)) + results = {} + for el in self.env.cr.dictfetchall(): + results[el.get('id')] = el.get('order_id') + for line in res.get('lines', []): + so_ids = results.get(line['st_line'].get('id')) + if so_ids: + line['sale_order_ids'] = so_ids + return res + + @api.model + def get_bank_statement_line_data(self, st_line_ids, excluded_ids=None): + res = super(AccountReconciliation, self).get_bank_statement_line_data(st_line_ids=st_line_ids, excluded_ids=excluded_ids) + res = self._get_sales_order(res) + return res diff --git a/addons/sale/static/src/js/account_reconciliation.js b/addons/sale/static/src/js/account_reconciliation.js new file mode 100644 index 00000000000..60d7d01459b --- /dev/null +++ b/addons/sale/static/src/js/account_reconciliation.js @@ -0,0 +1,68 @@ +odoo.define('sale.account_reconciliation', function (require) { +"use strict"; + +var ReconciliationRenderers = require('account.ReconciliationRenderer'); +var ReconciliationAction = require('account.ReconciliationClientAction'); +var LineRenderer = ReconciliationRenderers.LineRenderer; +var StatementAction = ReconciliationAction.StatementAction; +var core = require('web.core'); +var _t = core._t; + +LineRenderer.include({ + events: _.extend( + {}, + LineRenderer.prototype.events, + {'click .accounting_view caption .js_open_so': '_onOpenSaleOrder'} + ), + + _onOpenSaleOrder: function (event) { + event.preventDefault(); + this.trigger_up('open_sale_orders'); + }, +}); + +StatementAction.include({ + custom_events: _.extend( + {}, + StatementAction.prototype.custom_events, + {open_sale_orders: '_onOpenSaleOrders',} + ), + + _onOpenSaleOrders: function(event) { + var self = this; + var handle = event.target.handle; + var line = this.model.getLine(handle); + if (line.sale_order_ids && line.sale_order_ids.length > 1) { + // Open tree view + this.do_action({ + name: _t('Sale Orders'), + type: 'ir.actions.act_window', + res_model: 'sale.order', + views: [[false, 'list'], [false, 'form']], + view_type: "list", + view_mode: "list", + target: 'current', + domain: [['id', 'in', line.sale_order_ids]] + }, + { + on_reverse_breadcrumb: function() {self.trigger_up('reload');}, + }); + } + else if (line.sale_order_ids && line.sale_order_ids.length === 1) { + // Open form view + this.do_action({ + name: _t('Sale Order'), + type: 'ir.actions.act_window', + res_model: 'sale.order', + views: [[false, 'form']], + target: 'current', + res_id: line.sale_order_ids[0], + }, + { + on_reverse_breadcrumb: function() {self.trigger_up('reload');}, + }); + } + }, +}); + +}) \ No newline at end of file diff --git a/addons/sale/static/src/xml/account_reconciliation.xml b/addons/sale/static/src/xml/account_reconciliation.xml new file mode 100644 index 00000000000..14fe678cc4a --- /dev/null +++ b/addons/sale/static/src/xml/account_reconciliation.xml @@ -0,0 +1,15 @@ + + + + + + + +
+ There are uninvoiced sales orders matching the communication of the bank statement line +
+
+
+
+ +
\ No newline at end of file diff --git a/addons/sale/views/assets.xml b/addons/sale/views/assets.xml index 458b884f9d3..5ee94f6fdc2 100644 --- a/addons/sale/views/assets.xml +++ b/addons/sale/views/assets.xml @@ -13,6 +13,7 @@ +