[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
This commit is contained in:
Cedric Snauwaert
2018-12-06 14:01:38 +00:00
committed by qdp-odoo
parent ee2b9554ed
commit a6d250a053
9 changed files with 220 additions and 49 deletions
@@ -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);
}
},
@@ -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
@@ -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'));
});
$('<span class="line_info_button fa fa-info-circle"/>')
.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');},
});
},
/**
+3
View File
@@ -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
+1
View File
@@ -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
@@ -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
@@ -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');},
});
}
},
});
})
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="reconciliation.line" t-extend="reconciliation.line">
<t t-jquery="caption" t-operation="append">
<t t-if="state.sale_order_ids &amp;&amp; state.sale_order_ids.length > 0">
<div class="alert alert-info">
There are <strong><a href="#" class="js_open_so">uninvoiced sales orders</a></strong> matching the communication of the bank statement line
</div>
</t>
</t>
</t>
</templates>
+1
View File
@@ -13,6 +13,7 @@
<script type="text/javascript" src="/sale/static/src/js/product_configurator_controller.js"></script>
<script type="text/javascript" src="/sale/static/src/js/product_configurator_view.js"></script>
<script type="text/javascript" src="/sale/static/src/js/product_configurator_modal.js"></script>
<script type="text/javascript" src="/sale/static/src/js/account_reconciliation.js"></script>
</xpath>
</template>
<template id="assets_frontend_inherit_sale" inherit_id="web.assets_frontend" name="Sale frontend assets">