[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:
committed by
qdp-odoo
parent
ee2b9554ed
commit
a6d250a053
@@ -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');},
|
||||
});
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 && 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>
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user