summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-07-18 16:03:56 +0200
committerChristophe Simonis <chs@odoo.com>2018-07-18 16:03:56 +0200
commit8d1475ff573d833470c1503dfc66f4e3b9ecd9c4 (patch)
tree5931c7a2d6cb56c4a12ebd5345c4ea90fec04ac4 /addons
parent2eb8d06149b555208b29f46743fc5eea3c3e84d2 (diff)
parent27c017ad98b4c9ac5fdcd28c9fd84d3690993118 (diff)
[MERGE] forward port branch 11.0 up to 27c017ad98b
Diffstat (limited to 'addons')
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_model.js10
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_renderer.js25
-rw-r--r--addons/account/static/tests/reconciliation_tests.js197
-rw-r--r--addons/portal/views/portal_templates.xml6
-rw-r--r--addons/website/views/website_templates.xml12
5 files changed, 226 insertions, 24 deletions
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index a61c5e2908f..c8c97cea575 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -519,7 +519,6 @@ var StatementModel = BasicModel.extend({
},
/**
* Force the partial reconciliation to display the reconciliate button.
- * This method should only be called when there is onely one proposition.
*
* @param {string} handle
* @returns {Deferred}
@@ -529,14 +528,17 @@ var StatementModel = BasicModel.extend({
// Retrieve the toggle proposition
var selected;
- _.each(line.reconciliation_proposition, function (prop) {
+ 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 && line.st_line.amount > 0 && line.st_line.amount < prop.amount) ||
- ((line.balance.amount > 0 || !line.partial_reconcile) && prop.amount < 0 && line.st_line.amount < 0 && line.st_line.amount > prop.amount)) {
+ 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;
});
// If no toggled proposition found, reject it
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
index 3f32c8f0feb..51fac21f127 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
@@ -358,18 +358,21 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
// loop state propositions
var props = [];
- var nb_debit_props = 0;
- var nb_credit_props = 0;
+ var partialDebitProps = 0;
+ var partialCreditProps = 0;
_.each(state.reconciliation_proposition, function (prop) {
if (prop.display) {
props.push(prop);
- if (prop.amount < 0)
- nb_debit_props += 1;
- else if (prop.amount > 0)
- nb_credit_props += 1;
+ if (prop.amount > 0 && prop.amount > state.st_line.amount) {
+ partialDebitProps++;
+ } else if (prop.amount < 0 && prop.amount < state.st_line.amount) {
+ partialCreditProps++;
+ }
+
}
});
+ var targetLineAmount = state.st_line.amount;
_.each(props, function (line) {
var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state}));
if (!isNaN(line.id)) {
@@ -378,10 +381,10 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
.attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line}));
}
if (line.already_paid === false &&
- ((state.balance.amount_currency < 0 || line.partial_reconcile) && nb_credit_props == 1
- && line.amount > 0 && state.st_line.amount > 0 && state.st_line.amount < line.amount) ||
- ((state.balance.amount_currency > 0 || line.partial_reconcile) && nb_debit_props == 1
- && line.amount < 0 && state.st_line.amount < 0 && state.st_line.amount > line.amount)) {
+ ((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 $cell = $line.find(line.amount > 0 ? '.cell_right' : '.cell_left');
var text;
if (line.partial_reconcile) {
@@ -395,6 +398,8 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
.prependTo($cell)
.attr("data-content", text);
}
+ targetLineAmount -= line.amount;
+
$props.append($line);
});
diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js
index 991ebac12e6..ddada3b152b 100644
--- a/addons/account/static/tests/reconciliation_tests.js
+++ b/addons/account/static/tests/reconciliation_tests.js
@@ -1,6 +1,14 @@
odoo.define('account.reconciliation_tests.data', function () {
"use strict";
+/*
+ * Debug tip:
+ * To be able to "see" the test in the browser:
+ * var $body = $('body');
+ * $body.addClass('debug');
+ * clientAction.appendTo($body);
+ */
+
var Datas = {};
var db = {
@@ -1499,5 +1507,194 @@ QUnit.module('account', {
clientAction.destroy();
});
+
+ QUnit.test('Reconciliation: Payment < inv1 + inv2(partial)', function (assert) {
+ assert.expect(4);
+
+ /*
+ * One payment: $1175
+ * Two Invoices
+ * The first invoice will be fully reconciled $650
+ * The second invoice will be partially paid with the rest of the payment $999
+ */
+
+ // modify the second line that is already in db to put it at $999
+ var indexModif = _.findIndex(this.params.mv_lines['[5,"",0,6]'], function (line) {return line.id === 112});
+ this.params.mv_lines['[5,"",0,6]'][indexModif] =
+ {account_type: "receivable", amount_currency_str: "", currency_id: false, date_maturity: "2017-02-07", date: "2017-01-08",
+ total_amount_str: "$ 999.00", partner_id: 8, account_name: "101200 Account Receivable", name: "INV/2017/0003",
+ partner_name: "Agrolait", total_amount_currency_str: "", id: 112, credit: 0.0, journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 999.00", debit: 999.0, account_code: "101200", ref: "", already_paid: false};
+
+ var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
+ testUtils.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ mockRPC: function (route, args) {
+ if (args.method === 'process_reconciliations') {
+ assert.deepEqual(args.args,
+ [
+ [5], // Id of the bank statement line
+
+ [{counterpart_aml_dicts:
+ [{name:"INV/2017/0002",
+ debit: 0,
+ credit: 650,
+ counterpart_aml_id: 109},
+
+ {name: "INV/2017/0003",
+ debit: 0,
+ credit: 525,
+ counterpart_aml_id: 112}],
+
+ payment_aml_ids: [],
+ partner_id: 8,
+ new_aml_dicts: []}]
+ ], "should call process_reconciliations with partial reconcile values");
+ }
+ return this._super(route, args);
+ },
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$"
+ }
+ }
+ },
+ });
+ clientAction.appendTo($('#qunit-fixture'));
+
+ // The first reconciliation "line" is where it happens
+ var widget = clientAction.widgets[0];
+
+ // Add first invoice to reconcile fully
+ widget.$('.match .cell_account_code:first').trigger('click');
+ assert.notOk( widget.$('.cell_right .line_info_button').length,
+ "should not display the partial reconciliation alert");
+
+ // Add second invoice to reconcile partially
+ widget.$('.match .cell_account_code:first').trigger('click');
+ var $reconciliationAlert = widget.$('.cell_right .line_info_button');
+
+ assert.ok($reconciliationAlert.length,
+ "should display the partial reconciliation alert");
+
+ $reconciliationAlert.click();
+
+ var $buttonReconcile = widget.$('button.o_reconcile:not(hidden)');
+
+ assert.equal($buttonReconcile.length, 1,
+ 'The reconcile button must be visible');
+
+ $buttonReconcile.click();
+
+ clientAction.destroy();
+ });
+
+ QUnit.test('Reconciliation: payment and 2 partials', function (assert) {
+ assert.expect(6);
+
+ /*
+ * One payment: $1175
+ * Two Invoices as Inv1 = 1200; Inv2 = 1200:
+ * Payment < Inv1 AND Payment < Inv2
+ * No partial reconcile is possible, as a write-off of 1225 is necessary
+ */
+
+ // modify the invoice line to have their amount > payment
+ var indexInv1 = _.findIndex(this.params.mv_lines['[5,"",0,6]'], function (line) {return line.id === 109});
+ this.params.mv_lines['[5,"",0,6]'][indexInv1] =
+ {account_type: "receivable", amount_currency_str: "", currency_id: false, date_maturity: "2017-02-07", date: "2017-01-08",
+ total_amount_str: "$ 1200.00", partner_id: 8, account_name: "101200 Account Receivable", name: "INV/2017/0002", partner_name: "Agrolait",
+ total_amount_currency_str: "", id: 109, credit: 0.0, journal_id: [1, "Customer Invoices"], amount_str: "$ 1200.00", debit: 1200.0,
+ account_code: "101200", ref: "", already_paid: false};
+
+ var indexInv2 = _.findIndex(this.params.mv_lines['[5,"",0,6]'], function (line) {return line.id === 112});
+ this.params.mv_lines['[5,"",0,6]'][indexInv2] =
+ {account_type: "receivable", amount_currency_str: "", currency_id: false, date_maturity: "2017-02-07", date: "2017-01-08",
+ total_amount_str: "$ 1200.00", partner_id: 8, account_name: "101200 Account Receivable", name: "INV/2017/0003",
+ partner_name: "Agrolait", total_amount_currency_str: "", id: 112, credit: 0.0, journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1200.00", debit: 1200.0, account_code: "101200", ref: "", already_paid: false};
+
+ var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
+ testUtils.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ mockRPC: function (route, args) {
+ if (args.method === 'process_reconciliations') {
+ assert.deepEqual(args.args,
+ [
+ [5], // Id of the bank statement line
+
+ [{counterpart_aml_dicts:
+ [{name:"INV/2017/0002",
+ debit: 0,
+ credit: 1200,
+ counterpart_aml_id: 109},
+
+ {name: "INV/2017/0003",
+ debit: 0,
+ credit: 1200,
+ counterpart_aml_id: 112}],
+
+ payment_aml_ids: [],
+ partner_id: 8,
+ new_aml_dicts: [
+ {account_id: 282,
+ credit: 0,
+ debit: 1225,
+ name: 'SAJ/2014/002 and SAJ/2014/003',
+ }
+ ]}]
+ ], "should call process_reconciliations with new aml dict reconcile values");
+ }
+ return this._super(route, args);
+ },
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$"
+ }
+ }
+ },
+ });
+ clientAction.appendTo($('#qunit-fixture'));
+
+ // The first reconciliation "line" is where it happens
+ var widget = clientAction.widgets[0];
+
+ // Add first invoice
+ // There should be the opportunity to reconcile partially
+ widget.$('.match .cell_account_code:first').trigger('click');
+ assert.ok(widget.$('.cell_right .line_info_button').length,
+ "should display the partial reconciliation alert");
+
+ // Add second invoice
+ widget.$('.match .cell_account_code:first').trigger('click');
+ assert.notOk(widget.$('.cell_right .line_info_button').length,
+ "should not display the partial reconciliation alert");
+
+ var writeOffCreate = widget.$('div.create');
+
+ assert.equal(writeOffCreate.length, 1,
+ 'A write-off creation should be present');
+
+ assert.equal(writeOffCreate.find('input[name=amount]').val(), -1225,
+ 'The right amount should be proposed for the write-off');
+
+ writeOffCreate.find('.create_account_id input.ui-autocomplete-input').click();
+ $('ul.ui-autocomplete li a:first').click(); // select first account to do the write off in
+
+ var $buttonReconcile = widget.$('button.o_reconcile:not(hidden)');
+
+ assert.equal($buttonReconcile.length, 1,
+ 'The reconcile button must be visible');
+
+ $buttonReconcile.click();
+
+ clientAction.destroy();
+ });
});
});
diff --git a/addons/portal/views/portal_templates.xml b/addons/portal/views/portal_templates.xml
index cf22a0a1747..1e178f7d856 100644
--- a/addons/portal/views/portal_templates.xml
+++ b/addons/portal/views/portal_templates.xml
@@ -27,6 +27,12 @@
<div class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-top-collapse">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
<a href="/" class="navbar-brand logo">
<span t-field="res_company.logo" t-options="{'widget': 'image'}" t-att-alt="'Logo of %s' % res_company.name" t-att-title="res_company.name"/>
</a>
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index 2fcfd335f9b..1a36cebf80f 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -173,16 +173,8 @@
<t t-call-assets="website.assets_editor" t-css="false" groups="website.group_website_publisher"/>
</t>
</xpath>
- <xpath expr="//header//div[hasclass('navbar-header')]" position="replace">
- <div class="navbar-header">
- <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-top-collapse">
- <span class="sr-only">Toggle navigation</span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- </button>
- <a class="navbar-brand" href="/" t-if="website" t-field="website.name">My Website</a>
- </div>
+ <xpath expr="//header//a[hasclass('navbar-brand')]" position="replace">
+ <a class="navbar-brand" href="/" t-if="website" t-field="website.name">My Website</a>
</xpath>
<xpath expr="//header//ul[@id='top_menu']/li[hasclass('divider')]" position="attributes">
<attribute name="t-if">website.user_id != user_id</attribute>