diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index a45c670150c73..e0015b63ad172 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -947,7 +947,7 @@ def create(self, vals): '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 c0aa8478ca1a4..1261dec7fce96 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -412,8 +412,9 @@ - - + + + diff --git a/addons/board/static/src/js/board_view.js b/addons/board/static/src/js/board_view.js index a29d22cde6a0e..60abe892045b0 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 2293e1c87e6c1..384f3fd699e22 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: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + 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': + '', + }, + + 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 ce3d87688eea7..6935bae71364b 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -323,8 +323,10 @@ def record_production(self): 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 f062393378814..75030e95acc75 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 46adfc5118b0c..9fc94ea631042 100644 --- a/addons/product_margin/models/product_product.py +++ b/addons/product_margin/models/product_product.py @@ -107,7 +107,7 @@ def _compute_product_margin_fields_values(self, field_names=None): 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 7984cd13c8feb..d6916d05af4bb 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -603,7 +603,7 @@ def _compute_tax_id(self): 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 3adfb19c42659..c8862d15c4a4a 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 289d26ecae391..1121a38501aab 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 = $(''); - 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 860ab963ed3f9..87134fdd99042 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -164,7 +164,7 @@ def _compute_qty_delivered(self): 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 7465721b73018..a4176436998e8 100644 --- a/addons/sale_timesheet/views/hr_timesheet_templates.xml +++ b/addons/sale_timesheet/views/hr_timesheet_templates.xml @@ -155,7 +155,7 @@
- + diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index 55532ec6d1669..c078069b07408 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 faa6e82c3da5a..2bb98932c684d 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 fe428af5e5e0a..593969654fb07 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 89be23c849bb5..a92951dce9b45 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 30bafaab117c4..654617c9847d9 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 cebe9102551bd..270a0736845cf 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:'
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '', + 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 59147edb8960a..d21913e2c58c4 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 b013c05a71eab..10fe2fd0c54e2 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 = $('
').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 1a3391c720dca..20eb2918d041e 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 101f484bdd18a..200216d03e508 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: '' + + '' + + '' + + '
' + + '' + + '
' + + 'test link' + + '
' + + '
' + + '
' + + '
' + + '
', + 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 `` 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', { '' + '', 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 980b94e644c8e..3f557a45d4749 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', { '', 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 3d8d459857cd6..75a1fa9f6b869 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -1067,3 +1067,20 @@ def country_infos(self, country, mode, **kw): 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 52522fa8117ff..a7b7d41998b35 100644 --- a/addons/website_sale_delivery/controllers/main.py +++ b/addons/website_sale_delivery/controllers/main.py @@ -55,8 +55,7 @@ def _get_shop_payment_values(self, order, **kwargs): 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 @@ def update_eshop_carrier(self, **post): '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 971e604d1d145..79c70becf98ce 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); diff --git a/doc/cla/individual/linekio.md b/doc/cla/individual/linekio.md new file mode 100644 index 0000000000000..7c905b998baee --- /dev/null +++ b/doc/cla/individual/linekio.md @@ -0,0 +1,11 @@ +France, 2018-10-12 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +BenoƮt Fontaine https://github.com/Linekio