diff --git a/addons/board/static/src/js/board_view.js b/addons/board/static/src/js/board_view.js index b2d57903a3d..a40348bc319 100644 --- a/addons/board/static/src/js/board_view.js +++ b/addons/board/static/src/js/board_view.js @@ -9,6 +9,7 @@ var Domain = require('web.Domain'); var FormController = require('web.FormController'); var FormRenderer = require('web.FormRenderer'); var FormView = require('web.FormView'); +var pyUtils = require('web.pyeval'); // do not forwardport this to 12.0 var viewRegistry = require('web.view_registry'); var _t = core._t; @@ -236,7 +237,6 @@ var BoardRenderer = FormRenderer.extend({ */ _createController: function (params) { var self = this; - var context = params.context.eval(); return this._rpc({ route: '/web/action/load', params: {action_id: params.actionID} @@ -246,18 +246,22 @@ var BoardRenderer = FormRenderer.extend({ // the action does not exist anymore return $.when(); } + var rawContext = new Context(params.context, action.context); + var context = pyUtils.eval('context', rawContext); + var domain = params.domain || pyUtils.eval('domain', action.domain || '[]', action.context); + var viewType = params.viewType || action.views[0][1]; var view = _.find(action.views, function (descr) { - return descr[1] === params.viewType; - }) || [false, params.viewType]; + return descr[1] === viewType; + }) || [false, viewType]; return self.loadViews(action.res_model, context, [view]) .then(function (viewsInfo) { - var viewInfo = viewsInfo[params.viewType]; - var View = viewRegistry.get(params.viewType); + var viewInfo = viewsInfo[viewType]; + var View = viewRegistry.get(viewType); var view = new View(viewInfo, { action: action, context: context, - domain: params.domain, - groupBy: context.group_by, + domain: domain, + groupBy: context.group_by || [], modelName: action.res_model, hasSelectors: false, }); diff --git a/addons/board/static/tests/dashboard_tests.js b/addons/board/static/tests/dashboard_tests.js index e777b7b36b7..1329d883696 100644 --- a/addons/board/static/tests/dashboard_tests.js +++ b/addons/board/static/tests/dashboard_tests.js @@ -191,6 +191,57 @@ QUnit.test('basic functionality, with one sub action', function (assert) { form.destroy(); }); +QUnit.test('can render an action without view_mode attribute', function (assert) { + // The view_mode attribute is automatically set to the 'action' nodes when + // the action is added to the dashboard using the 'Add to dashboard' button + // in the searchview. However, other dashboard views can be written by hand + // (see openacademy tutorial), and in this case, we don't want hardcode + // action's params (like context or domain), as the dashboard can directly + // retrieve them from the action. Same applies for the view_type, as the + // first view of the action can be used, by default. + assert.expect(2); + + var form = createView({ + View: FormView, // replace by BoardView when forwarported to saas-11.3 + model: 'board', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + archs: { + 'partner,4,list': + '', + }, + mockRPC: function (route, args) { + if (route === '/board/static/src/img/layout_1-1-1.png') { + return $.when(); + } + if (route === '/web/action/load') { + return $.when({ + context: '{"b": 2}', + domain: '[["foo", "=", "yop"]]', + res_model: 'partner', + views: [[4, 'list'], [false, 'form']], + }); + } + if (route === '/web/dataset/search_read') { + assert.deepEqual(args.domain, [['foo', '=', 'yop']], + "should use the domain of the action"); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual(form.$('.oe_action:contains(ABC) .o_list_view').length, 1, + "the list view (first view of action) should have been rendered correctly"); + + form.destroy(); +}); + QUnit.test('can sort a sub list', function (assert) { assert.expect(2); diff --git a/addons/mrp/static/tests/mrp_tests.js b/addons/mrp/static/tests/mrp_tests.js index d86adf842c5..eff1104a041 100644 --- a/addons/mrp/static/tests/mrp_tests.js +++ b/addons/mrp/static/tests/mrp_tests.js @@ -1,6 +1,7 @@ odoo.define('mrp.tests', function (require) { "use strict"; +var field_registry = require('web.field_registry'); var FormView = require('web.FormView'); var testUtils = require("web.test_utils"); @@ -29,6 +30,49 @@ QUnit.module('mrp', { }, }, function () { + QUnit.test("pdf_viewer: upload rendering", function (assert) { + assert.expect(6); + + testUtils.patch(field_registry.map.pdf_viewer, { + on_file_change: function (ev) { + ev.target = {files: [new Blob()]}; + this._super.apply(this, arguments); + }, + _getURI: function (fileURI) { + var res = this._super.apply(this, arguments); + assert.step('_getURI'); + assert.ok(_.str.startsWith(fileURI, 'blob:')); + this.PDFViewerApplication = { + open: function (URI) { + assert.step('open'); + assert.ok(_.str.startsWith(URI, 'blob:')); + }, + }; + return 'about:blank'; + }, + }); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: + '
' + + '' + + '', + }); + + // first upload initialize iframe + form.$('input[type="file"]').trigger('change'); + assert.verifySteps(['_getURI']); + // second upload call pdfjs method inside iframe + form.$('input[type="file"]').trigger('change'); + assert.verifySteps(['_getURI', 'open']); + + testUtils.unpatch(field_registry.map.pdf_viewer); + form.destroy(); + }); + QUnit.test("bullet_state: basic rendering", function (assert) { assert.expect(2); diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index d911481d982..38f064685fb 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -185,6 +185,8 @@ class StockMoveLine(models.Model): ml = super(StockMoveLine, self).create(vals) if ml.state == 'done': + if 'qty_done' in vals: + ml.move_id.product_uom_qty = ml.move_id.quantity_done if ml.product_id.type == 'product': Quant = self.env['stock.quant'] quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id,rounding_method='HALF-UP') diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index 0796cbc706a..55532ec6d16 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -427,6 +427,10 @@ var FieldDate = InputField.extend({ this._super.apply(this, arguments); // use the session timezone when formatting dates this.formatOptions.timezone = true; + this.datepickerOptions = _.defaults( + this.nodeOptions.datepicker || {}, + {defaultDate: this.value} + ); }, /** * In edit mode, instantiates a DateWidget datepicker and listen to changes. @@ -491,13 +495,7 @@ var FieldDate = InputField.extend({ * @private */ _makeDatePicker: function () { - return new datepicker.DateWidget( - this, - _.defaults( - this.nodeOptions.datepicker || {}, - {defaultDate: this.value} - ) - ); + return new datepicker.DateWidget(this, this.datepickerOptions); }, /** @@ -515,6 +513,17 @@ var FieldDate = InputField.extend({ var FieldDateTime = FieldDate.extend({ supportedFieldTypes: ['datetime'], + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + if (this.value) { + var offset = this.getSession().getTZOffset(this.value); + var displayedValue = this.value.clone().add(offset, 'minutes'); + this.datepickerOptions.defaultDate = displayedValue; + } + }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -545,10 +554,8 @@ var FieldDateTime = FieldDate.extend({ * @private */ _makeDatePicker: function () { - var value = this.value && this.value.clone().add(this.getSession().getTZOffset(this.value), 'minutes'); - return new datepicker.DateTimeWidget(this, {defaultDate: value}); + return new datepicker.DateTimeWidget(this, this.datepickerOptions); }, - /** * Set the datepicker to the right value rather than the default one. * @@ -1314,19 +1321,23 @@ var FieldPdfViewer = FieldBinaryFile.extend({ }, /** * @private + * @param {string} [fileURI] file URI if specified * @returns {string} the pdf viewer URI */ - _getURI: function () { - var queryObj = { - model: this.model, - field: this.name, - id: this.res_id, - }; + _getURI: function (fileURI) { var page = this.recordData[this.name + '_page'] || 1; - var queryString = $.param(queryObj); - var url = encodeURIComponent('/web/image?' + queryString); + if (!fileURI) { + var queryObj = { + model: this.model, + field: this.name, + id: this.res_id, + }; + var queryString = $.param(queryObj); + fileURI = '/web/image?' + queryString + } + fileURI = encodeURIComponent(fileURI); var viewerURL = '/web/static/lib/pdfjs/web/viewer.html?file='; - return viewerURL + url + '#page=' + page; + return viewerURL + fileURI + '#page=' + page; }, /** * @private @@ -1370,14 +1381,16 @@ var FieldPdfViewer = FieldBinaryFile.extend({ */ on_file_change: function (ev) { this._super.apply(this, arguments); + var files = ev.target.files; + if (!files || files.length === 0) { + return; + } + // TOCheck: is there requirement to fallback on FileReader if browser don't support URL + var fileURI = URL.createObjectURL(files[0]); if (this.PDFViewerApplication) { - var files = ev.target.files; - if (!files || files.length === 0) { - return; - } - var file = files[0]; - // TOCheck: is there requirement to fallback on FileReader if browser don't support URL - this.PDFViewerApplication.open(URL.createObjectURL(file), 0); + this.PDFViewerApplication.open(fileURI, 0); + } else { + this.$('.o_pdfview_iframe').attr('src', this._getURI(fileURI)); } }, /** diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index 98468872ab9..5b36b433a05 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -2693,6 +2693,33 @@ QUnit.module('basic_fields', { form.destroy(); }); + QUnit.test('datepicker option: daysOfWeekDisabled', function (assert) { + assert.expect(2); + + this.data.partner.fields.datetime.default = "2017-08-02 12:00:05"; + this.data.partner.fields.datetime.required = true; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch:'
' + + '' + + '', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_create').click(); + form.$('.o_datepicker_input.o_input').click(); + assert.ok($('.day:last-child(),.day:nth-child(2)').hasClass('disabled'), + 'first and last days must be disabled'); + assert.notOk($('.day:not(:last-child()):not(:nth-child(2))').hasClass('disabled'), + 'other days must stay clickable'); + + form.destroy(); + }); + QUnit.module('FieldMonetary'); QUnit.test('monetary field in form view', function (assert) { diff --git a/addons/website_sale_options/views/website_sale_options_templates.xml b/addons/website_sale_options/views/website_sale_options_templates.xml index 6fb938231f1..36e54779fcc 100644 --- a/addons/website_sale_options/views/website_sale_options_templates.xml +++ b/addons/website_sale_options/views/website_sale_options_templates.xml @@ -92,17 +92,16 @@ - +
- - -
+
+ + - monetary (FieldMonetary) This is the default field type for fields of type 'monetary'. It is used to diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index ea387ecc1d7..e3016ee4077 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -127,6 +127,13 @@ def get_view_arch_from_file(filename, xmlid): if node.tag in ('template', 'record'): if node.tag == 'record': field = node.find('field[@name="arch"]') + if field is None: + if node.find('field[@name="view_id"]') is not None: + view_id = node.find('field[@name="view_id"]').attrib.get('ref') + ref_id = '%s%s' % ('.' not in view_id and xmlid.split('.')[0] + '.' or '', view_id) + return get_view_arch_from_file(filename, ref_id) + else: + return None _fix_multiple_roots(field) inner = u''.join([etree.tostring(child, encoding='unicode') for child in field.iterchildren()]) return field.text + inner