summaryrefslogtreecommitdiff
path: root/addons/account/static/src
diff options
context:
space:
mode:
authorLaurent Smet <las@openerp.com>2018-09-20 17:11:52 +0200
committerqdp-odoo <qdp@odoo.com>2018-09-20 17:11:52 +0200
commit7053ae782887c389ab8fcd8f4e832b46287e2efb (patch)
tree025c4ec98c6ef61625e87d024ce1c7541e0c63ed /addons/account/static/src
parente19ecefa8e78b9369fa8274c879e61799f743a06 (diff)
[IMP] account: management of reconciliation rules
Bank statement lines need to be matched with entries; We have to automate this operation as much as possible. At the same time, create an algorithm that fit everyone needs is quite impossible. For that reason we will let users choose the rules they want with the improvement of reconciliation models. Note that we did it only for bank statement reconliation, not for manual reconciliations. Was task: 1877375 Was PR #26669
Diffstat (limited to 'addons/account/static/src')
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_action.js14
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_model.js83
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_renderer.js97
-rw-r--r--addons/account/static/src/js/reconciliation/tour_reconciliation.js22
-rw-r--r--addons/account/static/src/xml/account_reconciliation.xml55
5 files changed, 147 insertions, 124 deletions
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_action.js b/addons/account/static/src/js/reconciliation/reconciliation_action.js
index 88668a2da97..bdca26fa15f 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_action.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_action.js
@@ -7,6 +7,7 @@ var ReconciliationRenderer = require('account.ReconciliationRenderer');
var ControlPanelMixin = require('web.ControlPanelMixin');
var Widget = require('web.Widget');
var core = require('web.core');
+var _t = core._t;
/**
@@ -109,7 +110,18 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
this.renderer.prependTo(self.$('.o_form_sheet'));
this._renderLines();
- this._openFirstLine();
+
+ // No more lines to reconcile, trigger the rainbowman.
+ var initialState = this.renderer._initialState;
+ if(initialState.valuenow === initialState.valuemax){
+ initialState.context = this.model.getContext();
+ this.renderer.showRainbowMan(initialState);
+ }else{
+ // Create a notification if some lines has been reconciled automatically.
+ if(initialState.valuenow > 0)
+ this.renderer._renderNotifications(this.model.statement.notifications);
+ this._openFirstLine();
+ }
},
/**
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index e8a6838a139..dcc93343189 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -358,13 +358,13 @@ var StatementModel = BasicModel.extend({
.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 = 0;
- self.valuemax = statement.st_lines_ids.length;
+ self.valuenow = statement.value_min;
+ self.valuemax = statement.value_max;
self.context.journal_id = statement.journal_id;
- _.each(statement.st_lines_ids, function (id) {
+ _.each(statement.lines, function (res) {
var handle = _.uniqueId('rline');
self.lines[handle] = {
- id: id,
+ id: res.st_line.id,
handle: handle,
reconciled: false,
mode: 'inactive',
@@ -412,7 +412,7 @@ var StatementModel = BasicModel.extend({
var ids = _.pluck(self.lines, 'id');
ids = ids.splice(0, self.defaultDisplayQty);
self.pagerIndex = ids.length;
- return self.loadData(ids, []);
+ return self._formatLine(self.statement.lines);
});
},
/**
@@ -445,7 +445,9 @@ var StatementModel = BasicModel.extend({
args: [ids, excluded_ids],
context: self.context,
})
- .then(self._formatLine.bind(self));
+ .then(function(res){
+ return self._formatLine(res['lines']);
+ })
},
/**
* Add lines into the propositions from the reconcile model
@@ -545,24 +547,14 @@ var StatementModel = BasicModel.extend({
var line = this.getLine(handle);
// Retrieve the toggle proposition
- var selected;
- var targetLineAmount = line.st_line.amount;
- line.reconciliation_proposition.every(function (prop) {
- if (!prop.invalid) {
- if (((line.balance.amount < 0 || !line.partial_reconcile) && prop.amount > 0 && targetLineAmount > 0 && targetLineAmount < prop.amount) ||
- ((line.balance.amount > 0 || !line.partial_reconcile) && prop.amount < 0 && targetLineAmount < 0 && targetLineAmount > prop.amount)) {
- selected = prop;
- return false;
- }
- targetLineAmount -= prop.amount;
- }
- return true;
- });
+ var selected = _.filter(line.reconciliation_proposition, function(prop){return prop.display_triangle});
// If no toggled proposition found, reject it
- if (selected == null)
+ if(selected.length != 1)
return $.Deferred().reject();
+ selected = selected[0];
+
// Inverse partial_reconcile value
selected.partial_reconcile = !selected.partial_reconcile;
if (!selected.partial_reconcile) {
@@ -985,41 +977,32 @@ var StatementModel = BasicModel.extend({
self._formatLineProposition(line, line.reconciliation_proposition);
if (!line.reconciliation_proposition.length) {
delete line.reconciliation_proposition;
- }else{
- // loop state propositions
- var debit_props = _.filter(line.reconciliation_proposition, function(p){
- return p.amount > 0 && line.st_line.amount;
- });
- var credit_props = _.filter(line.reconciliation_proposition, function(p){
- return p.amount < 0 && line.st_line.amount < 0;
- });
- var sum_debit_props = debit_props.length > 0 ? debit_props.map(function(p){return p.amount}).reduce(function(p1, p2){return p1 + p2}) : 0;
- var sum_credit_props = credit_props.length > 0 ? credit_props.map(function(p){return p.amount}).reduce(function(p1, p2){return p1 + p2}) : 0;
-
- // Want to display the triangle for partial reconciliation or not.
- if(line.st_line.amount_currency > 0 && line.st_line.amount_currency < sum_debit_props && debit_props.length == 1)
- debit_props[0].display_triangle = true;
- else if(line.st_line.amount_currency < 0 && line.st_line.amount_currency > sum_credit_props && credit_props.length == 1)
- credit_props[0].display_triangle = true;
}
// No partner set on st_line and all matching amls have the same one: set it on the st_line.
- if(!line.st_line.partner_id && line.reconciliation_proposition){
- var hasDifferentPartners = function(prop){
- return !prop.partner_id || prop.partner_id != line.reconciliation_proposition[0].partner_id;
- }
-
- if(!_.any(line.reconciliation_proposition, hasDifferentPartners)){
- defs.push(self.changePartner(line.handle, {
- 'id': line.reconciliation_proposition[0].partner_id,
- 'display_name': line.reconciliation_proposition[0].partner_name,
- }, true));
- }
- }
+ 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;
+ }
- defs.push(self._computeLine(line));
+ 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);
+ }
+ }
+ return true;
+ })
+ .then(function(){
+ return data.status === 'write_off'? self.quickCreateProposition(line.handle, data.model_id) : true;
+ })
+ );
});
- return $.when.apply($, defs);
+ return $.when(defs);
},
/**
* Format the server value then compute the line
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
index e7b9067ca3d..e59934610b6 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
@@ -84,6 +84,29 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, {
hideLoadMoreButton: function () {
this.$('.js_load_more').hide();
},
+ showRainbowMan(state){
+ 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('.button_close_statement').click(this._onCloseBankStatement.bind(this));
+ $done.find('.button_back_to_statement').click(this._onGoToBankStatement.bind(this));
+ this.$el.children().hide();
+ // 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
*
@@ -103,27 +126,7 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, {
.css('width', (state.valuenow/state.valuemax*100) + '%');
if (state.valuenow === state.valuemax && !this.$('.done_message').length) {
- 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('.button_close_statement').click(this._onCloseBankStatement.bind(this));
- $done.find('.button_back_to_statement').click(this._onGoToBankStatement.bind(this));
- this.$el.children().hide();
- // 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);
- }
+ this.showRainbowMan(state);
}
if (state.notifications) {
@@ -145,11 +148,7 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, {
_renderNotifications: function(notifications) {
this.$(".notification_area").empty();
for (var i=0; i<notifications.length; i++) {
- var $notification = $(qweb.render("reconciliation.notification", {
- type: notifications[i].type,
- message: notifications[i].message,
- details: notifications[i].details,
- })).hide();
+ var $notification = $(qweb.render("reconciliation.notification", notifications[i])).hide();
$notification.appendTo(this.$(".notification_area")).slideDown(300);
}
},
@@ -363,38 +362,48 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
// reconciliation_proposition
var $props = this.$('.accounting_view tbody').empty();
- // loop state propositions
+ // Search propositions that could be a partial credit/debit.
var props = [];
- var partialDebitProps = 0;
- var partialCreditProps = 0;
+ var partialDebitProp;
+ var partialCreditProp;
+ var balance = state.balance.amount_currency;
_.each(state.reconciliation_proposition, function (prop) {
if (prop.display) {
props.push(prop);
- if (prop.amount > 0 && prop.amount > state.st_line.amount) {
- partialDebitProps++;
- } else if (prop.amount < 0 && prop.amount < state.st_line.amount) {
- partialCreditProps++;
- }
+ /*
+ Examples:
+ statement line | 100 | |
+ move line 1 | | 200 | <- can be a partial of 100
+ balance: -100
+
+ statement line | 500 | |
+ move line 1 | | 300 | <- is not a eligible to be a partial due to the second line.
+ move line 2 | | 300 | <- can be a partial of 200
+ balance: -100
+
+ statement line | 500 | |
+ move line 1 | | 700 | <- must not be a partial (debit = 800 > 700 = credit).
+ move line 2 | 300 | |
+ balance: 100
+ */
+ if(!prop.display_new && balance < 0 && prop.amount > 0 && balance + prop.amount > 0)
+ partialDebitProp = prop;
+ else if(!prop.display_new && balance > 0 && prop.amount < 0 && balance + prop.amount < 0)
+ partialCreditProp = prop;
}
});
- var targetLineAmount = state.st_line.amount;
-
_.each(props, function (line) {
- var display_triangle = (line.already_paid === false &&
- ((state.balance.amount_currency < 0 || line.partial_reconcile)
- && line.amount > 0 && state.st_line.amount > 0 && targetLineAmount < line.amount && partialDebitProps <= 1) ||
- ((state.balance.amount_currency > 0 || line.partial_reconcile)
- && line.amount < 0 && state.st_line.amount < 0 && targetLineAmount > line.amount && partialCreditProps <= 1));
- var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state, 'display_triangle': display_triangle}));
+ line.display_triangle = (line.already_paid === false &&
+ ((state.balance.amount_currency < 0 || line.partial_reconcile) && partialDebitProp && partialDebitProp === line) ||
+ ((state.balance.amount_currency > 0 || line.partial_reconcile) && partialCreditProp && partialCreditProp === 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}));
}
- targetLineAmount -= line.amount;
-
$props.append($line);
});
diff --git a/addons/account/static/src/js/reconciliation/tour_reconciliation.js b/addons/account/static/src/js/reconciliation/tour_reconciliation.js
index ab6efaef113..533b68274ba 100644
--- a/addons/account/static/src/js/reconciliation/tour_reconciliation.js
+++ b/addons/account/static/src/js/reconciliation/tour_reconciliation.js
@@ -9,24 +9,15 @@ Tour.register('bank_statement_reconciliation', {
test: true,
// Go to the reconciliation page of the statement: "BNK/2014/001"
}, [
- // Reconciliation of 'SAJ/2018/002'
- // Select the 'INV/2018/0002' line and click on reconcile.
+ // Reconciliation of 'INV/2018/0002'
+ // Click on reconcile (matching done automatically by the reconciliation rule).
{
- content: "open the last line in match mode to test the reconcile button",
- trigger: '.toggle_match:last',
- extra_trigger: '.o_reconciliation',
- },
- {
- content: "select the 'INV/2018/0002' line",
- trigger: '.o_reconciliation_line:last .match .cell_label:contains("INV/2018/0002")'
- },
- {
content: "reconcile the line",
- trigger: '.o_reconciliation_line:last .o_reconcile:visible',
+ trigger: '.o_reconciliation_line:nth-child(1) .o_reconcile:visible',
},
- // Reconciliation of 'First 2000 € of SAJ/2014/0001'
+ // Reconciliation of 'First 2000 $ of INV/2018/0001'
// Make a partial reconciliation
{
@@ -35,10 +26,6 @@ Tour.register('bank_statement_reconciliation', {
trigger: '.o_reconciliation_line:last .cell_label:contains("First")'
},
{
- content: "select a line with with a higher amount",
- trigger: '.o_reconciliation_line:last .match .cell_label:contains("INV/2018/0001")'
- },
- {
content: "click on partial reconcile",
trigger: '.o_reconciliation_line:last .accounting_view .do_partial_reconcile_true'
},
@@ -67,7 +54,6 @@ Tour.register('bank_statement_reconciliation', {
},
{
content: "select a line linked to Deco Addict ",
- extra_trigger: '.o_reconciliation_line:nth-child(2) .match:not(:has(tr:eq(1)))',
trigger: ".o_reconciliation_line:nth-child(2) .match .line_info_button[data-content*='Deco Addict']"
},
{
diff --git a/addons/account/static/src/xml/account_reconciliation.xml b/addons/account/static/src/xml/account_reconciliation.xml
index 3d5cc28ec3d..39cff76a6a6 100644
--- a/addons/account/static/src/xml/account_reconciliation.xml
+++ b/addons/account/static/src/xml/account_reconciliation.xml
@@ -32,7 +32,6 @@
<div t-if="widget._initialState.valuemax &gt; widget._initialState.defaultDisplayQty">
<button class="btn btn-secondary js_load_more">Load more</button>
</div>
- <div class="float-right text-muted">Tip: Hit CTRL-Enter to reconcile all the balanced items in the sheet.</div>
</div>
<div t-else="" class="o_view_noreconciliation">
<p>Nothing to do!</p>
@@ -157,7 +156,11 @@
<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-att-data-reconcile-model-id="reconcileModel.id"><t t-esc="reconcileModel.name"/></button>
+ <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))"
+ 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>
@@ -214,7 +217,7 @@
<t t-name="reconciliation.line.mv_line.triangle">
- <t t-if="display_triangle || line.display_triangle">
+ <t t-if="line.display_triangle">
<t t-if="line.partial_reconcile">
<span t-att-class="'do_partial_reconcile_'+(!line.partial_reconcile)+' line_info_button fa fa-exclamation-triangle'"
data-content="Undo the partial reconciliation."/>
@@ -231,7 +234,12 @@
<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_action"><span class="fa fa-add-remove" role="img" aria-label="Set" title="Set"/></td>
<td class="cell_account_code"><t t-esc="line.account_code"/></td>
- <td class="cell_due_date"><t t-esc="line.date_maturity === false ? line.date : line.date_maturity"/></td>
+ <td class="cell_due_date">
+ <t t-if="typeof(line.id) != 'number'">
+ <span class="badge badge-secondary">New</span>
+ </t>
+ <t t-else="" t-esc="line.date_maturity === false ? line.date : line.date_maturity"/>
+ </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">
@@ -297,16 +305,41 @@
</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-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-if="template">
+ <t t-call="{{template}}"/>
+ </t>
+ <t t-else="">
+ <t t-call="reconciliation.notification.default"/>
</t>
</div>
</t>