diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index 0d7ed2f4fe2..0c7b6d71908 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -277,7 +277,7 @@ var AbstractField = Widget.extend({ * synchronous. * * @private - * @returns {Deferred} + * @returns {Deferred|undefined} */ _render: function () { if (this.mode === 'edit') { @@ -291,7 +291,7 @@ var AbstractField = Widget.extend({ * concrete widget. * * @private - * @returns {Deferred} + * @returns {Deferred|undefined} */ _renderEdit: function () { }, @@ -300,7 +300,7 @@ var AbstractField = Widget.extend({ * the concrete widget. * * @private - * @returns {Deferred} + * @returns {Deferred|undefined} */ _renderReadonly: function () { }, diff --git a/addons/web/static/src/js/fields/field_registry.js b/addons/web/static/src/js/fields/field_registry.js index 00423d670fb..c626aebdaec 100644 --- a/addons/web/static/src/js/fields/field_registry.js +++ b/addons/web/static/src/js/fields/field_registry.js @@ -13,6 +13,7 @@ var AbstractField = require('web.AbstractField'); var basic_fields = require('web.basic_fields'); var relational_fields = require('web.relational_fields'); var registry = require('web.field_registry'); +var special_fields = require('web.special_fields'); // Basic fields @@ -63,4 +64,8 @@ registry .add('statusbar', relational_fields.FieldStatus) .add('one2many_list', relational_fields.FieldOne2Many); +// Special fields +registry + .add('timezone_mismatch', special_fields.FieldTimezoneMismatch); + }); diff --git a/addons/web/static/src/js/fields/special_fields.js b/addons/web/static/src/js/fields/special_fields.js new file mode 100644 index 00000000000..50f5e293eb7 --- /dev/null +++ b/addons/web/static/src/js/fields/special_fields.js @@ -0,0 +1,108 @@ +odoo.define('web.special_fields', function (require) { +"use strict"; + +var core = require('web.core'); +var field_utils = require('web.field_utils'); +var relational_fields = require('web.relational_fields'); + +var FieldSelection = relational_fields.FieldSelection; +var _t = core._t; + + +/** + * This widget is intended to display a warning near a label of a 'timezone' field + * indicating if the browser timezone is identical (or not) to the selected timezone. + * This widget depends on a field given with the param 'tz_offset_field', which contains + * the time difference between UTC time and local time, in minutes. + */ +var FieldTimezoneMismatch = FieldSelection.extend({ + /** + * @override + */ + start: function () { + this._datetime = setInterval(this._renderDateTimeTimezone.bind(this), 1000); + return this._super.apply(this, arguments); + }, + /** + * @override + */ + destroy: function () { + clearInterval(this._datetime); + return this._super(); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + _render: function () { + this._super.apply(this, arguments); + this._renderTimezoneMismatch.bind(this); + }, + /** + * Display the time in the user timezone (reload each second) + * + * @private + */ + _renderDateTimeTimezone: function () { + if (!this.mismatch) { + return; + } + var offset = this.recordData.tz_offset.match(/([+-])([0-9]{2})([0-9]{2})/); + offset = (offset[1] === '-' ? -1 : 1) * (parseInt(offset[2])*60 + parseInt(offset[3])); + var datetime = field_utils.format.datetime(moment.utc().add(offset, 'minutes'), {timezone: false}); + var content = this.$option.html().split(' ')[0]; + content += ' ('+ datetime + ')'; + this.$option.html(content); + }, + /** + * Display the timezone alert + * + * Note: timezone alert is a span that is added after $el, and $el is now a + * set of two elements + * + * @private + */ + _renderTimezoneMismatch: function () { + // we need to clean the warning to have maximum one alert + this.$el.last().filter('.o_tz_warning').remove(); + this.$el = this.$el.first(); + + if (this.$option) { + this.$option.html(this.$option.html().split(' ')[0]); + } + + var userOffset = this.recordData.tz_offset; + this.mismatch = false; + if (userOffset) { + var offset = -(new Date().getTimezoneOffset()); + var browserOffset = (offset < 0) ? "-" : "+"; + browserOffset += _.str.sprintf("%02d", Math.abs(offset / 60)); + browserOffset += _.str.sprintf("%02d", Math.abs(offset % 60)); + this.mismatch = (browserOffset !== userOffset); + } + + if (this.mismatch){ + var $span = $(''); + $span.insertAfter(this.$el); + $span.attr('title', _t("Timezone Mismatch : The timezone of your browser doesn't match the selected one. The time in Odoo is displayed according to your field timezone.")); + this.$el = this.$el.add($span); + + var value = this.$el.val(); + this.$option = this.$('option').filter(function () { + return $(this).attr('value') === value; + }); + this._renderDateTimeTimezone(); + } + }, +}); + +return { + FieldTimezoneMismatch: FieldTimezoneMismatch, +}; + +}); diff --git a/addons/web/static/src/js/fields/upgrade_fields.js b/addons/web/static/src/js/fields/upgrade_fields.js index e24f26cc8cb..8304165b1c7 100644 --- a/addons/web/static/src/js/fields/upgrade_fields.js +++ b/addons/web/static/src/js/fields/upgrade_fields.js @@ -180,7 +180,6 @@ var UpgradeRadio = FieldRadio.extend(AbstractFieldUpgrade, { }, }); - field_registry .add('upgrade_boolean', UpgradeBoolean) .add('upgrade_radio', UpgradeRadio); diff --git a/addons/web/static/src/less/form_view.less b/addons/web/static/src/less/form_view.less index a422a04f2c7..f8a221672d8 100644 --- a/addons/web/static/src/less/form_view.less +++ b/addons/web/static/src/less/form_view.less @@ -376,7 +376,10 @@ // Timezone widget warning .o_tz_warning { color: @brand-danger; - cursor: pointer; + cursor: help; + position: absolute; + margin-left: -40px; + margin-top: 5px; } // One2Many Kanban views diff --git a/addons/web/static/tests/fields/special_fields.js b/addons/web/static/tests/fields/special_fields.js new file mode 100644 index 00000000000..3b16d0a0a62 --- /dev/null +++ b/addons/web/static/tests/fields/special_fields.js @@ -0,0 +1,172 @@ +odoo.define('web.special_fields_tests', function (require) { +"use strict"; + +var ListView = require('web.ListView'); +var testUtils = require('web.test_utils'); + +var createView = testUtils.createView; + +QUnit.module('fields', {}, function () { + +QUnit.module('special_fields', { + beforeEach: function () { + this.data = { + partner: { + fields: { + display_name: { string: "Displayed name", type: "char" }, + foo: {string: "Foo", type: "char", default: "My little Foo Value"}, + bar: {string: "Bar", type: "boolean", default: true}, + int_field: {string: "int_field", type: "integer", sortable: true}, + qux: {string: "Qux", type: "float", digits: [16,1] }, + p: {string: "one2many field", type: "one2many", relation: 'partner', relation_field: 'trululu'}, + turtles: {string: "one2many turtle field", type: "one2many", relation: 'turtle'}, + trululu: {string: "Trululu", type: "many2one", relation: 'partner'}, + timmy: { string: "pokemon", type: "many2many", relation: 'partner_type'}, + product_id: {string: "Product", type: "many2one", relation: 'product'}, + color: { + type: "selection", + selection: [['red', "Red"], ['black', "Black"]], + default: 'red', + }, + date: {string: "Some Date", type: "date"}, + datetime: {string: "Datetime Field", type: 'datetime'}, + user_id: {string: "User", type: 'many2one', relation: 'user'}, + }, + records: [{ + id: 1, + display_name: "first record", + bar: true, + foo: "yop", + int_field: 10, + qux: 0.44, + p: [], + turtles: [2], + timmy: [], + trululu: 4, + user_id: 17, + }, { + id: 2, + display_name: "second record", + bar: true, + foo: "blip", + int_field: 9, + qux: 13, + p: [], + timmy: [], + trululu: 1, + product_id: 37, + date: "2017-01-25", + datetime: "2016-12-12 10:55:05", + user_id: 17, + }, { + id: 4, + display_name: "aaa", + bar: false, + }], + onchanges: {}, + }, + product: { + fields: { + name: {string: "Product Name", type: "char"} + }, + records: [{ + id: 37, + display_name: "xphone", + }, { + id: 41, + display_name: "xpad", + }] + }, + partner_type: { + fields: { + name: {string: "Partner Type", type: "char"}, + color: {string: "Color index", type: "integer"}, + }, + records: [ + {id: 12, display_name: "gold", color: 2}, + {id: 14, display_name: "silver", color: 5}, + ] + }, + turtle: { + fields: { + display_name: { string: "Displayed name", type: "char" }, + turtle_foo: {string: "Foo", type: "char", default: "My little Foo Value"}, + turtle_bar: {string: "Bar", type: "boolean", default: true}, + turtle_int: {string: "int", type: "integer", sortable: true}, + turtle_qux: {string: "Qux", type: "float", digits: [16,1], required: true, default: 1.5}, + turtle_description: {string: "Description", type: "text"}, + turtle_trululu: {string: "Trululu", type: "many2one", relation: 'partner'}, + product_id: {string: "Product", type: "many2one", relation: 'product', required: true}, + partner_ids: {string: "Partner", type: "many2many", relation: 'partner'}, + }, + records: [{ + id: 1, + display_name: "leonardo", + turtle_bar: true, + turtle_foo: "yop", + partner_ids: [], + }, { + id: 2, + display_name: "donatello", + turtle_bar: true, + turtle_foo: "blip", + turtle_int: 9, + partner_ids: [2,4], + }, { + id: 3, + display_name: "raphael", + turtle_bar: false, + turtle_foo: "kawa", + turtle_int: 21, + turtle_qux: 9.8, + partner_ids: [], + }], + }, + user: { + fields: { + name: {string: "Name", type: "char"} + }, + records: [{ + id: 17, + name: "Aline", + }, { + id: 19, + name: "Christine", + }] + }, + }; + } +}, function () { + + QUnit.module('FieldTimezoneMismatch'); + + QUnit.test('widget timezone_mismatch in a list view', function (assert) { + assert.expect(3); + + this.data.partner.records.forEach(function (r) { + r.color = 'red'; + }); + + var list = createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: '' + + '' + + '', + }); + + assert.strictEqual(list.$('td:contains(Red)').length, 3, + "should have 3 rows with correct value"); + list.$('td:contains(Red):first').click(); + + var $td = list.$('tbody tr.o_selected_row td:not(.o_list_record_selector)'); + + assert.strictEqual($td.find('select').length, 1, "td should have a child 'select'"); + assert.strictEqual($td.contents().length, 1, "select tag should be only child of td"); + list.destroy(); + }); + +}); +}); +}); diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 47db318fd30..8eeb2c7eba5 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -184,6 +184,7 @@ + @@ -431,6 +432,7 @@ +