summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-10-16 17:53:26 +0200
committerChristophe Simonis <chs@odoo.com>2018-10-16 17:53:26 +0200
commit41bf8ce6d900cd247c4adf33cc75e3d018c89ffb (patch)
treea248bfe02d5b1e1495ad274bbc3c2b33f87d0b82 /addons
parenta9f11fb4772f2862ca5806baee38cf6a043b5143 (diff)
parentb29b545fe8464610ce04ac8be11e0356962d10d9 (diff)
[MERGE] forward port branch 11.0 up to b29b545fe84
Diffstat (limited to 'addons')
-rw-r--r--addons/account/models/account_move.py2
-rw-r--r--addons/account/views/account_view.xml5
-rw-r--r--addons/board/static/src/js/board_view.js2
-rw-r--r--addons/board/static/tests/dashboard_tests.js45
-rw-r--r--addons/mrp/models/mrp_workorder.py4
-rw-r--r--addons/point_of_sale/static/src/js/screens.js8
-rw-r--r--addons/product_margin/models/product_product.py2
-rw-r--r--addons/purchase/models/purchase.py2
-rw-r--r--addons/purchase_requisition/models/purchase_requisition.py4
-rw-r--r--addons/sale/static/src/js/sale.js4
-rw-r--r--addons/sale_stock/models/sale_order.py2
-rw-r--r--addons/sale_timesheet/views/hr_timesheet_templates.xml2
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js38
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js4
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js64
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_renderer.js2
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_record.js6
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js31
-rw-r--r--addons/web/static/tests/helpers/mock_server.js4
-rw-r--r--addons/web/static/tests/helpers/test_utils.js6
-rw-r--r--addons/web/static/tests/views/kanban_model_tests.js7
-rw-r--r--addons/web/static/tests/views/kanban_tests.js77
-rw-r--r--addons/web/static/tests/views/list_tests.js24
-rw-r--r--addons/website_sale/controllers/main.py17
-rw-r--r--addons/website_sale_delivery/controllers/main.py11
-rw-r--r--addons/website_sale_delivery/static/src/js/website_sale_delivery.js13
26 files changed, 318 insertions, 68 deletions
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index a45c670150c..e0015b63ad1 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -947,7 +947,7 @@ class AccountMoveLine(models.Model):
'credit': tax_vals['amount'] < 0 and -tax_vals['amount'] or 0.0,
'analytic_account_id': vals.get('analytic_account_id') if tax.analytic else False,
}
- bank = self.env["account.bank.statement"].browse(vals.get('statement_id'))
+ bank = self.env["account.bank.statement.line"].browse(vals.get('statement_line_id')).statement_id
if bank.currency_id != bank.company_id.currency_id:
ctx = {}
if 'date' in vals:
diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml
index c0aa8478ca1..1261dec7fce 100644
--- a/addons/account/views/account_view.xml
+++ b/addons/account/views/account_view.xml
@@ -412,8 +412,9 @@
</div>
<group>
<group>
- <field name="bank_acc_number"/>
- <field name="bank_id"/>
+ <field name="bank_account_id"/>
+ <field name="bank_acc_number" attrs="{'readonly': [('bank_account_id', '!=', False)]}"/>
+ <field name="bank_id" attrs="{'readonly': [('bank_account_id', '!=', False)]}"/>
</group>
<group>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
diff --git a/addons/board/static/src/js/board_view.js b/addons/board/static/src/js/board_view.js
index a29d22cde6a..60abe892045 100644
--- a/addons/board/static/src/js/board_view.js
+++ b/addons/board/static/src/js/board_view.js
@@ -247,6 +247,8 @@ var BoardRenderer = FormRenderer.extend({
// the action does not exist anymore
return $.when();
}
+ // tz and lang are saved in the custom view
+ // override the language to take the current one
var rawContext = new Context(params.context, action.context, {lang: session.user_context.lang});
var context = pyUtils.eval('context', rawContext);
var domain = params.domain || pyUtils.eval('domain', action.domain || '[]', action.context);
diff --git a/addons/board/static/tests/dashboard_tests.js b/addons/board/static/tests/dashboard_tests.js
index 2293e1c87e6..384f3fd699e 100644
--- a/addons/board/static/tests/dashboard_tests.js
+++ b/addons/board/static/tests/dashboard_tests.js
@@ -783,4 +783,49 @@ QUnit.test("Views should be loaded in the user's language", function (assert) {
form.destroy();
});
+QUnit.test("Views should be loaded in the user's language", function (assert) {
+ assert.expect(2);
+
+ var form = createView({
+ View: FormView,
+ model: 'board',
+ data: this.data,
+ session: {user_context: {lang: 'fr_FR'}},
+ arch: '<form string="My Dashboard">' +
+ '<board style="2-1">' +
+ '<column>' +
+ '<action context="{\'lang\': \'en_US\'}" view_mode="list" string="ABC" name="51" domain="[]"></action>' +
+ '</column>' +
+ '</board>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (route === "/web/dataset/search_read") {
+ assert.deepEqual(args.context, {lang: 'fr_FR'},
+ 'The data should be loaded with the correct context');
+ }
+
+ if (route === '/web/action/load') {
+ return $.when({
+ res_model: 'partner',
+ views: [[4, 'list']],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ 'partner,4,list':
+ '<list string="Partner"><field name="foo"/></list>',
+ },
+
+ interceptsPropagate: {
+ load_views: function (ev) {
+ assert.deepEqual(ev.data.context.eval(), {lang: 'fr_FR'},
+ 'The views should be loaded with the correct context');
+ }
+ },
+ });
+
+ form.destroy();
+});
+
});
diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py
index ce3d87688ee..6935bae7136 100644
--- a/addons/mrp/models/mrp_workorder.py
+++ b/addons/mrp/models/mrp_workorder.py
@@ -323,8 +323,10 @@ class MrpWorkorder(models.Model):
if self.product_id.tracking != 'none':
qty_to_add = float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding)
move._generate_consumed_move_line(qty_to_add, self.final_lot_id)
- else:
+ elif len(move._get_move_lines()) < 2:
move.quantity_done += float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding)
+ else:
+ move._set_quantity_done(move.quantity_done + float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding))
# Transfer quantities from temporary to final move lots or make them final
for move_line in self.active_move_line_ids:
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index f0623933788..75030e95acc 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -34,6 +34,7 @@ var core = require('web.core');
var rpc = require('web.rpc');
var utils = require('web.utils');
var field_utils = require('web.field_utils');
+var BarcodeEvents = require('barcodes.BarcodeEvents').BarcodeEvents;
var QWeb = core.qweb;
var _t = core._t;
@@ -1678,6 +1679,13 @@ var PaymentScreenWidget = ScreenWidget.extend({
// also called explicitly to handle some keydown events that
// do not generate keypress events.
this.keyboard_handler = function(event){
+ // On mobile Chrome BarcodeEvents relies on an invisible
+ // input being filled by a barcode device. Let events go
+ // through when this input is focused.
+ if (BarcodeEvents.$barcodeInput && BarcodeEvents.$barcodeInput.is(":focus")) {
+ return;
+ }
+
var key = '';
if (event.type === "keypress") {
diff --git a/addons/product_margin/models/product_product.py b/addons/product_margin/models/product_product.py
index 46adfc5118b..9fc94ea6310 100644
--- a/addons/product_margin/models/product_product.py
+++ b/addons/product_margin/models/product_product.py
@@ -107,7 +107,7 @@ class ProductProduct(models.Model):
select
sum(l.price_unit * l.quantity)/nullif(sum(l.quantity),0) as avg_unit_price,
sum(l.quantity) as num_qty,
- sum(l.quantity * (l.price_subtotal/(nullif(l.quantity,0)))) as total,
+ sum(l.quantity * (l.price_subtotal_signed/(nullif(l.quantity,0)))) as total,
sum(l.quantity * pt.list_price) as sale_expected
from account_invoice_line l
left join account_invoice i on (l.invoice_id = i.id)
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index 7984cd13c8f..d6916d05af4 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -603,7 +603,7 @@ class PurchaseOrderLine(models.Model):
taxes = line.product_id.supplier_taxes_id.filtered(lambda r: not line.company_id or r.company_id == line.company_id)
line.taxes_id = fpos.map_tax(taxes, line.product_id, line.order_id.partner_id) if fpos else taxes
- @api.depends('invoice_lines.invoice_id.state', 'invoice_lines.quantity')
+ @api.depends('invoice_lines.invoice_id.state', 'invoice_lines.quantity', 'invoice_lines.uom_id')
def _compute_qty_invoiced(self):
for line in self:
qty = 0.0
diff --git a/addons/purchase_requisition/models/purchase_requisition.py b/addons/purchase_requisition/models/purchase_requisition.py
index 3adfb19c426..c8862d15c4a 100644
--- a/addons/purchase_requisition/models/purchase_requisition.py
+++ b/addons/purchase_requisition/models/purchase_requisition.py
@@ -44,8 +44,8 @@ class PurchaseRequisition(models.Model):
def _get_picking_in(self):
pick_in = self.env.ref('stock.picking_type_in')
- if not pick_in:
- company = self.env['res.company']._company_default_get('purchase.requisition')
+ company = self.env['res.company']._company_default_get('purchase.requisition')
+ if not pick_in or pick_in.sudo().warehouse_id.company_id.id != company.id:
pick_in = self.env['stock.picking.type'].search(
[('warehouse_id.company_id', '=', company.id), ('code', '=', 'incoming')],
limit=1,
diff --git a/addons/sale/static/src/js/sale.js b/addons/sale/static/src/js/sale.js
index 289d26ecae3..1121a38501a 100644
--- a/addons/sale/static/src/js/sale.js
+++ b/addons/sale/static/src/js/sale.js
@@ -21,8 +21,8 @@ KanbanRecord.include({
ev.preventDefault();
this.$target_input = $('<input>');
- this.$('.o_kanban_primary_bottom').html(this.$target_input);
- this.$('.o_kanban_primary_bottom').prepend(_t("Set an invoicing target: "));
+ this.$('.o_kanban_primary_bottom:last').html(this.$target_input);
+ this.$('.o_kanban_primary_bottom:last').prepend(_t("Set an invoicing target: "));
this.$target_input.focus();
var self = this;
diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py
index 860ab963ed3..87134fdd990 100644
--- a/addons/sale_stock/models/sale_order.py
+++ b/addons/sale_stock/models/sale_order.py
@@ -164,7 +164,7 @@ class SaleOrderLine(models.Model):
qty = 0.0
for move in line.move_ids.filtered(lambda r: r.state == 'done' and not r.scrapped):
if move.location_dest_id.usage == "customer":
- if not move.origin_returned_move_id:
+ if not move.origin_returned_move_id or (move.origin_returned_move_id and move.to_refund):
qty += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom)
elif move.location_dest_id.usage != "customer" and move.to_refund:
qty -= move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom)
diff --git a/addons/sale_timesheet/views/hr_timesheet_templates.xml b/addons/sale_timesheet/views/hr_timesheet_templates.xml
index 7465721b730..a4176436998 100644
--- a/addons/sale_timesheet/views/hr_timesheet_templates.xml
+++ b/addons/sale_timesheet/views/hr_timesheet_templates.xml
@@ -155,7 +155,7 @@
<td>
<div t-if="repartition_employee_max" class="progress" t-att-style="'width: ' + str(repartition_employee[employee_id]['total'] / repartition_employee_max * 100) +'%'">
- <t t-set="total" t-value="repartition_employee[employee_id]['total']" />
+ <t t-set="total" t-value="repartition_employee[employee_id]['total'] or 1.0" />
<t t-set="billable_fixed" t-value="repartition_employee[employee_id]['billable_fixed']" />
<t t-set="billable_time" t-value="repartition_employee[employee_id]['billable_time']" />
<t t-set="non_billable" t-value="repartition_employee[employee_id]['non_billable']" />
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 55532ec6d16..c078069b074 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -948,6 +948,9 @@ var HandleWidget = AbstractField.extend({
var FieldEmail = InputField.extend({
className: 'o_field_email',
+ events: _.extend({}, InputField.prototype.events, {
+ 'click': '_onClick',
+ }),
prefix: 'mailto',
supportedFieldTypes: ['char'],
@@ -988,7 +991,21 @@ var FieldEmail = InputField.extend({
this.$el.text(this.value)
.addClass('o_form_uri o_text_overflow')
.attr('href', this.prefix + ':' + this.value);
- }
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Prevent the URL click from opening the record (when used on a list).
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClick: function (ev) {
+ ev.stopPropagation();
+ },
});
var FieldPhone = FieldEmail.extend({
@@ -1015,6 +1032,9 @@ var FieldPhone = FieldEmail.extend({
var UrlWidget = InputField.extend({
className: 'o_field_url',
+ events: _.extend({}, InputField.prototype.events, {
+ 'click': '_onClick',
+ }),
supportedFieldTypes: ['char'],
/**
@@ -1056,7 +1076,21 @@ var UrlWidget = InputField.extend({
.addClass('o_form_uri o_text_overflow')
.attr('target', '_blank')
.attr('href', this.value);
- }
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Prevent the URL click from opening the record (when used on a list).
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClick: function (ev) {
+ ev.stopPropagation();
+ },
});
var AbstractFieldBinary = AbstractField.extend({
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index faa6e82c3da..2bb98932c68 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -2306,10 +2306,12 @@ var FieldRadio = FieldSelection.extend({
* a FieldMany2one for its value.
* Its intern representation is similar to the many2one (a datapoint with a
* `name_get` as data).
+ * Note that there is some logic to support char field because of one use in our
+ * codebase, but this use should be removed along with this note.
*/
var FieldReference = FieldMany2One.extend({
specialData: "_fetchSpecialReference",
- supportedFieldTypes: ['char', 'reference'],
+ supportedFieldTypes: ['reference'],
template: 'FieldReference',
events: _.extend({}, FieldMany2One.prototype.events, {
'change select': '_onSelectionChange',
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index fe428af5e5e..593969654fb 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -904,27 +904,51 @@ var BasicModel = AbstractModel.extend({
route: '/web/dataset/resequence',
params: params,
})
- .then(function () {
- var offset = options.offset ? options.offset : 0;
- var old_data = data.data.slice();
- data.data = _.sortBy(data.data, function (d) {
- if (_.contains(resIDs, self.localData[d].res_id)) {
- return _.indexOf(resIDs, self.localData[d].res_id) + offset;
- } else {
- return _.indexOf(old_data, d);
- }
- });
- data.res_ids = [];
- _.each(data.data, function (d) {
- var dataPoint = self.localData[d];
- if (dataPoint.type === 'record') {
- data.res_ids.push(dataPoint.res_id);
- } else {
- data.res_ids = data.res_ids.concat(dataPoint.res_ids);
+ .then(function (wasResequenced) {
+ if (!wasResequenced) {
+ // the field on which the resequence was triggered does not
+ // exist, so no resequence happened server-side
+ return $.when();
+ }
+ var field = params.field ? params.field : 'sequence';
+
+ return self._rpc({
+ model: modelName,
+ method: 'read',
+ args: [resIDs, [field]],
+ }).then(function (records) {
+ if (data.data.length) {
+ var dataType = self.localData[data.data[0]].type;
+ if (dataType === 'record') {
+ _.each(data.data, function (dataPoint) {
+ var recordData = self.localData[dataPoint].data;
+ var inRecords = _.findWhere(records, {id: recordData.id});
+ if (inRecords) {
+ recordData[field] = inRecords[field];
+ }
+ });
+ data.data = _.sortBy(data.data, function (d) {
+ return self.localData[d].data[field];
+ });
+ }
+ if (dataType === 'list') {
+ data.data = _.sortBy(data.data, function (d) {
+ return _.indexOf(resIDs, self.localData[d].res_id)
+ });
+ }
}
- });
- self._updateParentResIDs(data);
- return parentID;
+ data.res_ids = [];
+ _.each(data.data, function (d) {
+ var dataPoint = self.localData[d];
+ if (dataPoint.type === 'record') {
+ data.res_ids.push(dataPoint.res_id);
+ } else {
+ data.res_ids = data.res_ids.concat(dataPoint.res_ids);
+ }
+ });
+ self._updateParentResIDs(data);
+ return parentID;
+ })
});
},
/**
diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js
index 89be23c849b..a92951dce9b 100644
--- a/addons/web/static/src/js/views/calendar/calendar_renderer.js
+++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js
@@ -77,7 +77,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, {
self.model.get(recordID),
{
mode: 'edit',
- can_create: false,
+ attrs: {can_create: false},
});
});
defs.push(def);
diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js
index 30bafaab117..654617c9847 100644
--- a/addons/web/static/src/js/views/kanban/kanban_record.js
+++ b/addons/web/static/src/js/views/kanban/kanban_record.js
@@ -450,10 +450,12 @@ var KanbanRecord = Widget.extend({
ischild = false;
}
var test_event = events && events.click && (events.click.length > 1 || events.click[0].namespace !== "tooltip");
+ var testLinkWithHref = elem.nodeName.toLowerCase() === 'a' && elem.href;
if (ischild) {
children.push(elem);
- if (test_event) {
- // do not trigger global click if one child has a click event registered
+ if (test_event || testLinkWithHref) {
+ // Do not trigger global click if one child has a click
+ // event registered (or it is a link with href)
trigger = false;
}
}
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index cebe9102551..270a0736845 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -9788,6 +9788,37 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('click on URL should not open the record', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].turtles = [1];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="turtles">' +
+ '<tree>' +
+ '<field name="display_name" widget="email"/>' +
+ '<field name="turtle_foo" widget="url"/>' +
+ '</tree>' +
+ '<form></form>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ form.$('.o_email_cell a').click();
+ assert.strictEqual($('.modal .o_form_view').length, 0,
+ 'click should not open the modal');
+
+ form.$('.o_url_cell a').click();
+ assert.strictEqual($('.modal .o_form_view').length, 0,
+ 'click should not open the modal');
+ form.destroy();
+ });
+
QUnit.module('FieldMany2Many');
QUnit.test('many2many kanban: edition', function (assert) {
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index 59147edb896..d21913e2c58 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -869,10 +869,14 @@ var MockServer = Class.extend({
var offset = args.offset ? Number(args.offset) : 0;
var field = args.field ? args.field : 'sequence';
var records = this.data[args.model].records;
+ if (!(field in this.data[args.model].fields)) {
+ return false;
+ }
for (var i in args.ids) {
var record = _.findWhere(records, {id: args.ids[i]});
record[field] = Number(i) + offset;
}
+ return true;
},
/**
* Simulate a 'search_count' operation
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index b013c05a71e..10fe2fd0c54 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -215,6 +215,12 @@ function createAsyncView(params) {
controlPanel.appendTo($web_client);
var $content = $('<div>').addClass('o_content').appendTo($web_client);
+ if (params.interceptsPropagate) {
+ _.each(params.interceptsPropagate, function (cb, name) {
+ intercept(widget, name, cb, true);
+ });
+ }
+
return view.getController(widget).then(function (view) {
// override the view's 'destroy' so that it calls 'destroy' on the widget
// instead, as the widget is the parent of the view and the mockServer.
diff --git a/addons/web/static/tests/views/kanban_model_tests.js b/addons/web/static/tests/views/kanban_model_tests.js
index 1a3391c720d..20eb2918d04 100644
--- a/addons/web/static/tests/views/kanban_model_tests.js
+++ b/addons/web/static/tests/views/kanban_model_tests.js
@@ -192,6 +192,8 @@ QUnit.module('Views', {
var done = assert.async();
assert.expect(8);
+ this.data.product.fields.sequence = {string: "Sequence", type: "integer"};
+ this.data.partner.fields.sequence = {string: "Sequence", type: "integer"};
this.data.partner.records.push({id: 3, foo: 'aaa', product_id: 37});
var nbReseq = 0;
@@ -204,7 +206,7 @@ QUnit.module('Views', {
if (nbReseq === 1) { // resequencing columns
assert.deepEqual(args.ids, [41, 37],
"ids should be correct");
- assert.strictEqual(args.model, 'product_id',
+ assert.strictEqual(args.model, 'product',
"model should be correct");
} else if (nbReseq === 2) { // resequencing records
assert.deepEqual(args.ids, [3, 1],
@@ -212,7 +214,6 @@ QUnit.module('Views', {
assert.strictEqual(args.model, 'partner',
"model should be correct");
}
- return $.when();
}
return this._super.apply(this, arguments);
},
@@ -229,7 +230,7 @@ QUnit.module('Views', {
"first group should be res_id 37");
// resequence columns
- return model.resequence('product_id', [41, 37], stateID);
+ return model.resequence('product', [41, 37], stateID);
})
.then(function (stateID) {
var state = model.get(stateID);
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js
index 101f484bdd1..200216d03e5 100644
--- a/addons/web/static/tests/views/kanban_tests.js
+++ b/addons/web/static/tests/views/kanban_tests.js
@@ -1288,6 +1288,72 @@ QUnit.module('Views', {
kanban.destroy();
});
+ QUnit.test('Do not open record when clicking on `a` with `href`', function (assert) {
+ assert.expect(5);
+
+ this.data.partner.records = [
+ { id: 1, foo: 'yop' },
+ ];
+
+ var kanban = createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: '<kanban class="o_kanban_test">' +
+ '<templates>' +
+ '<t t-name="kanban-box">' +
+ '<div class="oe_kanban_global_click">' +
+ '<field name="foo"/>' +
+ '<div>' +
+ '<a class="o_test_link" href="#">test link</a>' +
+ '</div>' +
+ '</div>' +
+ '</t>' +
+ '</templates>' +
+ '</kanban>',
+ intercepts: {
+ // when clicking on a record in kanban view,
+ // it switches to form view.
+ switch_view: function () {
+ throw new Error("should not switch view");
+ },
+ },
+ });
+
+ var $record = kanban.$('.o_kanban_record:not(.o_kanban_ghost)');
+ assert.strictEqual($record.length, 1,
+ "should display a kanban record");
+
+ var $testLink = $record.find('a');
+ assert.strictEqual($testLink.length, 1,
+ "should contain a link in the kanban record");
+ assert.ok(!!$testLink[0].href,
+ "link inside kanban record should have non-empty href");
+
+ // Mocked views prevent accessing a link with href. This is intented
+ // most of the time, but not in this test which specifically needs to
+ // let the browser access a link with href.
+ kanban.$el.off('click', 'a');
+ // Prevent the browser default behaviour when clicking on anything.
+ // This includes clicking on a `<a>` with `href`, so that it does not
+ // change the URL in the address bar.
+ // Note that we should not specify a click listener on 'a', otherwise
+ // it may influence the kanban record global click handler to not open
+ // the record.
+ $(document.body).on('click.o_test', function (ev) {
+ assert.notOk(ev.isDefaultPrevented(),
+ "should not prevented browser default behaviour beforehand");
+ assert.strictEqual(ev.target, $testLink[0],
+ "should have clicked on the test link in the kanban record");
+ ev.preventDefault();
+ });
+
+ $testLink.click();
+
+ $(document.body).off('click.o_test');
+ kanban.destroy();
+ });
+
QUnit.test('can drag and drop a record from one column to the next', function (assert) {
assert.expect(9);
@@ -1684,7 +1750,7 @@ QUnit.module('Views', {
});
QUnit.test('delete a column in grouped on m2o', function (assert) {
- assert.expect(29);
+ assert.expect(32);
testUtils.patch(KanbanRenderer, {
_renderGrouped: function () {
@@ -2646,6 +2712,7 @@ QUnit.module('Views', {
QUnit.test('resequence columns in grouped by m2o', function (assert) {
assert.expect(7);
+ this.data.product.fields.sequence = {string: "Sequence", type: "integer"};
var envIDs = [1, 3, 2, 4]; // the ids that should be in the environment during this test
var kanban = createView({
@@ -2659,12 +2726,6 @@ QUnit.module('Views', {
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
- mockRPC: function (route) {
- if (route === '/web/dataset/resequence') {
- return $.when();
- }
- return this._super.apply(this, arguments);
- },
intercepts: {
env_updated: function (event) {
assert.deepEqual(event.data.env.ids, envIDs,
@@ -2688,7 +2749,7 @@ QUnit.module('Views', {
kanban.update({}, {reload: false}); // re-render without reloading
assert.strictEqual(kanban.$('.o_kanban_group:first').data('id'), 5,
- "first column should be id 5 before resequencing");
+ "first column should be id 5 after resequencing");
kanban.destroy();
});
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 980b94e644c..3f557a45d47 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -3028,10 +3028,10 @@ QUnit.module('Views', {
foo: {
fields: {int_field: {string: "int_field", type: "integer", sortable: true}},
records: [
- {id: 1, int_field: 0},
- {id: 2, int_field: 1},
- {id: 3, int_field: 2},
- {id: 4, int_field: 3},
+ {id: 1, int_field: 11},
+ {id: 2, int_field: 12},
+ {id: 3, int_field: 13},
+ {id: 4, int_field: 14},
]
}
};
@@ -3050,14 +3050,15 @@ QUnit.module('Views', {
assert.deepEqual(args, {
model: "foo",
ids: [4, 3],
- offset: 2,
+ offset: 13,
field: "int_field",
});
}
if (moves === 1) {
assert.deepEqual(args, {
model: "foo",
- ids: [1, 4, 2, 3],
+ ids: [4, 2],
+ offset: 12,
field: "int_field",
});
}
@@ -3065,14 +3066,15 @@ QUnit.module('Views', {
assert.deepEqual(args, {
model: "foo",
ids: [2, 4],
- offset: 1,
+ offset: 12,
field: "int_field",
});
}
if (moves === 3) {
assert.deepEqual(args, {
model: "foo",
- ids: [1, 4, 2, 3],
+ ids: [4, 2],
+ offset: 12,
field: "int_field",
});
}
@@ -3139,7 +3141,6 @@ QUnit.module('Views', {
"should write the right field as sequence");
assert.deepEqual(args.ids, [4, 2, 3],
"should write the sequence in correct order");
- return $.when();
}
return this._super.apply(this, arguments);
},
@@ -3269,13 +3270,16 @@ QUnit.module('Views', {
'</tree>',
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
+ var _super = this._super.bind(this);
assert.strictEqual(args.offset, 1,
"should write the sequence starting from the lowest current one");
assert.strictEqual(args.field, 'int_field',
"should write the right field as sequence");
assert.deepEqual(args.ids, [4, 2, 3],
"should write the sequence in correct order");
- return $.when(def);
+ return $.when(def).then(function () {
+ return _super(route, args);
+ });
}
return this._super.apply(this, arguments);
},
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index 3d8d459857c..75a1fa9f6b8 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -1067,3 +1067,20 @@ class WebsiteSale(http.Controller):
states=[(st.id, st.name, st.code) for st in country.get_website_sale_states(mode=mode)],
phone_code=country.phone_code
)
+
+ @http.route(['/shop/update_carrier'], type='json', auth='public', methods=['POST'], website=True, csrf=False)
+ def update_eshop_carrier(self, **post):
+ results = {}
+ if hasattr(self, '_update_website_sale_delivery'):
+ results.update(self._update_website_sale_delivery(**post))
+
+ if hasattr(self, '_update_website_sale_coupon'):
+ results.update(self._update_website_sale_coupon(**post))
+
+ return results
+
+ def _format_amount(self, amount, currency):
+ fmt = "%.{0}f".format(currency.decimal_places)
+ lang = request.env['res.lang']._lang_get(request.env.context.get('lang') or 'en_US')
+ return lang.format(fmt, currency.round(amount), grouping=True, monetary=True)\
+ .replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}')
diff --git a/addons/website_sale_delivery/controllers/main.py b/addons/website_sale_delivery/controllers/main.py
index 52522fa8117..a7b7d41998b 100644
--- a/addons/website_sale_delivery/controllers/main.py
+++ b/addons/website_sale_delivery/controllers/main.py
@@ -55,8 +55,7 @@ class WebsiteSaleDelivery(WebsiteSale):
values['delivery_action_id'] = request.env.ref('delivery.action_delivery_carrier_form').id
return values
- @http.route(['/shop/update_carrier'], type='json', auth='public', methods=['POST'], website=True, csrf=False)
- def update_eshop_carrier(self, **post):
+ def _update_website_sale_delivery(self, **post):
order = request.website.sale_get_order()
carrier_id = int(post['carrier_id'])
currency = order.currency_id
@@ -70,10 +69,4 @@ class WebsiteSaleDelivery(WebsiteSale):
'new_amount_tax': self._format_amount(order.amount_tax, currency),
'new_amount_total': self._format_amount(order.amount_total, currency),
}
-
- def _format_amount(self, amount, currency):
- fmt = "%.{0}f".format(currency.decimal_places)
- lang = request.env['res.lang']._lang_get(request.env.context.get('lang') or 'en_US')
-
- return lang.format(fmt, currency.round(amount), grouping=True, monetary=True)\
- .replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}')
+ return {}
diff --git a/addons/website_sale_delivery/static/src/js/website_sale_delivery.js b/addons/website_sale_delivery/static/src/js/website_sale_delivery.js
index 971e604d1d1..79c70becf98 100644
--- a/addons/website_sale_delivery/static/src/js/website_sale_delivery.js
+++ b/addons/website_sale_delivery/static/src/js/website_sale_delivery.js
@@ -3,6 +3,8 @@ odoo.define('website_sale_delivery.checkout', function (require) {
require('web.dom_ready');
var ajax = require('web.ajax');
+ var core = require('web.core');
+ var _t = core._t;
/* Handle interactive carrier choice + cart update */
var $pay_button = $('#o_payment_form_pay');
@@ -14,6 +16,17 @@ odoo.define('website_sale_delivery.checkout', function (require) {
var $amount_total = $('#order_total span.oe_currency_value');
var $carrier_badge = $('#delivery_carrier input[name="delivery_type"][value=' + result.carrier_id + '] ~ .badge.hidden');
var $compute_badge = $('#delivery_carrier input[name="delivery_type"][value=' + result.carrier_id + '] ~ .o_delivery_compute');
+ var $discount = $('#order_discounted');
+
+ if ($discount && result.new_amount_order_discounted) {
+ // Cross module without bridge
+ // Update discount of the order
+ $discount.find('.oe_currency_value').text(result.new_amount_order_discounted);
+
+ // We are in freeshipping, so every carrier is Free
+ $('#delivery_carrier .badge').text(_t('Free'));
+ }
+
if (result.status === true) {
$amount_delivery.text(result.new_amount_delivery);
$amount_untaxed.text(result.new_amount_untaxed);