diff --git a/addons/web/static/src/legacy/js/fields/basic_fields.js b/addons/web/static/src/legacy/js/fields/basic_fields.js index 5ce2d004537..58b36a195a0 100644 --- a/addons/web/static/src/legacy/js/fields/basic_fields.js +++ b/addons/web/static/src/legacy/js/fields/basic_fields.js @@ -1068,10 +1068,83 @@ const RemainingDays = AbstractField.extend({ description: _lt("Remaining Days"), supportedFieldTypes: ['date', 'datetime'], + /** + * @override + */ + init() { + this._super(...arguments); + // use the session timezone when formatting dates + this.formatOptions.timezone = true; + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Returns the associated element. + * + * @override + */ + getFocusableElement() { + return this.datewidget && this.datewidget.$input || $(); + }, + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- + /** + * @override + * @returns {string} the content of the input + */ + _getValue() { + let value = this.datewidget.getValue(); + if (this.field.type === "datetime") { + value = value && value.add(-this.getSession().getTZOffset(value), 'minutes'); + } + return value; + }, + /** + * Instantiates a new DateWidget datepicker. + * + * @private + */ + _makeDatePicker() { + this.datepickerOptions = _.defaults({}, { defaultDate: this.value }); + if (this.field.type === "datetime" && this.value) { + const offset = this.getSession().getTZOffset(this.value); + const displayedValue = this.value.clone().add(offset, 'minutes'); + this.datepickerOptions.defaultDate = displayedValue; + } + if (this.field.type === "date") { + return new datepicker.DateWidget(this, this.datepickerOptions); + } + return new datepicker.DateTimeWidget(this, this.datepickerOptions); + }, + /** + * Displays date/datetime picker in edit mode. + * + * @override + */ + async _renderEdit() { + await this._super(...arguments); + if (this.datewidget) { + this.datewidget.destroy(); + } + + this.datewidget = this._makeDatePicker(); + this.datewidget.on('datetime_changed', this, () => { + const value = this._getValue(); + if ((!value && this.value) || (value && !this._isSameValue(value))) { + this._setValue(value); + } + }); + + await this.datewidget.appendTo('
'); + this.$el.append(this.datewidget.$el); + }, + /** * Displays the delta (in days) between the value of the field and today. If * the delta is larger than 99 days, displays the date as usual (without @@ -1079,7 +1152,7 @@ const RemainingDays = AbstractField.extend({ * * @override */ - _render() { + _renderReadonly() { if (this.value === false) { this.$el.removeClass('font-weight-bold text-danger text-warning'); return; diff --git a/addons/web/static/tests/legacy/fields/basic_fields_tests.js b/addons/web/static/tests/legacy/fields/basic_fields_tests.js index 668834bb189..0b74bd29627 100644 --- a/addons/web/static/tests/legacy/fields/basic_fields_tests.js +++ b/addons/web/static/tests/legacy/fields/basic_fields_tests.js @@ -4958,8 +4958,93 @@ QUnit.module('basic_fields', { unpatchDate(); }); + QUnit.test('remaining_days widget on a date field in multi edit list view', async function (assert) { + assert.expect(7); + + const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11 + this.data.partner.records = [ + { id: 1, date: '2017-10-08' }, // today + { id: 2, date: '2017-10-09' }, // tomorrow + { id: 8, date: false }, + ]; + + const list = await createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: '', + translateParameters: { // Avoid issues due to localization formats + date_format: '%m/%d/%Y', + }, + }); + + assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), 'Today'); + assert.strictEqual(list.$('.o_data_cell:nth(1)').text(), 'Tomorrow'); + + // select two records and edit them + await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input')); + await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input')); + + await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell')); + assert.containsOnce(list, 'input.o_datepicker_input', 'should have date picker input'); + await testUtils.fields.editAndTrigger(list.$('.o_datepicker_input'), '10/10/2017', ['input', 'change', 'focusout']); + + assert.containsOnce(document.body, '.modal'); + assert.strictEqual($('.modal .o_field_widget').text(), "In 2 days", + "should have 'In 2 days' value to change"); + await testUtils.dom.click($('.modal .modal-footer .btn-primary')); + + assert.strictEqual(list.$('.o_data_row:first .o_data_cell').text(), "In 2 days", + "should have 'In 2 days' as date field value"); + assert.strictEqual(list.$('.o_data_row:nth(1) .o_data_cell').text(), "In 2 days", + "should have 'In 2 days' as date field value"); + + list.destroy(); + unpatchDate(); + }); + + QUnit.test('remaining_days widget, enter wrong value manually in multi edit list view', async function (assert) { + assert.expect(6); + + const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11 + this.data.partner.records = [ + { id: 1, date: '2017-10-08' }, // today + { id: 2, date: '2017-10-09' }, // tomorrow + { id: 8, date: false }, + ]; + + const list = await createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: '', + translateParameters: { // Avoid issues due to localization formats + date_format: '%m/%d/%Y', + }, + }); + + assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), 'Today'); + assert.strictEqual(list.$('.o_data_cell:nth(1)').text(), 'Tomorrow'); + + // select two records and edit them + await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input')); + await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input')); + + await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell')); + assert.containsOnce(list, 'input.o_datepicker_input', 'should have date picker input'); + await testUtils.fields.editAndTrigger(list.$('.o_datepicker_input'), 'blabla', ['input', 'change']); + await testUtils.dom.click(list.$el); + + assert.containsNone(document.body, '.modal'); + assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), 'Today'); + assert.strictEqual(list.$('.o_data_cell:nth(1)').text(), 'Tomorrow'); + + list.destroy(); + unpatchDate(); + }); + QUnit.test('remaining_days widget on a date field in form view', async function (assert) { - assert.expect(4); + assert.expect(8); const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11 this.data.partner.records = [ @@ -4977,11 +5062,64 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_widget').text(), 'Today'); assert.hasClass(form.$('.o_field_widget'), 'font-weight-bold text-warning'); - // in edit mode, this widget should not be editable. + // in edit mode, this widget should be editable. await testUtils.form.clickEdit(form); assert.hasClass(form.$('.o_form_view'), 'o_form_editable'); - assert.containsOnce(form, 'div.o_field_widget[name=date]'); + assert.containsOnce(form, 'div.o_field_widget[name=date] .o_datepicker'); + + await testUtils.dom.openDatepicker(form.$('.o_datepicker')); + assert.strictEqual($('.bootstrap-datetimepicker-widget:visible').length, 1, + "datepicker should be opened"); + + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .day[data-day="10/09/2017"]')); + await testUtils.form.clickSave(form); + assert.strictEqual(form.$('.o_field_widget').text(), 'Tomorrow'); + + await testUtils.form.clickEdit(form); + await testUtils.dom.openDatepicker(form.$('.o_datepicker')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .day[data-day="10/07/2017"]')); + await testUtils.form.clickSave(form); + assert.strictEqual(form.$('.o_field_widget').text(), 'Yesterday'); + assert.hasClass(form.$('.o_field_widget'), 'text-danger'); + + form.destroy(); + unpatchDate(); + }); + + QUnit.test('remaining_days widget on a datetime field in form view', async function (assert) { + assert.expect(6); + + const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11 + this.data.partner.records = [ + { id: 1, datetime: '2017-10-08 10:00:00' }, // today + ]; + + const form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
', + res_id: 1, + }); + + assert.strictEqual(form.$('.o_field_widget').text(), 'Today'); + assert.hasClass(form.$('.o_field_widget'), 'text-warning'); + + // in edit mode, this widget should be editable. + await testUtils.form.clickEdit(form); + + assert.hasClass(form.$('.o_form_view'), 'o_form_editable'); + assert.containsOnce(form, 'div.o_field_widget[name=datetime] .o_datepicker'); + + await testUtils.dom.openDatepicker(form.$('.o_datepicker')); + assert.strictEqual($('.bootstrap-datetimepicker-widget:visible').length, 1, + "datepicker should be opened"); + + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .day[data-day="10/09/2017"]')); + await testUtils.dom.click($('a[data-action="close"]')); + await testUtils.form.clickSave(form); + assert.strictEqual(form.$('.o_field_widget').text(), 'Tomorrow'); form.destroy(); unpatchDate();