From 40df434b2db3edf9d934b7857488ab5bc1f8a944 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Thu, 29 Sep 2022 19:01:13 +0000 Subject: [PATCH] [REF] web: remove legacy calendar view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The calendar view and all its extensions have been converted to owl, so it's time to get rid of the legacy implementation. closes odoo/odoo#101816 X-original-commit: 681a6a98c9919021c2f688609e5e4263a9cf4d14 Related: odoo/enterprise#32130 Signed-off-by: Michaƫl Mattiello Signed-off-by: Aaron Bohy (aab) --- addons/web/__manifest__.py | 4 - .../static/src/legacy/js/libs/fullcalendar.js | 258 - .../js/views/calendar/calendar_controller.js | 573 --- .../js/views/calendar/calendar_model.js | 824 ---- .../js/views/calendar/calendar_popover.js | 268 -- .../views/calendar/calendar_quick_create.js | 129 - .../js/views/calendar/calendar_renderer.js | 1089 ----- .../legacy/js/views/calendar/calendar_view.js | 216 - .../src/legacy/js/views/view_registry.js | 4 +- addons/web/static/src/legacy/scss/views.scss | 3 +- .../static/src/legacy/scss/web_calendar.scss | 656 --- .../static/src/legacy/xml/web_calendar.xml | 181 - .../tests/legacy/core/owl_dialog_tests.js | 115 - .../tests/legacy/views/calendar_tests.js | 4245 ----------------- 14 files changed, 2 insertions(+), 8563 deletions(-) delete mode 100644 addons/web/static/src/legacy/js/libs/fullcalendar.js delete mode 100644 addons/web/static/src/legacy/js/views/calendar/calendar_controller.js delete mode 100644 addons/web/static/src/legacy/js/views/calendar/calendar_model.js delete mode 100644 addons/web/static/src/legacy/js/views/calendar/calendar_popover.js delete mode 100644 addons/web/static/src/legacy/js/views/calendar/calendar_quick_create.js delete mode 100644 addons/web/static/src/legacy/js/views/calendar/calendar_renderer.js delete mode 100644 addons/web/static/src/legacy/js/views/calendar/calendar_view.js delete mode 100644 addons/web/static/src/legacy/scss/web_calendar.scss delete mode 100644 addons/web/static/src/legacy/xml/web_calendar.xml delete mode 100644 addons/web/static/tests/legacy/views/calendar_tests.js diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py index 6a00ca88a55..a8fc85619a8 100644 --- a/addons/web/__manifest__.py +++ b/addons/web/__manifest__.py @@ -127,7 +127,6 @@ This module provides the core of the Odoo Web Client. 'web/static/src/legacy/js/libs/bootstrap.js', 'web/static/src/legacy/js/libs/content-disposition.js', 'web/static/src/legacy/js/libs/download.js', - 'web/static/src/legacy/js/libs/fullcalendar.js', 'web/static/src/legacy/js/libs/jquery.js', 'web/static/src/legacy/js/libs/moment.js', 'web/static/src/legacy/js/libs/underscore.js', @@ -223,7 +222,6 @@ This module provides the core of the Odoo Web Client. 'web/static/src/legacy/scss/kanban_examples_dialog.scss', 'web/static/src/legacy/scss/kanban_column_progressbar.scss', 'web/static/src/legacy/scss/kanban_view.scss', - 'web/static/src/legacy/scss/web_calendar.scss', 'web/static/src/legacy/scss/data_export.scss', 'base/static/src/scss/onboarding.scss', 'web/static/src/legacy/scss/attachment_preview.scss', @@ -290,7 +288,6 @@ This module provides the core of the Odoo Web Client. 'web/static/src/legacy/xml/control_panel.xml', 'web/static/src/legacy/xml/fields.xml', 'web/static/src/legacy/xml/kanban.xml', - 'web/static/src/legacy/xml/web_calendar.xml', 'web/static/src/legacy/xml/search_panel.xml', 'web/static/src/legacy/xml/week_days.xml', ], @@ -397,7 +394,6 @@ This module provides the core of the Odoo Web Client. 'web/static/src/legacy/js/libs/bootstrap.js', 'web/static/src/legacy/js/libs/content-disposition.js', 'web/static/src/legacy/js/libs/download.js', - 'web/static/src/legacy/js/libs/fullcalendar.js', 'web/static/src/legacy/js/libs/jquery.js', 'web/static/src/legacy/js/libs/moment.js', 'web/static/src/legacy/js/libs/underscore.js', diff --git a/addons/web/static/src/legacy/js/libs/fullcalendar.js b/addons/web/static/src/legacy/js/libs/fullcalendar.js deleted file mode 100644 index c3fd44e94ff..00000000000 --- a/addons/web/static/src/legacy/js/libs/fullcalendar.js +++ /dev/null @@ -1,258 +0,0 @@ -odoo.define('/web/static/src/js/libs/fullcalendar.js', function () { - "use strict"; - - function createYearCalendarView(FullCalendar) { - const { - Calendar, - createElement, - EventApi, - memoizeRendering, - View, - } = FullCalendar; - - class YearView extends View { - constructor() { - super(...arguments); - this.months = null; - this.renderSubCalendarsMem = memoizeRendering( - this.renderSubCalendars, this.unrenderSubCalendars); - this.events = []; - } - - //---------------------------------------------------------------------- - // Getters - //---------------------------------------------------------------------- - - get currentDate() { - return this.context.calendar.state.currentDate; - } - - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - - /** - * @override - */ - destroy() { - this.renderSubCalendarsMem.unrender(); - super.destroy(); - } - /** - * Removes the selection on sub calendar. - * Selections on sub calendars are not propagated to this view so - * this view cannot manage them. - */ - unselect() { - for (const { calendar } of this.months) { - calendar.unselect(); - } - } - /** - * @override - */ - render() { - this.renderSubCalendarsMem(this.context); - super.render(...arguments); - } - /** - * Renders the main layout (the 4x3 month grid) - */ - renderSubCalendars() { - this.el.classList.add('fc-scroller'); - if (!this.context.options.selectable) { - this.el.classList.add('fc-readonly-year-view'); - } - this.months = []; - const currentMonthNumber = (new Date()).getMonth(); - let currentMonthEl = undefined; - for (let monthNumber = 0; monthNumber < 12; monthNumber++) { - const monthDate = new Date(this.currentDate.getFullYear(), monthNumber); - const monthShortName = moment(monthDate).format('MMM').toLowerCase(); - const container = createElement('div', { class: 'fc-month-container' }); - this.el.appendChild(container); - const el = createElement('div', { - class: `fc-month fc-month-${monthShortName}`, - }); - container.appendChild(el); - const calendar = this._createMonthCalendar(el, monthDate); - this.months.push({ el, calendar }); - calendar.render(); - if (monthNumber === currentMonthNumber) { - currentMonthEl = el; - } - } - - if (currentMonthEl !== undefined) { - currentMonthEl.scrollIntoView(); - } - } - /** - * Removes the main layout (the 4x3 month grid). - * Called when view is switched/destroyed. - */ - unrenderSubCalendars() { - for (const { el, calendar } of this.months) { - calendar.destroy(); - el.remove(); - } - } - /** - * Renders events in sub calendars. - * Called every time event source changed (when changing the date, - * when changing filters, adding/removing filters). - */ - renderEvents() { - // `renderDates` also renders events so if it's called just before - // then do not execute this as it will do a re-render. - if (this.datesRendered) { - this.datesRendered = false; - return; - } - this.events = this._computeEvents(); - for (const { calendar } of this.months) { - calendar.refetchEvents(); - } - this._setCursorOnEventDates(); - } - /** - * Renders dates and events in sub calendars. - * Called when the year of the date changed to render a new - * 4*3 grid of month calendar based on the new year. - */ - renderDates() { - this.events = this._computeEvents(); - for (const [monthNumber, { calendar }] of Object.entries(this.months)) { - const monthDate = new Date(this.currentDate.getFullYear(), monthNumber); - calendar.gotoDate(monthDate); - } - this._setCursorOnEventDates(); - this.datesRendered = true; - } - - //---------------------------------------------------------------------- - // Private - //---------------------------------------------------------------------- - - /** - * @private - */ - _computeEvents() { - const calendar = this.context.calendar; - return calendar.getEvents().map(event => { - const endUTC = calendar.dateEnv.toDate(event._instance.range.end); - const end = new Date(event._instance.range.end); - if (endUTC.getHours() > 0 || endUTC.getMinutes() > 0 || - endUTC.getSeconds() > 0 || endUTC.getMilliseconds() > 0) { - end.setDate(end.getDate() + 1); - } - // clone event data to not trigger rerendering and issues - const instance = Object.assign({}, event._instance, { - range: { start: new Date(event._instance.range.start), end }, - }); - const def = Object.assign({}, event._def, { - rendering: 'background', - allDay: true, - }); - return new EventApi(this.context.calendar, def, instance); - }); - } - /** - * Create a month calendar for the date `monthDate` and mount it on container. - * - * @private - * @param {HTMLElement} container - * @param {Date} monthDate - */ - _createMonthCalendar(container, monthDate) { - return new Calendar(container, Object.assign({}, this.context.options, { - defaultDate: monthDate, - defaultView: 'dayGridMonth', - header: { left: false, center: 'title', right: false }, - titleFormat: { month: 'short', year: 'numeric' }, - height: 0, - contentHeight: 0, - showNonCurrentDates: false, - views: { - dayGridMonth: { - columnHeaderText: (date) => moment(date).format("ddd")[0], - }, - }, - selectMinDistance: 5, // needed to not trigger select when click - dateClick: this._onYearDateClick.bind(this), - datesRender: undefined, - events: (info, successCB) => { - successCB(this.events); - }, - windowResize: undefined, - })); - } - /** - * Sets fc-has-event class on every dates that have at least one event. - * - * @private - */ - _setCursorOnEventDates() { - for (const el of this.el.querySelectorAll('.fc-has-event')) { - el.classList.remove('fc-has-event'); - } - for (const event of Object.values(this.events)) { - let currentDate = moment(event.start); - while (currentDate.isBefore(event.end, 'day')) { - const formattedDate = currentDate.format('YYYY-MM-DD'); - const el = this.el.querySelector(`.fc-day-top[data-date="${formattedDate}"]`); - if (el) { - el.classList.add('fc-has-event'); - } - currentDate.add(1, 'days'); - } - } - } - - //---------------------------------------------------------------------- - // Handlers - //---------------------------------------------------------------------- - - /** - * @private - * @param {*} info - */ - _onYearDateClick(info) { - const calendar = this.context.calendar; - const events = this.events - .filter(event => { - const start = moment(event.start); - const end = moment(event.end); - const inclusivity = start.isSame(end, 'day') ? '[]' : '[)'; - return moment(info.date).isBetween(start, end, 'day', inclusivity); - }) - .map(event => { - return Object.assign({}, event._def, event._instance.range); - }); - const yearDateInfo = Object.assign({}, info, { - view: this, - monthView: info.view, - events, - selectable: this.context.options.selectable, - }); - calendar.publiclyTrigger('yearDateClick', [yearDateInfo]); - } - } - - return FullCalendar.createPlugin({ - views: { - dayGridYear: { - class: YearView, - duration: { years: 1 }, - defaults: { - fixedWeekCount: true, - }, - }, - }, - }); - } - - return { - createYearCalendarView, - }; -}); diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_controller.js b/addons/web/static/src/legacy/js/views/calendar/calendar_controller.js deleted file mode 100644 index 4f561dc4617..00000000000 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_controller.js +++ /dev/null @@ -1,573 +0,0 @@ -odoo.define('web.CalendarController', function (require) { -"use strict"; - -/** - * Calendar Controller - * - * This is the controller in the Model-Renderer-Controller architecture of the - * calendar view. Its role is to coordinate the data from the calendar model - * with the renderer, and with the outside world (such as a search view input) - */ - -var AbstractController = require('web.AbstractController'); -var core = require('web.core'); -var Dialog = require('web.Dialog'); -var dialogs = require('web.view_dialogs'); -var QuickCreate = require('web.CalendarQuickCreate'); - -var _t = core._t; -var QWeb = core.qweb; - -function dateToServer (date, fieldType) { - date = date.clone().locale('en'); - if (fieldType === "date") { - return date.local().format('YYYY-MM-DD'); - } - return date.utc().format('YYYY-MM-DD HH:mm:ss'); -} - -var CalendarController = AbstractController.extend({ - custom_events: _.extend({}, AbstractController.prototype.custom_events, { - changeDate: '_onChangeDate', - changeFilter: '_onChangeFilter', - deleteRecord: '_onDeleteRecord', - dropRecord: '_onDropRecord', - next: '_onNext', - openCreate: '_onOpenCreate', - openEvent: '_onOpenEvent', - prev: '_onPrev', - quickCreate: '_onQuickCreate', - updateRecord: '_onUpdateRecord', - viewUpdated: '_onViewUpdated', - AttendeeStatus: '_onAttendeeStatus', - }), - events: _.extend({}, AbstractController.prototype.events, { - 'click button.o_calendar_button_new': '_onButtonNew', - 'click button.o_calendar_button_prev': '_onButtonNavigation', - 'click button.o_calendar_button_today': '_onButtonNavigation', - 'click button.o_calendar_button_next': '_onButtonNavigation', - 'click button.o_calendar_button_day': '_onButtonScale', - 'click button.o_calendar_button_week': '_onButtonScale', - 'click button.o_calendar_button_month': '_onButtonScale', - 'click button.o_calendar_button_year': '_onButtonScale', - }), - /** - * @override - * @param {Widget} parent - * @param {AbstractModel} model - * @param {AbstractRenderer} renderer - * @param {Object} params - */ - init: function (parent, model, renderer, params) { - this._super.apply(this, arguments); - this.current_start = null; - this.displayName = params.displayName; - this.quickAddPop = params.quickAddPop; - this.disableQuickCreate = params.disableQuickCreate; - this.eventOpenPopup = params.eventOpenPopup; - this.showUnusualDays = params.showUnusualDays; - this.formViewId = params.formViewId; - this.readonlyFormViewId = params.readonlyFormViewId; - this.mapping = params.mapping; - this.context = params.context; - this.previousOpen = null; - // The quickCreating attribute ensures that we don't do several create - this.quickCreating = false; - this.scales = params.scales; - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Render the buttons according to the CalendarView.buttons template and - * add listeners on it. Set this.$buttons with the produced jQuery element - * - * @param {jQuery} [$node] a jQuery node where the rendered buttons - * should be inserted. $node may be undefined, in which case the Calendar - * inserts them into this.options.$buttons or into a div of its template - */ - renderButtons: function ($node) { - this.$buttons = $(QWeb.render('CalendarView.buttons', this._renderButtonsParameters())); - - this.$buttons.find('.o_calendar_button_' + this.mode).addClass('active'); - - if ($node) { - this.$buttons.appendTo($node); - } else { - this.$('.o_calendar_buttons').replaceWith(this.$buttons); - } - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Find a className in an array using the start of this class and - * return the last part of a string - * @private - * @param {string} startClassName start of string to find in the "array" - * @param {array|DOMTokenList} classList array of all class - * @return {string|undefined} - */ - _extractLastPartOfClassName(startClassName, classList) { - var result; - classList.forEach(function (value) { - if (value && value.indexOf(startClassName) === 0) { - result = value.substring(startClassName.length); - } - }); - return result; - }, - /** - * Move to the requested direction and reload the view - * - * @private - * @param {string} to either 'prev', 'next' or 'today' - * @returns {Promise} - */ - _move: function (to) { - this.model[to](); - return this.reload(); - }, - /** - * Parameter send to QWeb to render the template of Buttons - * - * @private - * @return {{}} - */ - _renderButtonsParameters() { - return { - scales: this.scales, - }; - }, - /** - * @override - * @private - */ - _update: function () { - var self = this; - if (!this.showUnusualDays) { - return this._super.apply(this, arguments); - } - return this._super.apply(this, arguments).then(function () { - self._rpc({ - model: self.modelName, - method: 'get_unusual_days', - args: [dateToServer(self.model.data.start_date, 'date'), dateToServer(self.model.data.end_date, 'date')], - context: self.context, - }).then(function (data) { - _.each(self.$el.find('td.fc-day'), function (td) { - var $td = $(td); - if (data[$td.data('date')]) { - $td.addClass('o_calendar_disabled'); - } - }); - }); - }); - }, - /** - * @private - * @param {Object} record - * @param {integer} record.id - * @returns {Promise} - */ - _updateRecord: function (record) { - var reload = this.reload.bind(this, {}); - return this.model.updateRecord(record).then(reload, reload); - }, - - /** - * @private - */ - _getEventId(event) { - var id = event.data._id; - id = id && parseInt(id).toString() === id ? parseInt(id) : id; - return id; - }, - - /** - * @private - */ - _getFormDialogOptions: function(self, event) { - var id = this._getEventId(event); - return { - res_model: self.modelName, - res_id: id || null, - context: event.context || self.context, - title: _.str.sprintf(_t("Open: %s"), event.data.title), - on_saved: function () { - if (event.data.on_save) { - event.data.on_save(); - } - self.reload(); - }, - }; - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Handler when a user clicks on button to create event - * - * @private - */ - _onButtonNew() { - this.trigger_up('switch_view', {view_type: 'form'}); - }, - /** - * Handler when a user click on navigation button like prev, next, ... - * - * @private - * @param {Event|jQueryEvent} jsEvent - */ - _onButtonNavigation(jsEvent) { - const action = this._extractLastPartOfClassName('o_calendar_button_', jsEvent.currentTarget.classList); - if (action) { - this._move(action); - } - }, - /** - * Handler when a user click on scale button like day, month, ... - * - * @private - * @param {Event|jQueryEvent} jsEvent - */ - _onButtonScale(jsEvent) { - const scale = this._extractLastPartOfClassName('o_calendar_button_', jsEvent.currentTarget.classList); - if (scale) { - this.model.setScale(scale); - this.reload(); - } - }, - - /** - * @private - * @param {OdooEvent} event - */ - _onChangeDate: function (event) { - var modelData = this.model.get(); - if (modelData.target_date.format('YYYY-MM-DD') === event.data.date.format('YYYY-MM-DD')) { - // When clicking on same date, toggle between the two views - switch (modelData.scale) { - case 'month': this.model.setScale('week'); break; - case 'week': this.model.setScale('day'); break; - case 'day': this.model.setScale('month'); break; - } - } else if (modelData.target_date.week() === event.data.date.week()) { - // When clicking on a date in the same week, switch to day view - this.model.setScale('day'); - } else { - // When clicking on a random day of a random other week, switch to week view - this.model.setScale('week'); - } - if (event.data.scale) { - this.model.setScale(event.data.scale); - } - this.model.setDate(event.data.date); - this.reload(); - }, - /** - * @private - * @param {OdooEvent} event - */ - _onChangeFilter: async function (event) { - if (event.data.value !== 'all' && event.target.filter_field) { - const domain = [['user_id', '=', this.getSession().uid]]; - if (event.data.value !== 'check_all') { - domain.push([event.target.write_field, '=', parseInt(event.data.value)]); - } - const existingFilter = await this._rpc({ - model: event.target.write_model, - method: 'search', - args: [domain], - }); - let val = { - [event.target.filter_field]: event.data.active, - }; - await this._rpc({ - model: event.target.write_model, - method: 'write', - args: [existingFilter, val], - }); - } - if (this.model.changeFilter(event.data) && !event.data.no_reload) { - this.reload(); - } - }, - /** - * @private - * @param {OdooEvent} event - */ - _onDeleteRecord: async function (event) { - var self = this; - if (event.data.event.record.recurrency) { - const recurrenceUpdate = await this._askRecurrenceUpdatePolicy(); - event.data = Object.assign({}, event.data, { - 'recurrenceUpdate': recurrenceUpdate, - }); - if (recurrenceUpdate === 'self_only') { - self.model.deleteRecords([event.data.id], self.modelName).then(function () { - self.reload(); - }); - } else { - return this._rpc({ - model: self.modelName, - method: 'action_mass_deletion', - args: [[event.data.id], recurrenceUpdate], - }).then( function () { - self.reload(); - }); - } - } else { - Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), { - confirm_callback: function () { - self.model.deleteRecords([event.data.id], self.modelName).then(function () { - self.reload(); - }); - } - }); - } - }, - /** - * @private - * @param {OdooEvent} event - */ - _onDropRecord: function (event) { - this._updateRecord(_.extend({}, event.data, { - 'drop': true, - })); - }, - /** - * @private - * @param {OdooEvent} event - */ - _onNext: function (event) { - event.stopPropagation(); - this._move('next'); - }, - /** - * @private - * @param {OdooEvent} event - */ - _onOpenCreate: function (event) { - var self = this; - if (["year", "month"].includes(this.model.get().scale)) { - event.data.allDay = true; - } - var data = this.model.calendarEventToRecord(event.data); - - var context = _.extend({}, this.context, event.options && event.options.context); - // context default has more priority in default_get so if data.name is false then it may - // lead to error/warning while saving record in form view as name field can be required - if (data.name) { - context.default_name = data.name; - } - context['default_' + this.mapping.date_start] = data[this.mapping.date_start] || null; - if (this.mapping.date_stop) { - context['default_' + this.mapping.date_stop] = data[this.mapping.date_stop] || null; - } - if (this.mapping.date_delay) { - context['default_' + this.mapping.date_delay] = data[this.mapping.date_delay] || null; - } - if (this.mapping.all_day) { - context['default_' + this.mapping.all_day] = data[this.mapping.all_day] || null; - } - - for (var k in context) { - if (context[k] && context[k]._isAMomentObject) { - context[k] = dateToServer(context[k]); - } - } - - var options = _.extend({}, this.options, event.options, { - context: context, - title: this._setEventTitle() - }); - - if (this.quick != null) { - this.quick.destroy(); - this.quick = null; - } - - if (!options.disableQuickCreate && !event.data.disableQuickCreate && this.quickAddPop) { - this.quick = new QuickCreate(this, true, options, data, event.data); - this.quick.open(); - this.quick.opened(function () { - self.quick.focus(); - }); - return; - } - - if (this.eventOpenPopup) { - if (this.previousOpen) { this.previousOpen.close(); } - this.previousOpen = new dialogs.FormViewDialog(self, { - res_model: this.modelName, - context: context, - title: options.title, - view_id: this.formViewId || false, - disable_multiple_selection: true, - on_saved: function () { - if (event.data.on_save) { - event.data.on_save(); - } - self.reload(); - }, - }); - this.previousOpen.on('closed', this, () => { - if (event.data.on_close) { - event.data.on_close(); - } - }) - this.previousOpen.open(); - } else { - this.do_action({ - type: 'ir.actions.act_window', - res_model: this.modelName, - views: [[this.formViewId || false, 'form']], - target: 'current', - context: context, - }); - } - }, - /** - * @private - * @param {OdooEvent} event - */ - _onOpenEvent: function (event) { - var self = this; - var id = self._getEventId(event); - - if (!this.eventOpenPopup) { - this._rpc({ - model: self.modelName, - method: 'get_formview_id', - //The event can be called by a view that can have another context than the default one. - args: [[id]], - context: event.context || self.context, - }).then(function (viewId) { - self.do_action({ - type:'ir.actions.act_window', - res_id: id, - res_model: self.modelName, - views: [[viewId || false, 'form']], - target: 'current', - context: event.context || self.context, - }); - }); - return; - } - - var options = self._getFormDialogOptions(self, event); - - if (this.formViewId) { - options.view_id = parseInt(this.formViewId); - } - new dialogs.FormViewDialog(this, options).open(); - }, - /** - * @private - * @param {OdooEvent} ev - */ - _onPrev: function (ev) { - ev.stopPropagation(); - this._move('prev'); - }, - - /** - * Handles saving data coming from quick create box - * - * @private - * @param {OdooEvent} event - */ - _onQuickCreate: function (event) { - var self = this; - if (this.quickCreating) { - return; - } - this.quickCreating = true; - this.model.createRecord(event) - .then(function () { - self.quick.destroy(); - self.quick = null; - self.reload(); - self.quickCreating = false; - }) - .guardedCatch(function (result) { - var errorEvent = result.event; - // This will occurs if there are some more fields required - // Preventdefaulting the error event will prevent the traceback window - errorEvent.preventDefault(); - event.data.options.disableQuickCreate = true; - event.data.data.on_save = self.quick.destroy.bind(self.quick); - self._onOpenCreate(event.data); - self.quickCreating = false; - }); - }, - /** - * @private - * @param {OdooEvent} event - */ - _onUpdateRecord: function (event) { - this._updateRecord(event.data); - }, - /** - * The internal state of the calendar (mode, period displayed) has changed, - * so update the control panel buttons and breadcrumbs accordingly. - * - * @private - * @param {OdooEvent} event - */ - _onViewUpdated: function (event) { - this.mode = event.data.mode; - if (this.$buttons) { - this.$buttons.find('.active').removeClass('active'); - const scaleSelection = this.$buttons.find('.scale_button_selection')[0]; - if (scaleSelection) { - scaleSelection.innerText = this.mode; - } - this.$buttons.find('.o_calendar_button_' + this.mode).addClass('active'); - } - const title = `${this.displayName} (${event.data.title})`; - return this.updateControlPanel({ title }); - }, - - /** - * Update Attendee status in batch for recurrent events - * @private - * @param {OdooEvent} event - */ - _onAttendeeStatus: async function(event) { - const self = this; - let recurrenceUpdate = false; - if (event.data.record.recurrency) { - recurrenceUpdate = await this._askRecurrenceUpdatePolicy(); - event.data = Object.assign({}, event.data, { - 'recurrenceUpdate': recurrenceUpdate, - }); - } - return this._rpc({ - model: self.modelName, - method: 'change_attendee_status', - args: [[event.data.id], event.data.selectedStatus, recurrenceUpdate], - }).then( function () { - self.reload(); - }); - }, - /** - * This function has been created for the only purpose of - * changing the title on the quick create from a calendar. - * This way it can be overriden in other apps - */ - _setEventTitle: function () { - return _t('New Event'); - }, -}); - -return CalendarController; - -}); diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_model.js b/addons/web/static/src/legacy/js/views/calendar/calendar_model.js deleted file mode 100644 index 63877603cb4..00000000000 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_model.js +++ /dev/null @@ -1,824 +0,0 @@ -odoo.define('web.CalendarModel', function (require) { -"use strict"; - -var AbstractModel = require('web.AbstractModel'); -var Context = require('web.Context'); -var core = require('web.core'); -var fieldUtils = require('web.field_utils'); -var session = require('web.session'); - -var _t = core._t; - -function dateToServer (date) { - return date.clone().utc().locale('en').format('YYYY-MM-DD HH:mm:ss'); -} - -return AbstractModel.extend({ - /** - * @override - */ - init: function () { - this._super.apply(this, arguments); - this.end_date = null; - var week_start = _t.database.parameters.week_start; - // calendar uses index 0 for Sunday but Odoo stores it as 7 - this.week_start = week_start !== undefined && week_start !== false ? week_start % 7 : moment().startOf('week').day(); - this.week_stop = this.week_start + 6; - this.filter_check_all = {}; - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Transform fullcalendar event object to OpenERP Data object - */ - calendarEventToRecord: function (event) { - // Normalize event_end without changing fullcalendars event. - var data = {}; - data[this.mapping.create_name_field || 'name'] = event.title; - var start = event.start.clone(); - var end = event.end && event.end.clone(); - - // Set end date if not existing - if (!end || end.diff(start) < 0) { // undefined or invalid end date - if (event.allDay) { - end = start.clone(); - } else { - // in week mode or day mode, convert allday event to event - end = start.clone().add(2, 'h'); - } - } else if (event.allDay) { - // For an "allDay", FullCalendar gives the end day as the - // next day at midnight (instead of 23h59). - end.add(-1, 'days'); - } - - var isDateEvent = this.fields[this.mapping.date_start].type === 'date'; - // An "allDay" event without the "all_day" option is not considered - // as a 24h day. It's just a part of the day (by default: 7h-19h). - if (event.allDay) { - if (!this.mapping.all_day && !isDateEvent) { - if (event.r_start) { - start.hours(event.r_start.hours()) - .minutes(event.r_start.minutes()) - .seconds(event.r_start.seconds()) - .utc(); - end.hours(event.r_end.hours()) - .minutes(event.r_end.minutes()) - .seconds(event.r_end.seconds()) - .utc(); - } else { - // default hours in the user's timezone - start.hours(7); - end.hours(19); - } - start.add(-this.getSession().getTZOffset(start), 'minutes'); - end.add(-this.getSession().getTZOffset(end), 'minutes'); - } - } else { - start.add(-this.getSession().getTZOffset(start), 'minutes'); - end.add(-this.getSession().getTZOffset(end), 'minutes'); - } - - if (this.mapping.all_day) { - if (event.record) { - data[this.mapping.all_day] = - (this.data.scale !== 'month' && event.allDay) || - event.record[this.mapping.all_day] && - end.diff(start) < 10 || - false; - } else { - data[this.mapping.all_day] = event.allDay; - } - } - - data[this.mapping.date_start] = start; - if (this.mapping.date_stop) { - data[this.mapping.date_stop] = end; - } - - if (this.mapping.date_delay) { - if (this.data.scale !== 'month' || (this.data.scale === 'month' && !event.drop)) { - data[this.mapping.date_delay] = (end.diff(start) <= 0 ? end.endOf('day').diff(start) : end.diff(start)) / 1000 / 3600; - } - } - - return data; - }, - /** - * @param {Object} filter - * @returns {boolean} - */ - changeFilter: function (filter) { - var Filter = this.data.filters[filter.fieldName]; - if (filter.value === 'all') { - Filter.all = filter.active; - } - else if (filter.value === 'check_all') { - // Select all numeric filters and update the checkboxes - this.filter_check_all[filter.fieldName] = filter.active; - // Deactivate the All selection - Filter.all = false; - if (filter.active) { - // Check every displayed filters except the 'all' filter - Filter.filters.filter(el => el.value !== 'all').forEach(element => element.active = true ); - return true; - - } else { - Filter.filters.forEach(element => element.active = false); - return true; - } - } - var f = _.find(Filter.filters, function (f) { - return f.value === filter.value; - }); - if (f) { - if (f.active !== filter.active) { - // Update the value according to the checkbox state - f.active = filter.active; - } else if (filter.active) { - // if the filter is NOT active, we remove it by returning true [triggered by _onFilterRemove] - return false; - } - } else if (filter.active) { - Filter.filters.push({ - value: filter.value, - active: true, - }); - } - return true; - }, - /** - * @param {OdooEvent} event - */ - createRecord: function (event) { - var data = this.calendarEventToRecord(event.data.data); - for (var k in data) { - if (data[k] && data[k]._isAMomentObject) { - data[k] = dateToServer(data[k]); - } - } - return this._rpc({ - model: this.modelName, - method: 'create', - args: [data], - context: event.data.options.context, - }); - }, - /** - * @param {any} ids - * @param {any} model - * @returns - */ - deleteRecords: function (ids, model) { - return this._rpc({ - model: model, - method: 'unlink', - args: [ids], - context: session.user_context, // todo: combine with view context - }); - }, - /** - * @override - * @returns {Object} - */ - __get: function () { - return _.extend({}, this.data, { - fields: this.fields - }); - }, - /** - * @override - * @param {any} params - * @returns {Promise} - */ - __load: function (params) { - var self = this; - this.modelName = params.modelName; - this.fields = params.fields; - this.fieldNames = params.fieldNames; - this.fieldsInfo = params.fieldsInfo; - this.mapping = params.mapping; - this.mode = params.mode; // one of month, week or day - this.scales = params.scales; // one of month, week or day - this.scalesInfo = params.scalesInfo; - - // Check whether the date field is editable (i.e. if the events can be - // dragged and dropped) - this.editable = params.editable; - this.creatable = params.creatable; - - // display more button when there are too much event on one day - this.eventLimit = params.eventLimit; - - // fields to display color, e.g.: user_id.partner_id - this.fieldColor = params.fieldColor; - if (!this.preloadPromise) { - this.preloadPromise = new Promise(function (resolve, reject) { - Promise.all([ - self._rpc({model: self.modelName, method: 'check_access_rights', args: ["write", false]}), - self._rpc({model: self.modelName, method: 'check_access_rights', args: ["create", false]}) - ]).then(function (result) { - var write = result[0]; - var create = result[1]; - self.write_right = write; - self.create_right = create; - resolve(); - }).guardedCatch(reject); - }); - } - - this.data = { - domain: params.domain, - context: params.context, - // get in arch the filter to display in the sidebar and the field to read - filters: params.filters, - }; - - this.setDate(params.initialDate); - // Use mode attribute in xml file to specify zoom timeline (day,week,month) - // by default month. - this.setScale(params.mode); - - _.each(this.data.filters, function (filter) { - if (filter.avatar_field && !filter.avatar_model) { - filter.avatar_model = self.modelName; - } - }); - - return this.preloadPromise.then(this._loadCalendar.bind(this)); - }, - /** - * Move the current date range to the next period - */ - next: function () { - this.setDate(this.data.target_date.clone().add(1, this.data.scale)); - }, - /** - * Move the current date range to the previous period - */ - prev: function () { - this.setDate(this.data.target_date.clone().add(-1, this.data.scale)); - }, - /** - * @override - * @param {Object} [params.context] - * @param {Array} [params.domain] - * @returns {Promise} - */ - __reload: function (handle, params) { - if (params.domain) { - this.data.domain = params.domain; - } - if (params.context) { - this.data.context = params.context; - } - return this._loadCalendar(); - }, - /** - * @param {Moment} start. in local TZ - */ - setDate: function (start) { - // keep highlight/target_date in localtime - this.data.highlight_date = this.data.target_date = start.clone(); - this.data.start_date = this.data.end_date = start; - switch (this.data.scale) { - case 'year': { - const yearStart = this.data.start_date.clone().startOf('year'); - let yearStartDay = this.week_start; - if (yearStart.day() < yearStartDay) { - // the 1st of January is before our week start (e.g. week start is Monday, and - // 01/01 is Sunday), so we go one week back - yearStartDay -= 7; - } - this.data.start_date = yearStart.day(yearStartDay).startOf('day'); - this.data.end_date = this.data.end_date.clone() - .endOf('year').day(this.week_stop).endOf('day'); - break; - } - case 'month': - var monthStart = this.data.start_date.clone().startOf('month'); - - var monthStartDay; - if (monthStart.day() >= this.week_start) { - // the month's first day is after our week start - // Then we are in the right week - monthStartDay = this.week_start; - } else { - // The month's first day is before our week start - // Then we should go back to the the previous week - monthStartDay = this.week_start - 7; - } - - this.data.start_date = monthStart.day(monthStartDay).startOf('day'); - this.data.end_date = this.data.start_date.clone().add(5, 'week').day(this.week_stop).endOf('day'); - break; - case 'week': - var weekStartDay = this.week_start; - if (this.data.start_date.day() < this.week_start) { - // The week's first day is after our current day - // Then we should go back to the previous week - weekStartDay -= 7; - } - this.data.start_date = this.data.start_date.clone().day(weekStartDay).startOf('day'); - this.data.end_date = this.data.end_date.clone().day(weekStartDay + 6).endOf('day'); - break; - default: - this.data.start_date = this.data.start_date.clone().startOf('day'); - this.data.end_date = this.data.end_date.clone().endOf('day'); - } - // We have set start/stop datetime as definite begin/end boundaries of a period (month, week, day) - // in local TZ (what is the begining of the week *I am* in ?) - // The following code: - // - converts those to UTC using our homemade method (testable) - // - sets the moment UTC flag to true, to ensure compatibility with third party libs - var manualUtcDateStart = this.data.start_date.clone().add(-this.getSession().getTZOffset(this.data.start_date), 'minutes'); - var formattedUtcDateStart = manualUtcDateStart.format('YYYY-MM-DDTHH:mm:ss') + 'Z'; - this.data.start_date = moment.utc(formattedUtcDateStart); - - var manualUtcDateEnd = this.data.end_date.clone().add(-this.getSession().getTZOffset(this.data.start_date), 'minutes'); - var formattedUtcDateEnd = manualUtcDateEnd.format('YYYY-MM-DDTHH:mm:ss') + 'Z'; - this.data.end_date = moment.utc(formattedUtcDateEnd); - }, - /** - * @param {string} scale the scale to set - */ - setScale: function (scale) { - if (!_.contains(this.scales, scale)) { - scale = "week"; - } - this.data.scale = scale; - this.setDate(this.data.target_date); - }, - /** - * Move the current date range to the period containing today - */ - today: function () { - this.setDate(moment(new Date())); - }, - /** - * @param {Object} record - * @param {integer} record.id - * @returns {Promise} - */ - updateRecord: function (record) { - // Cannot modify actual name yet - var data = _.omit(this.calendarEventToRecord(record), 'name'); - for (var k in data) { - if (data[k] && data[k]._isAMomentObject) { - data[k] = dateToServer(data[k]); - } - } - var context = new Context(this.data.context, {from_ui: true}); - return this._rpc({ - model: this.modelName, - method: 'write', - args: [[parseInt(record.id, 10)], data], - context: context - }); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Converts this.data.filters into a domain - * - * @private - * @returns {Array} - */ - _getFilterDomain: function () { - // List authorized values for every field - // fields with an active 'all' filter are skipped - var authorizedValues = {}; - var avoidValues = {}; - - _.each(this.data.filters, function (filter) { - // Skip 'all' filters because they do not affect the domain - if (filter.all) return; - // all visible filters have been activated - if (filter.visible) return; - - // Loop over subfilters to complete authorizedValues - _.each(filter.filters, function (f) { - if (filter.write_model) { - if (!authorizedValues[filter.fieldName]) - authorizedValues[filter.fieldName] = []; - - if (f.active) { - authorizedValues[filter.fieldName].push(f.value); - } - } else { - if (!f.active) { - if (!avoidValues[filter.fieldName]) - avoidValues[filter.fieldName] = []; - - avoidValues[filter.fieldName].push(f.value); - } - } - }); - }); - - // Compute the domain - var domain = []; - for (var field in authorizedValues) { - domain.push([field, 'in', authorizedValues[field]]); - } - for (var field in avoidValues) { - if (avoidValues[field].length > 0) { - domain.push([field, 'not in', avoidValues[field]]); - } - } - - return domain; - }, - /** - * @private - * @returns {Object} - */ - _getFullCalendarOptions: function () { - var format12Hour = { - hour: 'numeric', - minute: '2-digit', - omitZeroMinute: true, - meridiem: 'short' - }; - var format24Hour = { - hour: 'numeric', - minute: '2-digit', - hour12: false, - }; - return { - defaultView: this.scalesInfo[this.mode || 'week'], - header: false, - selectable: this.creatable && this.create_right, - selectMirror: true, - editable: this.editable, - droppable: true, - navLinks: false, - eventLimit: this.eventLimit, // allow "more" link when too many events - snapMinutes: 15, - longPressDelay: 500, - eventResizableFromStart: true, - nowIndicator: true, - weekNumbers: true, - weekNumbersWithinDays: true, - weekNumberCalculation: function (date) { - // Since FullCalendar v4 ISO 8601 week date is preferred so we force the old system - return moment(date).week(); - }, - weekLabel: _t("Week"), - allDayText: _t("All day"), - monthNames: moment.months(), - monthNamesShort: moment.monthsShort(), - dayNames: moment.weekdays(), - dayNamesShort: moment.weekdaysShort(), - dayNamesMin: moment.weekdaysMin(), - firstDay: this.week_start, - slotLabelFormat: _t.database.parameters.time_format.search("%H") !== -1 ? format24Hour : format12Hour, - allDaySlot: this.mapping.all_day || this.fields[this.mapping.date_start].type === 'date', - }; - }, - /** - * Return a domain from the date range - * - * @private - * @returns {Array} A domain containing datetimes start and stop in UTC - * those datetimes are formatted according to server's standards - */ - _getRangeDomain: function () { - // Build OpenERP Domain to filter object by this.mapping.date_start field - // between given start, end dates. - var domain = [[this.mapping.date_start, '<=', dateToServer(this.data.end_date)]]; - if (this.mapping.date_stop) { - domain.push([this.mapping.date_stop, '>=', dateToServer(this.data.start_date)]); - } else if (!this.mapping.date_delay) { - domain.push([this.mapping.date_start, '>=', dateToServer(this.data.start_date)]); - } - return domain; - }, - /** - * @private - * @returns {Promise} - */ - _loadCalendar: function () { - var self = this; - this.data.fc_options = this._getFullCalendarOptions(); - var defs = _.map(this.data.filters, this._loadFilter.bind(this)); - - return Promise.all(defs).then(function () { - return self._rpc({ - model: self.modelName, - method: 'search_read', - context: self.data.context, - fields: self.fieldNames, - domain: self.data.domain.concat(self._getRangeDomain()).concat(self._getFilterDomain()) - }) - .then(async function (events) { - self._parseServerData(events); - self.data.data = await self._getCalendarEventData(events); - return Promise.all([ - self._loadColors(self.data, self.data.data), - self._loadRecordsToFilters(self.data, self.data.data) - ]); - }); - }); - }, - /** - * @private - * @param {any} element - * @param {any} events - * @returns {Promise} - */ - _loadColors: function (element, events) { - if (this.fieldColor) { - const fieldName = this.fieldColor; - _.each(events, function (event) { - var value = event.record[fieldName]; - event.color_index = _.isArray(value) ? value[0] : value; - }); - this.model_color = this.fields[fieldName].relation || element.model; - } - return Promise.resolve(); - }, - /** - * Get the color index used to render the event and the filter. - * Since we have a maximum of 30 colors, we have to adapt the color - * index in case the id exceeds the number of colors available. - * - * - actual_color_list contains the list of color already used in the filter - * - color_index is the color we try to apply for the event - * - * @private - * @param {Array} actual_color_list - * @param {Integer} color_index - * @returns {Integer} - */ - _getColorIndex: function(actual_color_list, color_index) { - var actual_color_list = actual_color_list || [0]; - var new_color_index = color_index % 50; - if (new_color_index !== color_index && actual_color_list.includes(new_color_index)) { - new_color_index = this._getColorIndex(actual_color_list, new_color_index + 3); - } - return new_color_index; - }, - /** - * @private - * @param {any} filter - * @returns {Promise} - */ - _loadFilter: function (filter) { - if (!filter.write_model) { - return Promise.resolve(); - } - - var field = this.fields[filter.fieldName]; - var fields = [filter.write_field]; - if (filter.filter_field) { - fields.push(filter.filter_field); - } - return this._rpc({ - model: filter.write_model, - method: 'search_read', - domain: [["user_id", "=", session.uid]], - fields: fields, - }) - .then(function (res) { - var records = _.map(res, function (record) { - var _value = record[filter.write_field]; - var value = _.isArray(_value) ? _value[0] : _value; - var f = _.find(filter.filters, function (f) {return f.value === value;}); - var formater = fieldUtils.format[_.contains(['many2many', 'one2many'], field.type) ? 'many2one' : field.type]; - // By default, only current user partner is checked. - return { - 'id': record.id, - 'value': value, - 'label': formater(_value, field), - 'active': (f && f.active) || (filter.filter_field && record[filter.filter_field]), - }; - }); - records.sort(function (f1,f2) { - return _.string.naturalCmp(f2.label, f1.label); - }); - - // add my profile - if (field.relation === 'res.partner' || field.relation === 'res.users') { - var value = field.relation === 'res.partner' ? session.partner_id : session.uid; - var me = _.find(records, function (record) { - return record.value === value; - }); - if (me) { - records.splice(records.indexOf(me), 1); - } else { - var f = _.find(filter.filters, function (f) {return f.value === value;}); - me = { - 'value': value, - 'label': session.name, - 'active': !f || f.active, - }; - } - records.unshift(me); - } - // add all selection - records.push({ - 'value': 'all', - 'label': field.relation === 'res.partner' || field.relation === 'res.users' ? _t("Everybody's calendars") : _t("Everything"), - 'active': filter.all, - }); - - filter.filters = records; - }); - }, - /** - * @private - * @param {any} element - * @param {any} events - * @returns {Promise} - */ - _loadRecordsToFilters: function (element, events) { - var self = this; - var new_filters = {}; - var to_read = {}; - var defs = []; - var color_filter = {}; - - _.each(this.data.filters, function (filter, fieldName) { - var field = self.fields[fieldName]; - - new_filters[fieldName] = filter; - if (filter.write_model) { - if (field.relation === self.model_color) { - _.each(filter.filters, function (f) { - f.color_index = self._getColorIndex(_.map(filter.filters, f => f.value), f.value); - }); - } - return; - } - - _.each(filter.filters, function (filter) { - filter.display = !filter.active; - }); - - var fs = []; - var undefined_fs = []; - _.each(events, function (event) { - var data = event.record[fieldName]; - if (!_.contains(['many2many', 'one2many'], field.type)) { - data = [data]; - } else { - to_read[field.relation] = (to_read[field.relation] || []).concat(data); - } - _.each(data, function (_value) { - var value = _.isArray(_value) ? _value[0] : _value; - var f = { - 'color_index': self.model_color === (field.relation || element.model) ? value : false, - 'value': value, - 'label': fieldUtils.format[field.type](_value, field) || _t("Undefined"), - 'avatar_model': field.relation || element.model, - }; - // if field used as color does not have value then push filter in undefined_fs, - // such filters should come last in filter list with Undefined string, later merge it with fs - value ? fs.push(f) : undefined_fs.push(f); - }); - }); - _.each(_.union(fs, undefined_fs), function (f) { - var f1 = _.findWhere(filter.filters, _.omit(f, 'color_index')); - if (f1) { - f1.display = true; - } else { - f.display = f.active = true; - filter.filters.push(f); - } - }); - - if (filter.color_model && filter.field_color) { - var ids = filter.filters.reduce((acc, f) => { - if (!f.color_index && f.value) { - acc.push(f.value); - } - return acc; - }, []); - if (!color_filter[filter.color_model]) { - color_filter[filter.color_model] = {}; - } - if (ids.length) { - defs.push(self._rpc({ - model: filter.color_model, - method: 'search_read', - args: [[['id', 'in', _.uniq(ids)]], [filter.field_color]], - }) - .then(function (res) { - _.each(res, function (c) { - color_filter[filter.color_model][c.id] = c[filter.field_color]; - }); - })); - } - } - }); - - _.each(to_read, function (ids, model) { - defs.push(self._rpc({ - model: model, - method: 'name_get', - args: [_.uniq(ids)], - }) - .then(function (res) { - to_read[model] = _.object(res); - })); - }); - return Promise.all(defs).then(function () { - _.each(self.data.filters, function (filter) { - if (filter.write_model) { - return; - } - if (filter.filters.length && (filter.filters[0].avatar_model in to_read)) { - _.each(filter.filters, function (f) { - f.label = to_read[f.avatar_model][f.value]; - }); - } - if (filter.color_model && filter.field_color) { - _.each(filter.filters, function (f) { - if (!f.color_index) { - f.color_index = color_filter[filter.color_model] && color_filter[filter.color_model][f.value]; - } - }); - } - }); - }); - }, - /** - * parse the server values to javascript framwork - * - * @private - * @param {Object} data the server data to parse - */ - _parseServerData: function (data) { - var self = this; - _.each(data, function(event) { - _.each(self.fieldNames, function (fieldName) { - event[fieldName] = self._parseServerValue(self.fields[fieldName], event[fieldName]); - }); - }); - }, - /** - * Transform OpenERP event object to fullcalendar event object - * - * @private - * @param {Object} evt - */ - _recordToCalendarEvent: function (evt) { - var date_start; - var date_stop; - var date_delay = evt[this.mapping.date_delay] || 1.0, - all_day = this.fields[this.mapping.date_start].type === 'date' || - this.mapping.all_day && evt[this.mapping.all_day] || false, - the_title = '', - attendees = []; - - if (!all_day) { - date_start = evt[this.mapping.date_start].clone(); - date_stop = this.mapping.date_stop ? evt[this.mapping.date_stop].clone() : null; - } else { - date_start = evt[this.mapping.date_start].clone().startOf('day'); - date_stop = this.mapping.date_stop ? evt[this.mapping.date_stop].clone().startOf('day') : null; - } - - if (!date_stop && date_delay) { - date_stop = date_start.clone().add(date_delay,'hours'); - } - - if (!all_day) { - date_start.add(this.getSession().getTZOffset(date_start), 'minutes'); - date_stop.add(this.getSession().getTZOffset(date_stop), 'minutes'); - } - - if (this.mapping.all_day && evt[this.mapping.all_day]) { - date_stop.add(1, 'days'); - } - var r = { - 'record': evt, - 'start': date_start.local(true).toDate(), - 'end': date_stop.local(true).toDate(), - 'r_start': date_start.clone().local(true).toDate(), - 'r_end': date_stop.clone().local(true).toDate(), - 'title': the_title, - 'allDay': all_day, - 'id': evt.id, - 'attendees':attendees, - }; - - if (!(this.mapping.all_day && evt[this.mapping.all_day]) && this.data.scale === 'month' && this.fields[this.mapping.date_start].type !== 'date') { - r.showTime = true; - } - - return r; - }, - _getCalendarEventData: function (events) { - return _.map(events, this._recordToCalendarEvent.bind(this)); - } -}); - -}); diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_popover.js b/addons/web/static/src/legacy/js/views/calendar/calendar_popover.js deleted file mode 100644 index 12dcb016cfe..00000000000 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_popover.js +++ /dev/null @@ -1,268 +0,0 @@ -odoo.define('web.CalendarPopover', function (require) { -"use strict"; - -var fieldRegistry = require('web.field_registry'); -const fieldRegistryOwl = require('web.field_registry_owl'); -const FieldWrapper = require('web.FieldWrapper'); -var StandaloneFieldManagerMixin = require('web.StandaloneFieldManagerMixin'); -var Widget = require('web.Widget'); -const { WidgetAdapterMixin } = require('web.OwlCompatibility'); - -var CalendarPopover = Widget.extend(WidgetAdapterMixin, StandaloneFieldManagerMixin, { - template: 'CalendarView.event.popover', - events: { - 'click .o_cw_popover_edit': '_onClickPopoverEdit', - 'click .o_cw_popover_delete': '_onClickPopoverDelete', - }, - /** - * @constructor - * @param {Widget} parent - * @param {Object} eventInfo - */ - init: function (parent, eventInfo) { - this._super.apply(this, arguments); - StandaloneFieldManagerMixin.init.call(this); - this.hideDate = eventInfo.hideDate; - this.hideTime = eventInfo.hideTime; - this.eventTime = eventInfo.eventTime; - this.eventDate = eventInfo.eventDate; - this.displayFields = eventInfo.displayFields; - this.fields = eventInfo.fields; - this.event = eventInfo.event; - this.modelName = eventInfo.modelName; - this._canDelete = eventInfo.canDelete; - this.popoverFields = eventInfo.popoverFields; - }, - /** - * @override - */ - willStart: function () { - return Promise.all([this._super.apply(this, arguments), this._processFields()]); - }, - /** - * @override - */ - start: function () { - var self = this; - _.each(this.$fieldsList, function ($field) { - $field.appendTo(self.$('.o_cw_popover_fields_secondary')); - }); - return this._super.apply(this, arguments); - }, - /** - * @override - */ - destroy: function () { - this._super.apply(this, arguments); - WidgetAdapterMixin.destroy.call(this); - }, - /** - * Called each time the widget is attached into the DOM. - */ - on_attach_callback: function () { - WidgetAdapterMixin.on_attach_callback.call(this); - }, - /** - * Called each time the widget is detached from the DOM. - */ - on_detach_callback: function () { - WidgetAdapterMixin.on_detach_callback.call(this); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @return {boolean} - */ - isEventDeletable() { - return this._canDelete;; - }, - /** - * @return {boolean} - */ - isEventDetailsVisible() { - return true; - }, - /** - * @return {boolean} - */ - isEventEditable() { - return true; - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Returns the AbstractField specialization that should be used for the - * given field informations. If there is no mentioned specific widget to - * use, determines one according the field type. - * - * @private - * @param {Object} field - * @param {Object} attrs - * @returns {function|null} AbstractField specialization Class - */ - _getFieldWidgetClass(field, attrs) { - let FieldWidget; - if (attrs.widget) { - FieldWidget = fieldRegistry.getAny(['form' + "." + attrs.widget, attrs.widget]); - if (!FieldWidget) { - console.warn("Missing widget: ", attrs.widget, " for field", attrs.name, "of type", field.type); - } - } - return FieldWidget || fieldRegistry.getAny(['form' + "." + field.type, field.type, "abstract"]); - }, - - /** - * Generate fields to render into popover - * - * @private - * @returns {Promise} - */ - _processFields: function () { - var self = this; - var fieldsToGenerate = []; - const fieldInformation = {}; - var fields = _.keys(this.popoverFields); - for (var i=0; i', {class: fieldClass}); - let $fieldLabel; - if (field.icon) { - $fieldLabel = $('', {class: 'me-2', html: _.str.sprintf("", field.icon)}); - } else { - $fieldLabel = $('', {class: 'me-2', text: _.str.sprintf('%s : ', field.string)}); - } - $fieldLabel.appendTo($field); - var $fieldContainer = $('
', {class: 'flex-grow-1'}); - $fieldContainer.appendTo($field); - - let def; - if (isLegacy) { - def = fieldWidget.appendTo($fieldContainer); - } else { - def = fieldWidget.mount($fieldContainer[0]); - } - self.$fieldsList.push($field); - defs.push(def); - }); - return Promise.all(defs); - }); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - * @param {jQueryEvent} ev - */ - _onClickPopoverEdit: function (ev) { - ev.preventDefault(); - this.trigger_up('edit_event', { - id: this.event.id, - title: this.event.extendedProps.record.display_name, - }); - }, - /** - * @private - * @param {jQueryEvent} ev - */ - _onClickPopoverDelete: function (ev) { - ev.preventDefault(); - this.trigger_up('delete_event', {id: this.event.id}); - }, -}); - -return CalendarPopover; - -}); diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_quick_create.js b/addons/web/static/src/legacy/js/views/calendar/calendar_quick_create.js deleted file mode 100644 index fba58a5e08d..00000000000 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_quick_create.js +++ /dev/null @@ -1,129 +0,0 @@ -odoo.define('web.CalendarQuickCreate', function (require) { -"use strict"; - -var core = require('web.core'); -var Dialog = require('web.Dialog'); - -var _t = core._t; -var QWeb = core.qweb; - -/** - * Quick creation view. - * - * Triggers a single event "added" with a single parameter "name", which is the - * name entered by the user - * - * @class - * @type {*} - */ -var QuickCreate = Dialog.extend({ - events: _.extend({}, Dialog.events, { - 'keyup input': '_onkeyup', - }), - - /** - * @constructor - * @param {Widget} parent - * @param {Object} buttons - * @param {Object} options - * @param {Object} dataTemplate - * @param {Object} dataCalendar - */ - init: function (parent, buttons, options, dataTemplate, dataCalendar) { - this._buttons = buttons || false; - this.options = options; - this.parentCalendar = parent; - // Can hold data pre-set from where you clicked on agenda - this.dataTemplate = dataTemplate || {}; - this.dataCalendar = dataCalendar; - - var self = this; - this._super(parent, { - title: options.title, - // Close the dialog when clicking outside of it - backdrop: true, - size: 'small', - buttons: this._buttons ? [ - {text: _t("Create"), classes: 'btn-primary', click: function () { - if (!self._quickAdd(dataCalendar)) { - self.focus(); - } - }}, - {text: _t("Edit"), click: function () { - dataCalendar.disableQuickCreate = true; - dataCalendar.title = self.$('input').val().trim(); - dataCalendar.on_save = self.destroy.bind(self); - self.trigger_up('openCreate', dataCalendar); - }}, - {text: _t("Cancel"), close: true}, - ] : [], - $content: QWeb.render('CalendarView.quick_create', {widget: this}) - }); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - focus: function () { - this.$('input').focus(); - }, - - destroy: function () { - // On quick create, remaining dialogs may be closed. We avoid to lose the selection for new quickcreate - // by checking that the dialog is not already destroyed. - if (!this.isDestroyed()) { - // remove selection when the dialog is closed - if (this.parentCalendar.renderer.state.scale === 'year') { - this.parentCalendar.renderer.calendar.view.unselect(); - } else { - this.parentCalendar.renderer.calendar.unselect(); - } - } - this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Gathers data from the quick create dialog a launch quick_create(data) method - */ - _quickAdd: function (dataCalendar) { - dataCalendar = $.extend({}, this.dataTemplate, dataCalendar); - var val = this.$('input').val().trim(); - if (!val) { - this.$('label, input').addClass('o_field_invalid'); - this.displayNotification({ title: _t("Invalid fields:"), message: _t("Meeting Subject"), type: 'danger' }); - } - dataCalendar.title = val; - return (val)? this.trigger_up('quickCreate', {data: dataCalendar, options: this.options}) : false; - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - * @param {keyEvent} event - */ - _onkeyup: function (event) { - if (this._flagEnter) { - return; - } - if(event.keyCode === $.ui.keyCode.ENTER) { - this._flagEnter = true; - if (!this._quickAdd(this.dataCalendar)){ - this._flagEnter = false; - } - } else if (event.keyCode === $.ui.keyCode.ESCAPE && this._buttons) { - this.close(); - } - }, -}); - -return QuickCreate; - -}); diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_renderer.js b/addons/web/static/src/legacy/js/views/calendar/calendar_renderer.js deleted file mode 100644 index 37fe3b66ef6..00000000000 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_renderer.js +++ /dev/null @@ -1,1089 +0,0 @@ -/* global FullCalendar */ -odoo.define('web.CalendarRenderer', function (require) { -"use strict"; - -var AbstractRenderer = require('web.AbstractRenderer'); -var CalendarPopover = require('web.CalendarPopover'); -var core = require('web.core'); -var field_utils = require('web.field_utils'); -var FieldManagerMixin = require('web.FieldManagerMixin'); -var relational_fields = require('web.relational_fields'); -var session = require('web.session'); -var Widget = require('web.Widget'); -const { createYearCalendarView } = require('/web/static/src/js/libs/fullcalendar.js'); - -var _t = core._t; -var qweb = core.qweb; - -var SidebarFilterM2O = relational_fields.FieldMany2One.extend({ - _getSearchBlacklist: function () { - return this._super.apply(this, arguments).concat(this.filter_ids || []); - }, -}); - -var SidebarFilter = Widget.extend(FieldManagerMixin, { - template: 'CalendarView.sidebar.filter', - custom_events: _.extend({}, FieldManagerMixin.custom_events, { - field_changed: '_onFieldChanged', - }), - /** - * @constructor - * @param {Widget} parent - * @param {Object} options - * @param {string} options.fieldName - * @param {Object[]} options.filters A filter is an object with the - * following keys: id, value, label, active, avatar_model, color, - * can_be_removed - * @param {Object} [options.favorite] this is an object with the following - * keys: fieldName, model, fieldModel - */ - init: function (parent, options) { - this._super.apply(this, arguments); - FieldManagerMixin.init.call(this); - - this.title = options.title; - this.fields = options.fields; - this.fieldName = options.fieldName; - this.write_model = options.write_model; - this.write_field = options.write_field; - this.filter_field = options.filter_field; - this.avatar_field = options.avatar_field; - this.avatar_model = options.avatar_model; - this.filters = options.filters; - this.label = options.label; - this.getColor = options.getColor; - this.filter_check_all = options.check_all; - }, - /** - * @override - */ - willStart: function () { - var self = this; - var defs = [this._super.apply(this, arguments)]; - - if (this.write_model || this.write_field) { - var def = this.model.makeRecord(this.write_model, [{ - name: this.write_field, - relation: this.fields[this.fieldName].relation, - type: 'many2one', - }]).then(function (recordID) { - self.many2one = new SidebarFilterM2O(self, - self.write_field, - self.model.get(recordID), - { - mode: 'edit', - attrs: { - string: _t(self.fields[self.fieldName].string), - placeholder: "+ " + _.str.sprintf(_t("Add %s"), self.title), - can_create: false - }, - }); - }); - defs.push(def); - } - return Promise.all(defs); - - }, - /** - * @override - */ - start: function () { - this._super(); - if (this.many2one) { - this.many2one.appendTo(this.$el); - this.many2one.filter_ids = _.without(_.pluck(this.filters, 'value'), 'all'); - } - this.$el.on('click', '.o_remove', this._onFilterRemove.bind(this)); - this.$el.on('click', '.o_calendar_filter_items input', this._onFilterActive.bind(this)); - this.$el.on('click', '.o_calendar_filter_items_checkall input', this._onFilterCheckAll.bind(this)); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - * @param {OdooEvent} event - */ - _onFieldChanged: function (event) { - var self = this; - event.stopPropagation(); - var createValues = {'user_id': session.uid}; - var value = event.data.changes[this.write_field].id; - createValues[this.write_field] = value; - this._rpc({ - model: this.write_model, - method: 'create', - args: [createValues], - }) - .then(function () { - self.trigger_up('changeFilter', { - 'fieldName': self.fieldName, - 'value': value, - 'active': true, - }); - }); - }, - /** - * @private - * @param {MouseEvent} e - */ - _onFilterActive: function (e) { - var $input = $(e.currentTarget); - this.filter_check_all[this.fieldName] = false; - this.trigger_up('changeFilter', { - 'fieldName': this.fieldName, - 'value': $input.closest('.o_calendar_filter_item').data('value'), - 'active': $input.prop('checked'), - }); - }, - /** - * @private - * @param {MouseEvent} e - */ - _onFilterRemove: function (e) { - var self = this; - var $filter = $(e.currentTarget).closest('.o_calendar_filter_item'); - this.filter_check_all[this.fieldName] = false; - self._rpc({ - model: self.write_model, - method: 'unlink', - args: [[$filter.data('id')]], - }) - .then(function () { - self.trigger_up('changeFilter', { - 'fieldName': self.fieldName, - 'id': $filter.data('id'), - 'active': false, - 'value': $filter.data('value'), - }); - }); - }, - /** - * @private - * When the user check the box near the filter title: all existing filters - * are active, excepts the 'all' filter that removes the domain on active filters. - * @param {MouseEvent} e - */ - _onFilterCheckAll: function (e) { - let $input = $(e.currentTarget); - this.filter_check_all[this.fieldName] = $input.prop('checked'); - this.trigger_up('changeFilter', { - 'fieldName': this.fieldName, - 'value': 'check_all', - 'active': $input.prop('checked'), - }); - - }, -}); - -return AbstractRenderer.extend({ - template: "CalendarView", - config: { - CalendarPopover: CalendarPopover, - eventTemplate: 'calendar-box', - }, - custom_events: _.extend({}, AbstractRenderer.prototype.custom_events || {}, { - edit_event: '_onEditEvent', - delete_event: '_onDeleteEvent', - render_event: '_renderEvents', - }), - - /** - * @constructor - * @param {Widget} parent - * @param {Object} state - * @param {Object} params - */ - init: function (parent, state, params) { - this._super.apply(this, arguments); - this.displayFields = params.displayFields; - this.popoverFields = params.popoverFields; - this.model = params.model; - this.filters = []; - this.color_map = {}; - this.hideDate = params.hideDate; - this.hideTime = params.hideTime; - this.canDelete = params.canDelete; - this.canCreate = params.canCreate; - this.scalesInfo = params.scalesInfo; - this._isInDOM = false; - }, - /** - * @override - * @returns {Promise} - */ - start: function () { - this._initSidebar(); - this._initCalendar(); - return this._super(); - }, - /** - * @override - */ - on_attach_callback: function () { - this._super(...arguments); - this._isInDOM = true; - // BUG Test ???? - // this.$el.height($(window).height() - this.$el.offset().top); - this.calendar.render(); - this._renderCalendar(); - window.addEventListener('click', this._onWindowClick.bind(this)); - }, - /** - * Called when the field is detached from the DOM. - */ - on_detach_callback: function () { - this._super(...arguments); - this._isInDOM = false; - window.removeEventListener('click', this._onWindowClick); - }, - /** - * @override - */ - destroy: function () { - if (this.calendar) { - this.calendar.destroy(); - } - if (this.$small_calendar) { - this.$small_calendar.datepicker('destroy'); - $('#ui-datepicker-div:empty').remove(); - } - this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Note: this is not dead code, it is called by the calendar-box template - * - * @param {any} record - * @param {any} fieldName - * @param {any} imageField - * @returns {string[]} - */ - getAvatars: function (record, fieldName, imageField) { - var field = this.state.fields[fieldName]; - - if (!record[fieldName]) { - return []; - } - if (field.type === 'one2many' || field.type === 'many2many') { - return _.map(record[fieldName], function (id) { - return ''; - }); - } else if (field.type === 'many2one') { - return ['']; - } else { - var value = this._format(record, fieldName); - var color = this.getColor(value); - if (isNaN(color)) { - return ['']; - } - else { - return ['']; - } - } - }, - /** - * Note: this is not dead code, it is called by two template - * - * @param {any} key - * @returns {integer} - */ - getColor: function (key) { - if (!key) { - return; - } - if (this.color_map[key]) { - return this.color_map[key]; - } - // check if the key is a css color - if (typeof key === 'string' && key.match(/^((#[A-F0-9]{3})|(#[A-F0-9]{6})|((hsl|rgb)a?\(\s*(?:(\s*\d{1,3}%?\s*),?){3}(\s*,[0-9.]{1,4})?\))|)$/i)) { - return this.color_map[key] = key; - } - if (typeof key === 'number' && !(key in this.color_map)) { - return this.color_map[key] = key; - } - var index = (((_.keys(this.color_map).length + 1) * 5) % 24) + 1; - this.color_map[key] = index; - return index; - }, - /** - * @override - */ - getLocalState: function () { - var fcScroller = this.calendarElement.querySelector('.fc-scroller'); - return { - scrollPosition: fcScroller.scrollTop, - }; - }, - /** - * @override - */ - setLocalState: function (localState) { - if (localState.scrollPosition) { - var fcScroller = this.calendarElement.querySelector('.fc-scroller'); - fcScroller.scrollTop = localState.scrollPosition; - } - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Convert the new format of Event from FullCalendar V4 to a Event FullCalendar V3 - * @param fc4Event - * @return {Object} FullCalendar V3 Object Event - * @private - */ - _convertEventToFC3Event: function (fc4Event) { - var event = fc4Event; - if (!moment.isMoment(fc4Event.start)) { - event = { - id: fc4Event.id, - title: fc4Event.title, - start: moment(fc4Event.start).utcOffset(0, true), - end: fc4Event.end && moment(fc4Event.end).utcOffset(0, true), - allDay: fc4Event.allDay, - color: fc4Event.color, - }; - if (fc4Event.extendedProps) { - event = Object.assign({}, event, { - r_start: fc4Event.extendedProps.r_start && moment(fc4Event.extendedProps.r_start).utcOffset(0, true), - r_end: fc4Event.extendedProps.r_end && moment(fc4Event.extendedProps.r_end).utcOffset(0, true), - record: fc4Event.extendedProps.record, - attendees: fc4Event.extendedProps.attendees, - disableQuickCreate: fc4Event.extendedProps.disableQuickCreate, - }); - } - } - return event; - }, - /** - * @param {any} event - * @returns {string} the html for the rendered event - */ - _eventRender: function (event) { - var qweb_context = { - event: event, - record: event.extendedProps.record, - color: this.getColor(event.extendedProps.color_index), - showTime: !this.hideTime && event.extendedProps.showTime, - showLocation: this.state.scale !== 'month' - }; - this.qweb_context = qweb_context; - if (_.isEmpty(qweb_context.record)) { - return ''; - } else { - return qweb.render(this.config.eventTemplate, qweb_context); - } - }, - /** - * @private - * @param {any} record - * @param {any} fieldName - * @returns {string} - */ - _format: function (record, fieldName) { - var field = this.state.fields[fieldName]; - if (field.type === "one2many" || field.type === "many2many") { - return field_utils.format[field.type]({data: record[fieldName]}, field); - } else { - return field_utils.format[field.type](record[fieldName], field, {forceString: true}); - } - }, - _preOpenCreate: function (data) { - if (this.$('.o_cw_popover').length) { - this._unselectEvent(); - // Don't take into account the selection if the user closed a popover by doing a "selection" - this.$('.fc-event').not(".o_event").remove(); - // Clicking anywhere in the interface will only close the popover but we don't want the quickCreate - return; - } - if (this.state.context.default_name) { - data.title = this.state.context.default_name; - } - this.trigger_up('openCreate', { - ...this._convertEventToFC3Event(data), - on_close: () => { - if (this.state.scale === 'year') { - this.calendar.view.unselect(); - } else { - this.calendar.unselect(); - } - } - }); - }, - /** - * Return the Object options for FullCalendar - * - * @private - * @param {Object} fcOptions - * @return {Object} - */ - _getFullCalendarOptions: function (fcOptions) { - var self = this; - const options = Object.assign({}, this.state.fc_options, { - plugins: [ - 'moment', - 'interaction', - 'dayGrid', - 'timeGrid' - ], - eventDrop: function (eventDropInfo) { - var event = self._convertEventToFC3Event(eventDropInfo.event); - self.trigger_up('dropRecord', event); - }, - eventResize: function (eventResizeInfo) { - self._unselectEvent(); - var event = self._convertEventToFC3Event(eventResizeInfo.event); - self.trigger_up('updateRecord', event); - }, - eventClick: function (eventClickInfo) { - eventClickInfo.jsEvent.preventDefault(); - eventClickInfo.jsEvent.stopPropagation(); - var eventData = eventClickInfo.event; - self._unselectEvent(); - $(self.calendarElement).find(self._computeEventSelector(eventClickInfo)).addClass('o_cw_custom_highlight'); - self._renderEventPopover(eventData, $(eventClickInfo.el)); - }, - selectAllow: function (event) { - if (event.end.getDate() === event.start.getDate() || event.allDay) { - return true; - } - }, - yearDateClick: function (info) { - self._unselectEvent(); - info.view.unselect(); - if (!info.events.length) { - if (info.selectable) { - const data = { - start: info.date, - allDay: true, - }; - if (self.state.context.default_name) { - data.title = self.state.context.default_name; - } - self.trigger_up('openCreate', self._convertEventToFC3Event(data)); - } - } else { - self._renderYearEventPopover(info.date, info.events, $(info.dayEl)); - } - }, - select: function (selectionInfo) { - var data = {start: selectionInfo.start, end: selectionInfo.end, allDay: selectionInfo.allDay}; - self._preOpenCreate(data); - }, - eventRender: function (info) { - var event = info.event; - var element = $(info.el); - var view = info.view; - self._addEventAttributes(element, event); - if (view.type === 'dayGridYear') { - const color = this.getColor(event.extendedProps.color_index); - if (typeof color === 'string') { - element.css({ - backgroundColor: color, - }); - } else if (typeof color === 'number') { - element.addClass(`o_calendar_color_${color}`); - } else { - element.addClass('o_calendar_color_1'); - } - } else { - var $render = $(self._eventRender(event)); - element.find('.fc-content').html($render.html()); - element.addClass($render.attr('class')); - - // Add background if doesn't exist - if (!element.find('.fc-bg').length) { - element.find('.fc-content').after($('
', {class: 'fc-bg'})); - } - - if (view.type === 'dayGridMonth' && event.extendedProps.record) { - var start = event.extendedProps.r_start || event.start; - var end = event.extendedProps.r_end || event.end; - $(this.el).find(_.str.sprintf('.fc-day[data-date="%s"]', moment(start).format('YYYY-MM-DD'))).addClass('fc-has-event'); - // Detect if the event occurs in just one day - // note: add & remove 1 min to avoid issues with 00:00 - var isSameDayEvent = moment(start).clone().add(1, 'minute').isSame(moment(end).clone().subtract(1, 'minute'), 'day'); - if (!event.extendedProps.record.allday && isSameDayEvent) { - // For month view: do not show background for non allday, single day events - element.addClass('o_cw_nobg'); - if (event.extendedProps.showTime && !self.hideTime) { - const displayTime = moment(start).clone().format(self._getDbTimeFormat()); - element.find('.fc-content .fc-time').text(displayTime); - } - } - } - - // On double click, edit the event - element.on('dblclick', function () { - self.trigger_up('edit_event', { - id: event.id, - title: event.extendedProps.record.display_name - }); - }); - } - }, - datesRender: function (info) { - const viewToMode = Object.fromEntries( - Object.entries(self.scalesInfo).map(([k, v]) => [v, k]) - ); - self.trigger_up('viewUpdated', { - mode: viewToMode[info.view.type], - title: info.view.title, - }); - }, - // Add/Remove a class on hover to style multiple days events. - // The css ":hover" selector can't be used because these events - // are rendered using multiple elements. - eventMouseEnter: function (mouseEnterInfo) { - $(self.calendarElement).find(self._computeEventSelector(mouseEnterInfo)).addClass('o_cw_custom_hover'); - }, - eventMouseLeave: function (mouseLeaveInfo) { - if (!mouseLeaveInfo.event.id) { - return; - } - $(self.calendarElement).find(self._computeEventSelector(mouseLeaveInfo)).removeClass('o_cw_custom_hover'); - }, - eventDragStart: function (mouseDragInfo) { - mouseDragInfo.el.classList.add(mouseDragInfo.view.type); - $(self.calendarElement).find(`[data-event-id=${mouseDragInfo.event.id}]`).addClass('o_cw_custom_hover'); - self._unselectEvent(); - }, - eventResizeStart: function (mouseResizeInfo) { - $(self.calendarElement).find(`[data-event-id=${mouseResizeInfo.event.id}]`).addClass('o_cw_custom_hover'); - self._unselectEvent(); - }, - eventLimitClick: function () { - self._unselectEvent(); - return 'popover'; - }, - windowResize: function () { - self._onWindowResize(); - }, - views: { - timeGridDay: { - columnHeaderFormat: 'LL' - }, - timeGridWeek: { - columnHeaderFormat: 'ddd D' - }, - dayGridMonth: { - columnHeaderFormat: 'dddd' - }, - dayGridYear: { - weekNumbers: false - } - }, - height: 'parent', - unselectAuto: false, - // prevent too small events - timeGridEventMinHeight: 15, - dir: _t.database.parameters.direction, - events: (info, successCB) => { - successCB(self.state.data); - }, - }, fcOptions); - options.plugins.push(createYearCalendarView(FullCalendar, options)); - return options; - }, - /** - * Compute the selector to select the correct event we are currently on - * @param {jQueryElement} info - * @returns {String} - */ - _computeEventSelector: function (info) { - return `[data-event-id=${info.event.id}]`; - }, - /** - * Add attribute to the element - * @param {jQueryElement} element - * @param {Object} event - */ - _addEventAttributes: function (element, event) { - element.attr('data-event-id', event.id); - }, - /** - * Initialize the main calendar - * - * @private - */ - _initCalendar: function () { - this.calendarElement = this.$(".o_calendar_widget")[0]; - var locale = moment.locale(); - - var fcOptions = this._getFullCalendarOptions({ - locale: locale, // reset locale when fullcalendar has already been instanciated before now - }); - - this.calendar = new FullCalendar.Calendar(this.calendarElement, fcOptions); - }, - /** - * Initialize the mini calendar in the sidebar - * - * @private - */ - _initCalendarMini: function () { - var self = this; - this.$small_calendar = this.$(".o_calendar_mini"); - this.$small_calendar.datepicker({ - 'onSelect': function (datum, obj) { - self.trigger_up('changeDate', { - date: moment(new Date(+obj.currentYear , +obj.currentMonth, +obj.currentDay)) - }); - }, - 'showOtherMonths': true, - 'dayNamesMin': this.state.fc_options.dayNamesMin.map(x => x[0]), - 'monthNames': this.state.fc_options.monthNamesShort, - 'firstDay': this.state.fc_options.firstDay, - }); - }, - /** - * Initialize the sidebar - * - * @private - */ - _initSidebar: function () { - this.$sidebar = this.$('.o_calendar_sidebar'); - this.$calendarSyncContainer = this.$('#calendar_sync'); - this.$sidebar_container = this.$(".o_calendar_sidebar_container"); - this._initCalendarMini(); - }, - /** - * Finalise the popover - * - * @param {jQueryElement} $popoverElement - * @param {web.CalendarPopover} calendarPopover - * @private - */ - _onPopoverShown: function ($popoverElement, calendarPopover) { - const $popover = $(Popover.getInstance($popoverElement[0]).tip); - $popover.find('.o_cw_popover_close').on('click', this._unselectEvent.bind(this)); - $popover.find('.o_cw_body').replaceWith(calendarPopover.$el); - }, - /** - * Render the calendar view, this is the main entry point. - * - * @override - */ - async _renderView() { - this.$('.o_legacy_calendar_view')[0].prepend(this.calendarElement); - if (this._isInDOM) { - this._renderCalendar(); - } - this.$small_calendar.datepicker("setDate", this.state.highlight_date.toDate()) - .find('.o_selected_range') - .removeClass('o_color o_selected_range'); - var $a; - switch (this.state.scale) { - case 'year': $a = this.$small_calendar.find('td'); break; - case 'month': $a = this.$small_calendar.find('td'); break; - case 'week': $a = this.$small_calendar.find('tr:has(.ui-state-active)'); break; - case 'day': $a = this.$small_calendar.find('a.ui-state-active'); break; - } - $a.addClass('o_selected_range'); - setTimeout(function () { - $a.not('.ui-state-active').addClass('o_color'); - }); - - await this._renderFilters(); - }, - /** - * Render the specific code for the FullCalendar when it's in the DOM - * - * @private - */ - _renderCalendar() { - this.calendar.unselect(); - - if (this.scalesInfo[this.state.scale] !== this.calendar.view.type) { - this.calendar.changeView(this.scalesInfo[this.state.scale]); - } - - if (this.target_date !== this.state.target_date.toString()) { - this.calendar.gotoDate(moment(this.state.target_date).toDate()); - this.target_date = this.state.target_date.toString(); - } else { - // this.calendar.gotoDate already renders events when called - // so render events only when domain changes - this._renderEvents(); - } - - this._unselectEvent(); - // this._scrollToScrollTime(); - }, - /** - * Render all events - * - * @private - */ - _renderEvents: function () { - this.calendar.refetchEvents(); - }, - /** - * Render all filters - * - * @private - * @returns {Promise} resolved when all filters have been rendered - */ - _renderFilters: function () { - // Dispose of filter popover - this.$('.o_calendar_filter_item').popover('dispose'); - _.each(this.filters || (this.filters = []), function (filter) { - filter.destroy(); - }); - if (this.state.fullWidth) { - return Promise.resolve(); - } - return this._renderFiltersOneByOne(); - }, - /** - * Renders each filter one by one, waiting for the first filter finished to - * be rendered and appended to render the next one. - * We need to do like this since render a filter is asynchronous, we don't - * know which one will be appened at first and we want tp force them to be - * rendered in order. - * - * @param {number} filterIndex if not set, 0 by default - * @returns {Promise} resolved when all filters have been rendered - */ - _renderFiltersOneByOne: function (filterIndex) { - filterIndex = filterIndex || 0; - var arrFilters = _.toArray(this.state.filters); - var prom; - if (filterIndex < arrFilters.length) { - var options = arrFilters[filterIndex]; - if (!_.find(options.filters, function (f) {return f.display == null || f.display;})) { - return this._renderFiltersOneByOne(filterIndex + 1); - } - - var self = this; - options.getColor = this.getColor.bind(this); - options.fields = this.state.fields; - var sidebarFilter = new SidebarFilter(self, options); - prom = sidebarFilter.appendTo(this.$sidebar).then(function () { - // Show filter popover - if (options.avatar_field) { - _.each(options.filters, function (filter) { - if (!['all', false].includes(filter.value)) { - var selector = `.o_calendar_filter_item[data-value=${filter.value}]`; - sidebarFilter.$el.find(selector).popover({ - animation: false, - trigger: 'hover', - html: true, - placement: 'top', - title: filter.label, - delay: {show: 300, hide: 0}, - content: function () { - return $('', { - src: `/web/image/${options.avatar_model}/${filter.value}/${options.avatar_field}`, - class: 'mx-auto', - }); - }, - }); - } - }); - } - return self._renderFiltersOneByOne(filterIndex + 1); - }); - this.filters.push(sidebarFilter); - } - return Promise.resolve(prom); - }, - /** - * Returns the time format from database parameters (only hours and minutes). - * FIXME: this looks like a weak heuristic... - * - * @private - * @returns {string} - */ - _getDbTimeFormat: function () { - return _t.database.parameters.time_format.search('%H') !== -1 ? 'HH:mm' : 'hh:mm a'; - }, - /** - * Returns event's formatted date for popovers. - * - * @private - * @param {moment} start - * @param {moment} end - * @param {boolean} showDayName - * @param {boolean} allDay - */ - _getFormattedDate: function (start, end, showDayName, allDay) { - const isSameDayEvent = start.clone().add(1, 'minute') - .isSame(end.clone().subtract(1, 'minute'), 'day'); - if (allDay) { - // cancel correction done in _recordToCalendarEvent - end = end.clone().subtract(1, 'day'); - } - if (!isSameDayEvent && start.isSame(end, 'month')) { - // Simplify date-range if an event occurs into the same month (eg. '4-5 August 2019') - return start.clone().format('MMMM D') + '-' + end.clone().format('D, YYYY'); - } else { - return isSameDayEvent ? - start.clone().format(showDayName ? 'dddd, LL' : 'LL') : - start.clone().format('LL') + ' - ' + end.clone().format('LL'); - } - }, - /** - * Prepare context to display in the popover. - * - * @private - * @param {Object} eventData - * @returns {Object} context - */ - _getPopoverContext: function (eventData) { - var context = { - hideDate: this.hideDate, - hideTime: this.hideTime, - eventTime: {}, - eventDate: {}, - fields: this.state.fields, - displayFields: this.displayFields, - event: eventData, - modelName: this.model, - canDelete: this.canDelete, - popoverFields: this.popoverFields, - }; - - var start = moment((eventData.extendedProps && eventData.extendedProps.r_start) || eventData.start); - var end = moment((eventData.extendedProps && eventData.extendedProps.r_end) || eventData.end); - var isSameDayEvent = start.clone().add(1, 'minute').isSame(end.clone().subtract(1, 'minute'), 'day'); - - // Do not display timing if the event occur across multiple days. Otherwise use user's timing preferences - if (!this.hideTime && !eventData.extendedProps.record.allday && isSameDayEvent) { - var dbTimeFormat = this._getDbTimeFormat(); - - context.eventTime.time = start.clone().format(dbTimeFormat) + ' - ' + end.clone().format(dbTimeFormat); - - // Calculate duration and format text - var durationHours = moment.duration(end.diff(start)).hours(); - var durationHoursKey = (durationHours === 1) ? 'h' : 'hh'; - var durationMinutes = moment.duration(end.diff(start)).minutes(); - var durationMinutesKey = (durationMinutes === 1) ? 'm' : 'mm'; - - var localeData = moment.localeData(); // i18n for 'hours' and "minutes" strings - context.eventTime.duration = (durationHours > 0 ? localeData.relativeTime(durationHours, true, durationHoursKey) : '') - + (durationHours > 0 && durationMinutes > 0 ? ', ' : '') - + (durationMinutes > 0 ? localeData.relativeTime(durationMinutes, true, durationMinutesKey) : ''); - } - - if (!this.hideDate) { - - if (eventData.extendedProps.record.allday && isSameDayEvent) { - context.eventDate.duration = _t("All day"); - } else if (eventData.extendedProps.record.allday && !isSameDayEvent) { - var daysLocaleData = moment.localeData(); - var days = moment.duration(end.diff(start)).days(); - context.eventDate.duration = daysLocaleData.relativeTime(days, true, 'dd'); - } - - context.eventDate.date = this._getFormattedDate(start, end, true, eventData.extendedProps.record.allday); - } - - return context; - }, - /** - * Prepare the parameters for the popover. - * This allow the parameters to be extensible. - * - * @private - * @param {Object} eventData - */ - _getPopoverParams: function (eventData) { - // Display a small lock for private events that we manage - const displayLock = eventData.extendedProps.record.privacy === 'private' && eventData.extendedProps.record.partner_ids.includes(session.partner_id); - let title = eventData.extendedProps.record.display_name; - if (title.length > 30) { - title = title.substring(0, 30) + "…"; - } - return { - animation: false, - delay: { - show: 50, - hide: 100 - }, - trigger: 'manual', - html: true, - title: title, - template: qweb.render('CalendarView.event.popover.placeholder', - {color: this.getColor(eventData.extendedProps.color_index), displayLock: displayLock}), - container: eventData.allDay ? '.fc-view' : '.fc-scroller', - } - }, - /** - * Render event popover - * - * @private - * @param {Object} eventData - * @param {jQueryElement} $eventElement - */ - _renderEventPopover: function (eventData, $eventElement) { - var self = this; - - // Initialize popover widget - var calendarPopover = new self.config.CalendarPopover(self, self._getPopoverContext(eventData)); - calendarPopover.appendTo($('
')).then(() => { - $eventElement.popover( - self._getPopoverParams(eventData) - ).on('shown.bs.popover', function () { - self._onPopoverShown($(this), calendarPopover); - }).popover('show'); - }); - }, - /** - * Render year event popover - * - * @private - * @param {Date} date - * @param {Object[]} events - * @param {jQueryElement} $el - */ - _renderYearEventPopover: function (date, events, $el) { - const groupKeys = []; - const groupedEvents = {}; - for (const event of events) { - const start = moment(event.extendedProps.r_start); - const end = moment(event.extendedProps.r_end); - const duration = moment.duration(end.diff(start)).asDays(); - event.extendedProps.record.start_hour = !event.extendedProps.record.allday && duration < 1 ? start.format("HH:mm") : ""; - const key = this._getFormattedDate(start, end, false, event.extendedProps.record.allday); - if (!(key in groupedEvents)) { - groupedEvents[key] = []; - groupKeys.push({ - key: key, - start: event.start, - end: event.end, - isSameDayEvent: start.clone().add(1, 'minute') - .isSame(end.clone().subtract(1, 'minute'), 'day'), - }); - } - groupedEvents[key].push(event); - } - - const popoverContent = qweb.render('CalendarView.yearEvent.popover', { - groupedEvents, - groupKeys: groupKeys - .sort((a, b) => { - if (a.isSameDayEvent) { - // if isSameDayEvent then put it before the others - return Number.MIN_SAFE_INTEGER; - } else if (b.isSameDayEvent) { - return Number.MAX_SAFE_INTEGER; - } else if (a.start.getTime() - b.start.getTime() === 0) { - return a.end.getTime() - b.end.getTime(); - } - return a.start.getTime() - b.start.getTime(); - }) - .map(x => x.key), - canCreate: this.canCreate, - }); - - $el.popover({ - animation: false, - delay: { - show: 50, - hide: 100 - }, - trigger: 'manual', - html: true, - content: popoverContent, - template: qweb.render('CalendarView.yearEvent.popover.placeholder'), - container: '.fc-dayGridYear-view', - }).on('shown.bs.popover', () => { - $('.o_cw_popover .o_cw_popover_close').on('click', () => this._unselectEvent()); - $('.o_cw_popover .o_cw_popover_create').on('click', (e) => { - e.preventDefault(); - e.stopPropagation(); - this._unselectEvent(); - const data = { - start: date, - allDay: true, - }; - if (this.state.context.default_name) { - data.title = this.state.context.default_name; - } - this.trigger_up('openCreate', this._convertEventToFC3Event(data)); - }); - $('.o_cw_popover .o_cw_popover_link').on('click', (e) => { - e.preventDefault(); - e.stopPropagation(); - this._unselectEvent(); - this.trigger_up('openEvent', { - _id: parseInt(e.target.dataset.id, 10), - title: e.target.dataset.title, - }); - }); - }).popover('show'); - }, - /** - * Scroll to the time set in the FullCalendar parameter - * @private - */ - _scrollToScrollTime: function () { - var scrollTime = this.calendar.getOption('scrollTime'); - this.calendar.scrollToTime(scrollTime); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Remove highlight classes and dispose of popovers - * - * @private - */ - _unselectEvent: function () { - this.$('.fc-event').removeClass('o_cw_custom_highlight'); - this.$('.o_cw_popover').popover('dispose'); - }, - /** - * @private - * @param {MouseEvent} e - */ - _onWindowClick: function (e) { - const popover = this.el.querySelector('.o_cw_popover'); - if (popover && !popover.contains(e.target)) { - this._unselectEvent(); - } - }, - /** - * Handler for resize event. - * As the result it will resize the calendar to fit the window space. - * - * @private - */ - _onWindowResize: function () { - this.$el.height($(window).height() - this.$el.offset().top); - this.calendar.updateSize(); - }, - /** - * @private - * @param {OdooEvent} event - */ - _onEditEvent: function (event) { - this._unselectEvent(); - this.trigger_up('openEvent', { - _id: event.data.id, - title: event.data.title, - }); - }, - /** - * @private - * @param {OdooEvent} event - */ - _onDeleteEvent: function (event) { - this._unselectEvent(); - this.trigger_up('deleteRecord', {id: parseInt(event.data.id, 10), event: event.target.event.extendedProps}); - }, -}); - -}); diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_view.js b/addons/web/static/src/legacy/js/views/calendar/calendar_view.js deleted file mode 100644 index 9fc608456e3..00000000000 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_view.js +++ /dev/null @@ -1,216 +0,0 @@ -odoo.define('web.CalendarView', function (require) { -"use strict"; - -var AbstractView = require('web.AbstractView'); -var CalendarModel = require('web.CalendarModel'); -var CalendarController = require('web.CalendarController'); -var CalendarRenderer = require('web.CalendarRenderer'); -var core = require('web.core'); -var pyUtils = require('web.py_utils'); -var utils = require('web.utils'); - -var _lt = core._lt; - -// gather the fields to get -var fieldsToGather = [ - "date_start", - "date_delay", - "date_stop", - "all_day", - "recurrence_update", - "create_name_field", -]; - -const scalesInfo = { - day: 'timeGridDay', - week: 'timeGridWeek', - month: 'dayGridMonth', - year: 'dayGridYear', -}; - -var CalendarView = AbstractView.extend({ - display_name: _lt('Calendar'), - icon: 'fa fa-calendar', - jsLibs: [ - '/web/static/lib/fullcalendar/core/main.js', - '/web/static/lib/fullcalendar/interaction/main.js', - '/web/static/lib/fullcalendar/moment/main.js', - '/web/static/lib/fullcalendar/daygrid/main.js', - '/web/static/lib/fullcalendar/timegrid/main.js', - '/web/static/lib/fullcalendar/list/main.js' - ], - cssLibs: [ - '/web/static/lib/fullcalendar/core/main.css', - '/web/static/lib/fullcalendar/daygrid/main.css', - '/web/static/lib/fullcalendar/timegrid/main.css', - '/web/static/lib/fullcalendar/list/main.css' - ], - config: _.extend({}, AbstractView.prototype.config, { - Model: CalendarModel, - Controller: CalendarController, - Renderer: CalendarRenderer, - }), - viewType: 'calendar', - searchMenuTypes: ['filter', 'favorite'], - - /** - * @override - */ - init: function (viewInfo, params) { - this._super.apply(this, arguments); - var arch = this.arch; - var fields = this.fields; - var attrs = arch.attrs; - - if (!attrs.date_start) { - throw new Error(_lt("Calendar view has not defined 'date_start' attribute.")); - } - - var mapping = {}; - var fieldNames = fields.display_name ? ['display_name'] : []; - var displayFields = {}; - let popoverFields = {}; - - _.each(fieldsToGather, function (field) { - if (arch.attrs[field]) { - var fieldName = attrs[field]; - mapping[field] = fieldName; - fieldNames.push(fieldName); - } - }); - - var filters = {}; - - var eventLimit = attrs.event_limit !== null && (isNaN(+attrs.event_limit) ? _.str.toBool(attrs.event_limit) : +attrs.event_limit); - - var modelFilters = []; - _.each(arch.children, function (child) { - if (child.tag !== 'field') return; - var fieldName = child.attrs.name; - fieldNames.push(fieldName); - popoverFields[fieldName] = {attrs: child.attrs}; - if (!child.attrs.invisible || child.attrs.filters) { - child.attrs.options = child.attrs.options ? pyUtils.py_eval(child.attrs.options) : {}; - if (!child.attrs.invisible) { - displayFields[fieldName] = {attrs: child.attrs}; - } - - if (params.sidebar === false) return; // if we have not sidebar, (eg: Dashboard), we don't use the filter "coworkers" - - if (child.attrs.avatar_field) { - filters[fieldName] = filters[fieldName] || { - 'title': fields[fieldName].string, - 'fieldName': fieldName, - 'filters': [], - 'check_all': {}, - }; - filters[fieldName].avatar_field = child.attrs.avatar_field; - filters[fieldName].avatar_model = fields[fieldName].relation; - } - if (child.attrs.write_model) { - filters[fieldName] = filters[fieldName] || { - 'title': fields[fieldName].string, - 'fieldName': fieldName, - 'filters': [], - 'check_all': {}, - }; - filters[fieldName].write_model = child.attrs.write_model; - filters[fieldName].write_field = child.attrs.write_field; // can't use a x2many fields - filters[fieldName].filter_field = child.attrs.filter_field; - - modelFilters.push(fields[fieldName].relation); - } - if (child.attrs.filters) { - filters[fieldName] = filters[fieldName] || { - 'title': fields[fieldName].string, - 'fieldName': fieldName, - 'check_all': {}, - 'filters': [], - }; - if (child.attrs.color) { - filters[fieldName].field_color = child.attrs.color; - filters[fieldName].color_model = fields[fieldName].relation; - } - if (!child.attrs.avatar_field && fields[fieldName].relation) { - if (fields[fieldName].relation.includes(['res.users', 'res.partner', 'hr.employee'])) { - filters[fieldName].avatar_field = 'avatar_128'; - } - filters[fieldName].avatar_model = fields[fieldName].relation; - } - } - } - }); - - if (attrs.color) { - var fieldName = attrs.color; - fieldNames.push(fieldName); - } - - //if quick_add = False, we don't allow quick_add - //if quick_add = not specified in view, we use the default widgets.QuickCreate - //if quick_add = is NOT False and IS specified in view, we this one for widgets.QuickCreate' - this.controllerParams.quickAddPop = (!('quick_add' in attrs) || utils.toBoolElse(attrs.quick_add+'', true)); - this.controllerParams.disableQuickCreate = params.disable_quick_create || !this.controllerParams.quickAddPop; - - // If form_view_id is set, then the calendar view will open a form view - // with this id, when it needs to edit or create an event. - this.controllerParams.formViewId = - attrs.form_view_id ? parseInt(attrs.form_view_id, 10) : false; - if (!this.controllerParams.formViewId && params.action) { - var formViewDescr = _.find(params.action.views, function (v) { - return v.type === 'form'; - }); - if (formViewDescr) { - this.controllerParams.formViewId = formViewDescr.viewID; - } - } - - let scales; - const allowedScales = Object.keys(scalesInfo); - if (arch.attrs.scales) { - scales = arch.attrs.scales.split(',') - .filter(x => allowedScales.includes(x)); - } else { - scales = allowedScales; - } - - this.controllerParams.eventOpenPopup = utils.toBoolElse(attrs.event_open_popup || '', false); - this.controllerParams.showUnusualDays = utils.toBoolElse(attrs.show_unusual_days || '', false); - this.controllerParams.mapping = mapping; - this.controllerParams.context = params.context || {}; - this.controllerParams.displayName = params.displayName; - this.controllerParams.scales = scales; - - this.rendererParams.displayFields = displayFields; - this.rendererParams.popoverFields = popoverFields; - this.rendererParams.model = viewInfo.model; - this.rendererParams.hideDate = utils.toBoolElse(attrs.hide_date || '', false); - this.rendererParams.hideTime = utils.toBoolElse(attrs.hide_time || '', false); - this.rendererParams.canDelete = this.controllerParams.activeActions.delete; - this.rendererParams.canCreate = this.controllerParams.activeActions.create; - this.rendererParams.scalesInfo = scalesInfo; - - this.loadParams.fieldNames = _.uniq(fieldNames); - this.loadParams.mapping = mapping; - this.loadParams.fields = fields; - this.loadParams.fieldsInfo = viewInfo.fieldsInfo; - this.loadParams.editable = !fields[mapping.date_start].readonly; - this.loadParams.creatable = this.controllerParams.activeActions.create; - this.loadParams.eventLimit = eventLimit; - this.loadParams.fieldColor = attrs.color; - - this.loadParams.filters = filters; - this.loadParams.mode = (params.context && params.context.default_mode) || attrs.mode; - this.loadParams.scales = scales; - this.loadParams.initialDate = moment( - (params.context && params.context.initial_date) || - params.initialDate || - new Date() - ); - this.loadParams.scalesInfo = scalesInfo; - }, -}); - -return CalendarView; - -}); diff --git a/addons/web/static/src/legacy/js/views/view_registry.js b/addons/web/static/src/legacy/js/views/view_registry.js index c5fded34d22..c32c93d2f93 100644 --- a/addons/web/static/src/legacy/js/views/view_registry.js +++ b/addons/web/static/src/legacy/js/views/view_registry.js @@ -28,7 +28,6 @@ odoo.define('web._view_registry', function (require) { var FormView = require('web.FormView'); var KanbanView = require('web.KanbanView'); var ListView = require('web.ListView'); -var CalendarView = require('web.CalendarView'); var view_registry = require('web.view_registry'); view_registry @@ -36,6 +35,5 @@ view_registry .add('legacy_form', FormView) .add('list', ListView) .add('legacy_list', ListView) - .add('kanban', KanbanView) - .add('calendar', CalendarView); + .add('kanban', KanbanView); }); diff --git a/addons/web/static/src/legacy/scss/views.scss b/addons/web/static/src/legacy/scss/views.scss index e796a4b718e..cbf0d96092d 100644 --- a/addons/web/static/src/legacy/scss/views.scss +++ b/addons/web/static/src/legacy/scss/views.scss @@ -5,8 +5,7 @@ .o_debug .o_legacy_form_view, .o_debug .o_legacy_kanban_view, -.o_debug .o_legacy_list_view, -.o_debug .o_legacy_calendar_view { +.o_debug .o_legacy_list_view { border-top: 3px solid red; } diff --git a/addons/web/static/src/legacy/scss/web_calendar.scss b/addons/web/static/src/legacy/scss/web_calendar.scss deleted file mode 100644 index a40b30d2a7b..00000000000 --- a/addons/web/static/src/legacy/scss/web_calendar.scss +++ /dev/null @@ -1,656 +0,0 @@ -.o_legacy_calendar_view { - flex: 1 1 auto; - min-width: 0; - height: 100%; - background-color: map-get($grays, '100'); - background: linear-gradient(-45deg, map-get($grays, '100'), white); - - .fc-event { - margin: 0 1px; - border-style: solid; - border-width: 0 0 0 3px; - box-sizing: border-box; - overflow: hidden; - background: none; - font-size: 11px; - line-height: 1; - - &:not([href]):not([tabindex]) { - color: $body-color; - } - - &.fc-dragging.fc-day-grid-event.dayGridMonth .fc-content { - @include text-truncate(); - margin: 4px 4px 3px; - } - - .fc-bg { - background-color: mix(map-get($theme-colors, 'primary'), white); // Used for placeholder events only (on creation) - width: 101%; - height: 101%; // Compensate border - opacity: 0.7; - transition: opacity 0.2s; - } - - .fc-content { - white-space: normal; - margin: 0.25rem; - font-size: 1.1em; - font-weight: 500; - } - - // Try to show one full lien for short event - &.fc-short .fc-content { - margin-top: 1px; - } - - &.o_cw_custom_highlight { - z-index: 10!important; - - .fc-bg{ - opacity: 0.95; - } - } - } - - .o_calendar_widget { - height: 100%; - - > .fc-view-container { - height: 100%; - } - - // === Adapt calendar table borders === - // ===================================== - - td { - border-color: $border-color; - } - - .fc-time-grid .fc-slats .fc-minor td { - border-top-color: map-get($grays, '400'); - } - - .fc-widget-content { - border-left-color: transparent; - } - - .fc-widget-header { - border-color: transparent; - border-bottom-color: $border-color; - padding: 3px 0 5px; - } - - hr.fc-widget-header { - padding: 1px; - border: 0; - background: map-get($grays, '400'); - } - - .fc-timeGrid-view .fc-day-grid .fc-row .fc-content-skeleton { - padding: .5em; - } - - .fc-event-container { - color: white; - } - - .fc-more-popover { - .fc-header { - padding-left: 1rem; - .fc-title { - font-weight: bold; - } - } - - .fc-body { - max-height: 500px; - overflow: auto; - } - } - - .o_calendar_disabled { - background-color: $gray-200; - } - - // ====== Specific agenda types ====== - // ==================================== - - // ====== Both Day and Week agenda - .fc-timeGridDay-view, .fc-timeGridWeek-view { - .fc-axis { - padding-left: $o-horizontal-padding; - } - - .fc-widget-header.fc-today{ - border-radius: 25px; - background: $o-brand-odoo; - color: white; - } - - // Reinfornce default border color - tbody td { - border-top-color: map-get($grays, '400'); - } - - // Remove dotted borders (half-hours) - .fc-time-grid .fc-slats .fc-minor td { - border-top-style: none; - } - - // Align labels and timelines - .fc-axis.fc-time { - border-top-color: transparent; - - span { - max-width: 45px; - margin-top: -19px; - position: relative; - display: block; - } - } - - // Add a small gap on top to show the first time label (0:00) - .fc-scroller .fc-time-grid > .fc-slats, - .fc-scroller .fc-time-grid > .fc-bg { - padding-top: 15px; - } - - // Row containing "all day" events - div.fc-day-grid { - background-color: $o-view-background-color; - box-shadow: 0 6px 12px -6px rgba(black, 0.16); - border-left-color: rgba($o-brand-odoo, 0.3); - border-right-color: rgba($o-brand-odoo, 0.3); - - + hr.fc-widget-header { - padding: 1px 0 0; - background: map-get($grays, '300'); - } - - .fc-content-skeleton tr:not(:first-child) .fc-h-event{ - margin-top: 3px - } - } - - // Create a 'preudo-border' for the first row. The actual border - // it's hidden because of border-collapse settings. - .fc-slats tr:first-child td.fc-widget-content:last-child { - box-shadow: inset 0 1px 0 map-get($grays, '400'); - } - - .fc-day.fc-widget-content.fc-today:not(.o_calendar_disabled) { - border-left-color: rgba($o-brand-odoo, 0.3); - border-right-color: rgba($o-brand-odoo, 0.3); - background: $o-view-background-color; - } - - .fc-event { - // Prevent events with similar color to visually overlap each other - box-shadow: 0 0 0 1px white; - - &.fc-event:not(.fc-h-event) { - border-width: 3px 0 0; - - &.fc-not-start { - border-width: 0 0 3px; - - &.fc-not-end { - border-width: 0; - } - } - } - } - - // Reset position to keep the "nowIndicator" line visible - .fc-content-col { - position: initial; - } - } - - // ====== Day only - .fc-timeGridDay-view .fc-event { - padding: 10px; - font-size: 14px; - - // Try to avoid showing no title for short event - &.fc-short { - padding-top: 0; - padding-bottom: 0; - } - - .fc-content { - margin-top: 1px!important; - } - } - - // ====== Week only - .fc-timeGridWeek-view { - .fc-now-indicator { - left: $o-horizontal-padding; - } - - // Expand tiny events on hover/select - .fc-event:not(.fc-h-event).o_cw_custom_highlight, .fc-event:not(.fc-h-event).o_cw_custom_hover { - transition: margin .1s .3s, left .1s .3s, right .1s .3s; - margin: 0!important; - right: 1px!important; - left: 1px!important; - } - } - - // ====== Month only - .fc-dayGridMonth-view { - padding-left: $o-horizontal-padding; - - .fc-event { - border-radius: 25px; - } - - .fc-widget-header { - padding: 3px 0; - } - - .fc-week-number { - background: none; - font-size: 1.2rem; - padding: 0.1rem 0.3rem 0.1rem 0 !important; - line-height: 1; - } - .fc-day-top { - text-align: center; - padding-top: 3px; - padding-bottom: 3px; - .fc-day-number { - float: none !important; - } - } - - .fc-day-number { - margin: 5px; - padding: 0.1rem 0.3rem 0.1rem 0; - font-size: 1.2rem; - color: map-get($grays, '900'); - font-weight: 400; - line-height: 1; - float: none !important; - } - - .fc-day-top.fc-other-month { - opacity: 0.8; - - .fc-day-number { - color: map-get($grays, '500'); - } - } - - td:last-child { - border-right-color: transparent; - } - - .fc-bg .fc-today { - border-color: map-get($grays, '300'); - } - .fc-bg .fc-today:not(.o_calendar_disabled) { - background: $o-view-background-color; - } - - .fc-content-skeleton .fc-today .fc-day-number { - margin-top: 3px; - padding: 0.4em 0.4em 0.35em; - border-radius: 100%; - min-width: 1.1em; - background: $o-cw-color-today-accent; - text-align: center; - color: white; - font-size: 1.1rem; - } - - .fc-more-cell { - > div, .fc-more { - height: 100%; - display: flex; - align-items: center; - justify-content: center; - } - } - - .fc-event { - margin: 0 3px 2px; - - .fc-content { - display: flex; - justify-content: start; - flex-direction: row; - margin: 4px 4px 3px; - } - - .fc-time:not(:empty) { - /*rtl:ignore*/ - padding-right: 0.5em; - } - - .o_event_title { - @include text-truncate(); - } - - &.o_cw_nobg { - - &.o_cw_custom_hover, &.o_cw_custom_highlight, &:hover { - .fc-bg { - visibility: visible; - } - } - } - - &.fc-not-start { - border-right-width: 3px; - - .fc-content { - padding-left: 6px; - } - } - - &.fc-not-end { - margin-right: 0; - - .fc-content { - padding-right: 6px; - } - } - } - } - // ====== Year only - .fc-dayGridYear-view { - border: none; - height: 100%; - padding-left: $o-horizontal-padding; - box-sizing: border-box; - display: flex; - flex-wrap: wrap; - justify-content: space-evenly; - overflow-y: auto; - - @include media-breakpoint-down(md) { - padding-right: 5px; - padding-left: 5px; - } - - @for $i from 1 through length($o-colors-complete) { - $color: nth($o-colors-complete, $i); - - .fc-bgevent.o_calendar_striked { - background: linear-gradient( - transparent 0 40%, - $color 40% 60%, - transparent 60% 100%) - } - - .o_cw_popover_link.o_calendar_color_#{$i - 1} { - border: solid 2px; - padding-left: 5px; - border-color: $color; - margin-top: 2px; - background: $color; - color: black; - - &.o_attendee_status_tentative { - color: color-contrast($color); - background: repeating-linear-gradient( - 45deg, - $color, - $color 10px, - rgba($color, 0.7) 10px, - rgba($color, 0.7) 20px) !important; - } - - &.o_attendee_status_needsAction { - background-color: rgba(white, 0.9)!important; - } - - &.o_attendee_status_declined { - text-decoration: line-through; - background-color: rgba(white, 0.9)!important; - } - } - } - - > table { - height: 100%; - } - - table, tr, th, td { - border: none; - } - - &.fc-readonly-year-view { - .fc-day-top:not(.fc-has-event) { - cursor: default !important; - } - } - - &:not(.fc-readonly-year-view) { - .fc-day-top:hover > .fc-day-number { - font-weight: bold; - border-radius: 100%; - text-align: center; - background-color: rgba(#87c0d1, 0.5); - color: map-get($grays, '900'); - } - } - - > .fc-month-container { - width: 25%; - min-width: 25rem; - box-sizing: border-box; - - @include media-breakpoint-down(md) { - width: 50%; - min-width: 12rem; - } - - > .fc-month { - width: 21rem; - margin: auto; - - @include media-breakpoint-down(md) { - width: 95%; - } - - > .fc-toolbar.fc-header-toolbar { - padding-top: 10px; - margin-bottom: 4px; - cursor: default; - - h2 { - font-size: 1.2rem; - color: map-get($grays, '600'); - } - } - - .fc-widget-header { - padding: 2px 0; - cursor: default; - } - - .fc-dayGridMonth-view { - padding-left: unset; - - .fc-has-event { - background-color: #B4DFF5; - } - } - - .fc-week.fc-row { - min-height: 2rem; - } - - .fc-disabled-day { - background-color: unset; - } - - .fc-day-top { - text-align: center; - padding: 0.4vh; - cursor: pointer; - - > .fc-day-number { - display: block; - float: unset; - line-height: unset; - margin: auto; - padding: 0.1rem 0; - font-size: calc(11px + 0.2vh); - } - - &.fc-today > .fc-day-number { - font-weight: bold; - } - } - } - } - } - // ====== RTL layout(s) - &.fc-rtl { - .fc-timeGrid-view .fc-event { - border-width: 0 3px 0 0; - } - - .fc-dayGridMonth-view .fc-event { - border-width: 0 3px 0 0; - - &.fc-not-start { - margin: 0 0 1px 5px; - border-width: 0 0 0 3px; - - .fc-content { - padding-right: 6px; - padding-left: 0; - } - } - - &.fc-not-end { - margin: 0 5px 1px 0; - - .fc-content { - padding-left: 6px; - padding-right: 0; - } - } - } - } - } -} - -// =============== Generate color classes =============== -@for $i from 1 through length($o-filter_colors) { - $color: nth($o-filter_colors, $i); - - .o_legacy_calendar_view .fc-view { - .o_calendar_hatched_#{$i - 1} { - background: repeating-linear-gradient( - 45deg, - $color, - $color 10px, - rgba($color, 0.4) 10px, - rgba($color, 0.4) 20px - ) !important; - - .fc-bg { - background: repeating-linear-gradient( - 45deg, - $color, - $color 10px, - rgba($color, 0.5) 10px, - rgba($color, 0.5) 20px - ); - } - } - .o_calendar_striked_#{$i - 1} { - background: linear-gradient( - transparent 0 45%, - $color 45% 55%, - transparent 55% 100%); - - .fc-bg { - background: none; - } - } - .fc-bgevent.o_calendar_color_#{$i - 1} { - border-color: $color; - background-color: $color; - opacity: 0.5; - } - .fc-event.o_calendar_striked_#{$i - 1} { - border: none; - } - .fc-event.o_calendar_color_#{$i - 1} { - border-color: $color; - color: darken($color, 45%); - opacity: 1; - - &.o_event_highlight { - opacity: 1; - - .fc-content { - font-weight: bold; - } - } - - &:not(.o_calendar_hatched):not(.o_calendar_striked){ - background: scale-color($color, $lightness: 50%); - .fc-bg { - background: scale-color($color, $lightness: 50%); - } - } - - &.o_cw_custom_hover, &.o_cw_custom_highlight { - box-shadow: 0 12px 12px -5px rgba($color, 0.3); - color: color-contrast($color); - z-index: 10!important; - opacity: 1; - right: 1px!important; - left: 1px!important; - margin-right: 0!important; - } - - &.o_attendee_status_needsAction, &.o_attendee_status_tentative, &.o_attendee_status_declined, &.o_attendee_status_alone { - border-width: 2px 2px 2px !important; - - &.o_cw_custom_highlight { - background-color: $color; - } - } - - &.o_attendee_status_tentative { - color: color-contrast($color); - .fc-bg { - background: repeating-linear-gradient( - 45deg, - $color, - $color 10px, - rgba(white, 0.4) 10px, - rgba(white, 0.4) 20px - ) !important; - } - } - - &.o_attendee_status_alone, &.o_attendee_status_needsAction { - background-color: rgba(white, 0.9)!important; - color: black; - .fc-bg { - background-color: rgba(white, 0.9)!important; - } - } - - &.o_attendee_status_declined { - text-decoration: line-through; - background-color: rgba(white, 0.9)!important; - color: black; - .fc-bg { - background-color: rgba(white, 0.9)!important; - } - } - } - } -} diff --git a/addons/web/static/src/legacy/xml/web_calendar.xml b/addons/web/static/src/legacy/xml/web_calendar.xml deleted file mode 100644 index 1972507c360..00000000000 --- a/addons/web/static/src/legacy/xml/web_calendar.xml +++ /dev/null @@ -1,181 +0,0 @@ - diff --git a/addons/web/static/tests/legacy/core/owl_dialog_tests.js b/addons/web/static/tests/legacy/core/owl_dialog_tests.js index c95f3b4c29c..83a51f01360 100644 --- a/addons/web/static/tests/legacy/core/owl_dialog_tests.js +++ b/addons/web/static/tests/legacy/core/owl_dialog_tests.js @@ -2,13 +2,9 @@ odoo.define('web.owl_dialog_tests', function (require) { "use strict"; const LegacyDialog = require('web.Dialog'); - const FormView = require('web.FormView'); - const CalendarView = require('web.CalendarView'); const makeTestEnvironment = require('web.test_env'); const Dialog = require('web.OwlDialog'); const testUtils = require('web.test_utils'); - const { registry } = require("@web/core/registry"); - const legacyViewRegistry = require('web.view_registry'); const { makeLegacyDialogMappingTestEnv } = require('@web/../tests/helpers/legacy_env_utils'); const { Dialog: WowlDialog } = require("@web/core/dialog/dialog"); @@ -19,7 +15,6 @@ odoo.define('web.owl_dialog_tests', function (require) { mount, destroy, } = require("@web/../tests/helpers/utils"); - const { createWebClient, doAction } = require("@web/../tests/webclient/helpers"); const { LegacyComponent } = require("@web/legacy/legacy_component"); const { Component, useState, xml } = owl; @@ -417,115 +412,5 @@ odoo.define('web.owl_dialog_tests', function (require) { destroy(parent1); destroy(parent2); }); - - QUnit.test("remove tabindex on inactive dialog", async (assert) => { - registry.category("views").remove("calendar"); // remove new calendar from registry - registry.category("views").remove("form"); // remove new form from registry - legacyViewRegistry.add("form", FormView); // add legacy form -> will be wrapped and added to new registry - legacyViewRegistry.add("calendar", CalendarView); // add legacy calendar -> will be wrapped and added to new registry - - const serverData = { - actions: { - 1: { - id: 1, - flags: { initialDate: new Date(2020, 6, 13) }, - name: "Test", - res_model: "event", - type: "ir.actions.act_window", - views: [[false, "calendar"]], - }, - 2: { - id: 2, - name: "Test", - res_model: "event", - target: "new", - type: "ir.actions.act_window", - views: [[2, "form"]], - }, - }, - models: { - event: { - fields: { - id: { type: "integer" }, - display_name: { type: "char" }, - start: { type: "date" }, - }, - records: [{ id: 1, display_name: "Event 1", start: "2020-07-13" }], - methods: { - async check_access_rights() { - return true; - }, - async get_formview_id() { - return false; - }, - }, - }, - }, - views: { - "event,false,calendar": ``, - "event,false,search": ``, - "event,false,form": ` -
- - -
- `, - "event,2,form": `
`, - }, - activateMockServer: true, - }; - - const target = getFixture(); - const webClient = await createWebClient({ serverData }); - await doAction(webClient, 1); - - await testUtils.dom.click(target.querySelector(`.fc-event[data-event-id="1"]`)); - await testUtils.dom.click(target.querySelector(`.o_cw_popover_edit`)); - - assert.containsNone(target, ".o_dialog"); - assert.containsOnce(target, ".modal"); - assert.containsOnce(target, ".modal[tabindex='-1']"); - - assert.strictEqual( - target.querySelector(`.o_field_widget[name="display_name"]`).value, - "Event 1" - ); - await testUtils.fields.editInput( - target.querySelector(`.o_field_widget[name="display_name"]`), - "legacy" - ); - assert.strictEqual( - target.querySelector(`.o_field_widget[name="display_name"]`).value, - "legacy" - ); - - await testUtils.dom.click(target.querySelector(`button[name="2"]`)); - assert.containsOnce(target, ".o_dialog"); - assert.containsN(target, ".modal", 2); - assert.containsOnce(target, ".modal:not([tabindex='-1'])"); - assert.containsOnce(target, ".o_dialog .modal[tabindex='-1']"); - - assert.strictEqual( - target.querySelector(`.o_dialog .o_field_widget[name="display_name"]`).value, - "" - ); - await testUtils.fields.editInput( - target.querySelector(`.o_dialog .o_field_widget[name="display_name"]`), - "wowl" - ); - assert.strictEqual( - target.querySelector(`.o_dialog .o_field_widget[name="display_name"]`).value, - "wowl" - ); - - await testUtils.dom.click(target.querySelector(`.o_dialog .modal-header .btn-close`)); - assert.containsNone(target, ".o_dialog"); - assert.containsOnce(target, ".modal"); - assert.containsOnce(target, ".modal[tabindex='-1']"); - - await testUtils.dom.click(target.querySelector(`.modal-header .btn-close`)); - assert.containsNone(target, ".o_dialog"); - assert.containsNone(target, ".modal"); - }); }); }); diff --git a/addons/web/static/tests/legacy/views/calendar_tests.js b/addons/web/static/tests/legacy/views/calendar_tests.js deleted file mode 100644 index a5a90cead92..00000000000 --- a/addons/web/static/tests/legacy/views/calendar_tests.js +++ /dev/null @@ -1,4245 +0,0 @@ -odoo.define('web.calendar_tests', function (require) { -"use strict"; - -const AbstractField = require('web.AbstractField'); -const BasicModel = require('web.BasicModel'); -const FormView = require('web.FormView'); -var CalendarView = require('web.CalendarView'); -var CalendarRenderer = require('web.CalendarRenderer'); -var Dialog = require('web.Dialog'); -const fieldRegistry = require('web.field_registry'); -var ViewDialogs = require('web.view_dialogs'); -var fieldUtils = require('web.field_utils'); -var mixins = require('web.mixins'); -var testUtils = require('web.test_utils'); -var session = require('web.session'); -const Widget = require('web.Widget'); -const { registry } = require('@web/core/registry'); -const legacyViewRegistry = require('web.view_registry'); - -const { getFixture, patchWithCleanup } = require("@web/../tests/helpers/utils"); - -const { createWebClient, doAction } = require('@web/../tests/webclient/helpers'); -let serverData; - -CalendarRenderer.include({ - getAvatars: function () { - var res = this._super.apply(this, arguments); - for (var k in res) { - res[k] = res[k].replace(/src="([^"]+)"/, 'data-src="\$1"'); - } - return res; - } -}); - - -var createCalendarView = testUtils.createCalendarView; - -// 2016-12-12 08:00:00 -var initialDate = new Date(2016, 11, 12, 8, 0, 0); -initialDate = new Date(initialDate.getTime() - initialDate.getTimezoneOffset()*60*1000); - -function _preventScroll(ev) { - ev.stopImmediatePropagation(); -} - -QUnit.module('LegacyViews', { - beforeEach: function () { - window.addEventListener('scroll', _preventScroll, true); - patchWithCleanup(session, { - uid: -1 - }); - registry.category("views").remove("calendar"); // remove new calendar from registry - registry.category("views").remove("form"); // remove new form from registry - legacyViewRegistry.add("calendar", CalendarView); // add legacy calendar -> will be wrapped and added to new registry - legacyViewRegistry.add("form", FormView); // add legacy form -> will be wrapped and added to new registry - this.data = { - event: { - fields: { - id: {string: "ID", type: "integer"}, - user_id: {string: "user", type: "many2one", relation: 'user', default: session.uid}, - partner_id: {string: "user", type: "many2one", relation: 'partner', related: 'user_id.partner_id', default: 1}, - name: {string: "name", type: "char"}, - start_date: {string: "start date", type: "date"}, - stop_date: {string: "stop date", type: "date"}, - start: {string: "start datetime", type: "datetime"}, - stop: {string: "stop datetime", type: "datetime"}, - delay: {string: "delay", type: "float"}, - allday: {string: "allday", type: "boolean"}, - partner_ids: {string: "attendees", type: "one2many", relation: 'partner', default: [[6, 0, [1]]]}, - type: {string: "type", type: "integer"}, - event_type_id: {string: "Event Type", type: "many2one", relation: 'event_type'}, - color: {string: "Color", type: "integer", related: 'event_type_id.color'}, - is_hatched: {string: "Hatched", type: "boolean"} - }, - records: [ - {id: 1, user_id: session.uid, partner_id: 1, name: "event 1", start: "2016-12-11 00:00:00", stop: "2016-12-11 00:00:00", allday: false, partner_ids: [1,2,3], type: 1, is_hatched: false}, - {id: 2, user_id: session.uid, partner_id: 1, name: "event 2", start: "2016-12-12 10:55:05", stop: "2016-12-12 14:55:05", allday: false, partner_ids: [1,2], type: 3, is_hatched: false}, - {id: 3, user_id: 4, partner_id: 4, name: "event 3", start: "2016-12-12 15:55:05", stop: "2016-12-12 16:55:05", allday: false, partner_ids: [1], type: 2, is_hatched: true}, - {id: 4, user_id: session.uid, partner_id: 1, name: "event 4", start: "2016-12-14 15:55:05", stop: "2016-12-14 18:55:05", allday: true, partner_ids: [1], type: 2, is_hatched: false}, - {id: 5, user_id: 4, partner_id: 4, name: "event 5", start: "2016-12-13 15:55:05", stop: "2016-12-20 18:55:05", allday: false, partner_ids: [2,3], type: 2, is_hatched: true}, - {id: 6, user_id: session.uid, partner_id: 1, name: "event 6", start: "2016-12-18 08:00:00", stop: "2016-12-18 09:00:00", allday: false, partner_ids: [3], type: 3, is_hatched: true}, - {id: 7, user_id: session.uid, partner_id: 1, name: "event 7", start: "2016-11-14 08:00:00", stop: "2016-11-16 17:00:00", allday: false, partner_ids: [2], type: 1, is_hatched: false}, - ], - check_access_rights: function () { - return Promise.resolve(true); - } - }, - user: { - fields: { - id: {string: "ID", type: "integer"}, - display_name: {string: "Displayed name", type: "char"}, - partner_id: {string: "partner", type: "many2one", relation: 'partner'}, - image: {string: "image", type: "integer"}, - }, - records: [ - {id: session.uid, display_name: "user 1", partner_id: 1}, - {id: 4, display_name: "user 4", partner_id: 4}, - ] - }, - partner: { - fields: { - id: {string: "ID", type: "integer"}, - display_name: {string: "Displayed name", type: "char"}, - image: {string: "image", type: "integer"}, - }, - records: [ - {id: 1, display_name: "partner 1", image: 'AAA'}, - {id: 2, display_name: "partner 2", image: 'BBB'}, - {id: 3, display_name: "partner 3", image: 'CCC'}, - {id: 4, display_name: "partner 4", image: 'DDD'} - ] - }, - event_type: { - fields: { - id: {string: "ID", type: "integer"}, - display_name: {string: "Displayed name", type: "char"}, - color: {string: "Color", type: "integer"}, - }, - records: [ - {id: 1, display_name: "Event Type 1", color: 1}, - {id: 2, display_name: "Event Type 2", color: 2}, - {id: 3, display_name: "Event Type 3 (color 4)", color: 4}, - ] - }, - filter_partner: { - fields: { - id: {string: "ID", type: "integer"}, - user_id: {string: "user", type: "many2one", relation: 'user'}, - partner_id: {string: "partner", type: "many2one", relation: 'partner'}, - partner_checked: {string: "checked", type: "boolean"} - }, - records: [ - {id: 1, user_id: session.uid, partner_id: 1, partner_checked: true}, - {id: 2, user_id: session.uid, partner_id: 2, partner_checked: true}, - {id: 3, user_id: 4, partner_id: 3, partner_checked: true} - ] - }, - }; - serverData = { models: this.data }; - this.data.event.methods = { - check_access_rights: this.data.event.check_access_rights, - } - }, - afterEach: function () { - window.removeEventListener('scroll', _preventScroll, true); - }, -}, function () { - - QUnit.module('CalendarView (legacy)'); - - var archs = { - "event,false,form": - '
'+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - '', - "event,1,form": - '
' + - '' + - ''+ - ''+ - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - assert.ok(calendar.$('.o_legacy_calendar_view').find('.fc-view-container').length, - "should instance of fullcalendar"); - - var $sidebar = calendar.$('.o_calendar_sidebar'); - - // test view scales - assert.containsN(calendar, '.fc-event', 0, - "By default, only the events of the current user are displayed (0 in this case)"); - - // display all events - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=all] input')); - assert.containsN(calendar, '.fc-event', 9, - "should display 9 events on the week (4 event + 5 days event)"); - assert.containsN($sidebar, 'tr:has(.ui-state-active) td', 7, - "week scale should highlight 7 days in mini calendar"); - - await testUtils.dom.click(calendar.$buttons.find('.scale_button_selection')); - await testUtils.dom.click(calendar.$buttons.find('.o_calendar_button_day')); // display only one day - assert.containsN(calendar, '.fc-event', 2, "should display 2 events on the day"); - assert.containsOnce($sidebar, '.o_selected_range', - "should highlight the target day in mini calendar"); - - await testUtils.dom.click(calendar.$buttons.find('.scale_button_selection')); - await testUtils.dom.click(calendar.$buttons.find('.o_calendar_button_month')); // display all the month - - // We display the events or partner 1 2 and 4. Partner 2 has nothing and Event 6 is for partner 6 (not displayed) - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=all] input')); - await testUtils.dom.click(calendar.$('.o_calendar_filter .o_calendar_filter_item[data-value=1] input')[0]); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=2] input')); - assert.containsN(calendar, '.fc-event', 7, - "should display 7 events on the month (5 events + 2 week event - 1 'event 6' is filtered + 1 'Undefined event')"); - assert.containsN($sidebar, 'td a', 31, - "month scale should highlight all days in mini calendar"); - - // test filters - assert.containsN($sidebar, '.o_calendar_filter', 2, "should display 2 filters"); - - var $typeFilter = $sidebar.find('.o_calendar_filter:has(span:contains(user))'); - assert.ok($typeFilter.length, "should display 'user' filter"); - assert.containsN($typeFilter, '.o_calendar_filter_item', 3, "should display 3 filter items for 'user'"); - - // filters which has no value should show with string "Undefined", should not have any user image and should show at the last - assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last').data('value'), false, "filters having false value should be displayed at last in filter items"); - assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last .o_cw_filter_title').text(), "Undefined", "filters having false value should display 'Undefined' string"); - assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last label img').length, 0, "filters having false value should not have any user image"); - - var $attendeesFilter = $sidebar.find('.o_calendar_filter:has(span:contains(attendees))'); - assert.ok($attendeesFilter.length, "should display 'attendees' filter"); - assert.containsN($attendeesFilter, '.o_calendar_filter_item', 3, "should display 3 filter items for 'attendees' who use write_model (checkall + 2 saved + Everything)"); - assert.ok($attendeesFilter.find('.o_field_many2one').length, "should display one2many search bar for 'attendees' filter"); - - assert.containsN(calendar, '.fc-event', 7, - "should display 7 events ('event 5' counts for 2 because it spans two weeks and thus generate two fc-event elements)"); - await testUtils.dom.click(calendar.$('.o_calendar_filter input[type="checkbox"]').eq(1)); // click on partner 2 - assert.containsN(calendar, '.fc-event', 4, "should now only display 4 event"); - await testUtils.dom.click(calendar.$('.o_calendar_filter input[type="checkbox"]').eq(2)); - assert.containsNone(calendar, '.fc-event', "should not display any event anymore"); - - // test search bar in filter - await testUtils.dom.click($sidebar.find('input[type="text"]')); - assert.strictEqual($('ul.ui-autocomplete li').length, 2,"should display 2 choices in one2many autocomplete"); - await testUtils.dom.click($('ul.ui-autocomplete li:first')); - assert.containsN($sidebar, '.o_calendar_filter:has(span:contains(attendees)) .o_calendar_filter_item', 4, "should display 4 filter items for 'attendees'"); - await testUtils.dom.click($sidebar.find('input[type="text"]')); - assert.strictEqual($('ul.ui-autocomplete li').text(), "partner 4", "should display the last choice in one2many autocomplete"); - await testUtils.dom.click($sidebar.find('.o_calendar_filter_item .o_remove').eq(1), {allowInvisible: true}); - assert.containsN($sidebar, '.o_calendar_filter:has(span:contains(attendees)) .o_calendar_filter_item', 3, "click on remove then should display 3 filter items for 'attendees'"); - calendar.destroy(); - }); - - QUnit.test('delete attribute on calendar doesn\'t show delete button in popover', async function (assert) { - assert.expect(2); - - const calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content')); - - assert.containsOnce(calendar, '.o_cw_popover', - "should open a popover clicking on event"); - assert.containsNone(calendar, '.o_cw_popover .o_cw_popover_delete', - "should not have the 'Delete' Button"); - - calendar.destroy(); - }); - - QUnit.test('breadcrumbs are updated with the displayed period', async function (assert) { - assert.expect(4); - - serverData.views = { - 'event,1,calendar': '', - 'event,false,search': '', - }; - - serverData.actions = { - 1: { - id: 1, - flags: { - initialDate: initialDate, - }, - name: 'Meetings Test', - res_model: 'event', - type: 'ir.actions.act_window', - views: [[1, 'calendar']], - }, - }; - - const webClient = await createWebClient({ serverData }); - await doAction(webClient, 1); - - // displays month mode by default - assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), - 'Meetings Test (Dec 11 – 17, 2016)', "should display the current week"); - - // switch to day mode - await testUtils.dom.click($('.o_control_panel .scale_button_selection')); - await testUtils.dom.click($('.o_control_panel .o_calendar_button_day')); - assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), - 'Meetings Test (December 12, 2016)', "should display the current day"); - - // switch to month mode - await testUtils.dom.click($('.o_control_panel .scale_button_selection')); - await testUtils.dom.click($('.o_control_panel .o_calendar_button_month')); - assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), - 'Meetings Test (December 2016)', "should display the current month"); - - // switch to year mode - await testUtils.dom.click($('.o_control_panel .scale_button_selection')); - await testUtils.dom.click($('.o_control_panel .o_calendar_button_year')); - assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), - 'Meetings Test (2016)', "should display the current year"); - }); - - QUnit.test('create and change events', async function (assert) { - assert.expect(28); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - mockRPC: function (route, args) { - if (args.method === 'write') { - assert.deepEqual(args.args[1], {name: 'event 4 modified'}, "should update the record"); - } - return this._super.apply(this, arguments); - }, - viewOptions: { - initialDate: initialDate, - }, - }); - - assert.ok(calendar.$('.fc-dayGridMonth-view').length, "should display in month mode"); - - // click on an existing event to open the formViewDialog - - await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content')); - - assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event"); - assert.ok(calendar.$('.o_cw_popover .o_cw_popover_edit').length, "popover should have an edit button"); - assert.ok(calendar.$('.o_cw_popover .o_cw_popover_delete').length, "popover should have a delete button"); - assert.ok(calendar.$('.o_cw_popover .o_cw_popover_close').length, "popover should have a close button"); - - await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_edit')); - - assert.ok($('.modal-body').length, "should open the form view in dialog when click on event"); - - await testUtils.fields.editInput($('.modal-body input:first'), 'event 4 modified'); - await testUtils.dom.click($('.modal-footer button.btn:contains(Save)')); - - assert.notOk($('.modal-body').length, "save button should close the modal"); - - // create a new event, quick create only - - var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - - assert.ok($('.modal-sm').length, "should open the quick create dialog"); - - await testUtils.fields.editInput($('.modal-body input:first'), 'new event in quick create'); - await testUtils.dom.click($('.modal-footer button.btn:contains(Create)')); - - assert.strictEqual(calendar.$('.fc-event:contains(new event in quick create)').length, 1, "should display the new record after quick create"); - assert.containsN(calendar, 'td.fc-event-container[colspan]', 2, "should the new record have only one day"); - - // create a new event, quick create only (validated by pressing enter key) - - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - - assert.ok($('.modal-sm').length, "should open the quick create dialog"); - - await testUtils.fields.editInput($('.modal-body input:first'), - 'new event in quick create validated by pressing enter key.'); - $('.modal-body input:first') - .val('new event in quick create validated by pressing enter key.') - .trigger($.Event('keyup', {keyCode: $.ui.keyCode.ENTER})) - .trigger($.Event('keyup', {keyCode: $.ui.keyCode.ENTER})); - await testUtils.nextTick(); - assert.containsOnce(calendar, '.fc-event:contains(new event in quick create validated by pressing enter key.)', - "should display the new record by pressing enter key"); - - - // create a new event and edit it - - $cell = calendar.$('.fc-day-grid .fc-row:eq(4) .fc-day:eq(2)'); - - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - - assert.strictEqual($('.modal-sm').length, 1, "should open the quick create dialog"); - - testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); - await testUtils.dom.click($('.modal-footer button.btn:contains(Edit)')); - - assert.strictEqual($('.modal-lg .o_legacy_form_view').length, 1, "should open the slow create dialog"); - assert.strictEqual($('.modal-lg .modal-title').text(), "New Event", - "should use the string attribute as modal title"); - assert.strictEqual($('.modal-lg .o_legacy_form_view input[name="name"]').val(), "coucou", - "should have set the name from the quick create dialog"); - - await testUtils.dom.click($('.modal-lg button.btn:contains(Save)')); - - assert.strictEqual(calendar.$('.fc-event:contains(coucou)').length, 1, - "should display the new record with string attribute"); - - // create a new event with 2 days - - $cell = calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day:eq(2)'); - - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell.next(), "mousemove"); - testUtils.dom.triggerMouseEvent($cell.next(), "mouseup"); - await testUtils.nextTick(); - - testUtils.fields.editInput($('.modal-dialog input:first'), 'new event in quick create 2'); - await testUtils.dom.click($('.modal-footer button.btn:contains(Edit)')); - - assert.strictEqual($('.modal-lg input:first').val(),'new event in quick create 2', - "should open the formViewDialog with default values"); - - await testUtils.dom.click($('.modal-lg button.btn:contains(Save)')); - - assert.notOk($('.modal').length, "should close dialogs"); - var $newevent2 = calendar.$('.fc-event:contains(new event in quick create 2)'); - assert.ok($newevent2.length, "should display the 2 days new record"); - assert.hasAttrValue($newevent2.closest('.fc-event-container'), - 'colspan', "2","the new record should have 2 days"); - - await testUtils.dom.click(calendar.$('.fc-event:contains(new event in quick create 2) .fc-content')); - var $popover_description = calendar.$('.o_cw_popover .o_cw_body .list-group-item'); - assert.strictEqual($popover_description.children()[1].textContent,'December 20-21, 2016', - "The popover description should indicate the correct range"); - assert.strictEqual($popover_description.children()[2].textContent,'(2 days)', - "The popover description should indicate 2 days"); - await testUtils.dom.click(calendar.$('.o_cw_popover .fa-close')); - - // delete the a record - - await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content')); - await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_delete')); - assert.ok($('.modal-footer button.btn:contains(Ok)').length, "should display the confirm message"); - await testUtils.dom.click($('.modal-footer button.btn:contains(Ok)')); - assert.notOk(calendar.$('.fc-event:contains(event 4) .fc-content').length, "the record should be deleted"); - - assert.containsN(calendar, '.fc-event-container .fc-event', 10, "should display 10 events"); - // move to next month - await testUtils.dom.click(calendar.$buttons.find('.o_calendar_button_next')); - - assert.containsNone(calendar, '.fc-event-container .fc-event', "should display 0 events"); - - calendar.destroy(); - }); - - QUnit.test('quickcreate with custom create_name_field', async function (assert) { - assert.expect(3); - - this.data.custom_event = { - fields: { - id: {string: "ID", type: "integer"}, - x_name: {string: "name", type: "char"}, - x_start_date: {string: "start date", type: "date"}, - }, - records: [ - {id: 1, x_name: 'some event', x_start_date: "2016-12-06"} - ], - check_access_rights: function () { - return Promise.resolve(true); - } - }; - var calendar = await createCalendarView({ - View: CalendarView, - model: 'custom_event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - if (args.method === "create") { - assert.deepEqual(args.args[0], { - "x_name": "custom event in quick create", - "x_start_date": "2016-12-13 00:00:00", - }, - "the custom create_name_field should be used instead of `name`"); - this.data.custom_event.records.push({ - id: 1, - x_name: args.args[0].x_name, - x_start_date: args.args[0].x_start_date, - display_name: args.args[0].x_name, - }) - return Promise.resolve(1); - } - return this._super(route, args); - }, - }); - - // create a new event - var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - - assert.strictEqual($('.modal-sm .modal-title').text(), 'New Event', - "should open the quick create dialog"); - - await testUtils.fields.editInput($('.modal-body input:first'), 'custom event in quick create'); - await testUtils.dom.click($('.modal-footer button.btn:contains(Create)')); - await testUtils.nextTick(); - - assert.containsOnce(calendar, '.fc-event:contains(custom event in quick create)', - "should display the new custom event record"); - calendar.destroy(); - }); - - QUnit.test('quickcreate switching to actual create for required fields', async function (assert) { - assert.expect(4); - - var event = $.Event(); - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - if (args.method === "create") { - return Promise.reject({ - message: { - code: 200, - data: {}, - message: "Odoo server error", - }, - event: event - }); - } - return this._super(route, args); - }, - }); - - // create a new event - var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - - assert.strictEqual($('.modal-sm .modal-title').text(), 'New Event', - "should open the quick create dialog"); - - await testUtils.fields.editInput($('.modal-body input:first'), 'new event in quick create'); - await testUtils.dom.click($('.modal-footer button.btn:contains(Create)')); - await testUtils.nextTick(); - - // If the event is not default-prevented, a traceback will be raised, which we do not want - assert.ok(event.isDefaultPrevented(), "fail deferred event should have been default-prevented"); - - assert.strictEqual($('.modal-lg .modal-title').text(), 'New Event', - "should have switched to a bigger modal for an actual create rather than quickcreate"); - assert.strictEqual($('.modal-lg main .o_legacy_form_view.o_form_editable').length, 1, - "should open the full event form view in a dialog"); - - calendar.destroy(); - }); - - QUnit.test('open multiple event form at the same time', async function (assert) { - assert.expect(2); - - var prom = testUtils.makeTestPromise(); - var counter = 0; - testUtils.mock.patch(ViewDialogs.FormViewDialog, { - open: function () { - counter++; - this.options = _.omit(this.options, 'fields_view'); // force loadFieldView - return this._super.apply(this, arguments); - }, - loadFieldView: function () { - var self = this; - var args = arguments; - var _super = this._super; - return prom.then(function () { - return _super.apply(self, args); - }); - }, - }); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - for (var i = 0; i < 5; i++) { - await testUtils.dom.triggerMouseEvent($cell, "mousedown"); - await testUtils.dom.triggerMouseEvent($cell, "mouseup"); - } - prom.resolve(); - await testUtils.nextTick(); - assert.equal(counter, 5, "there should had been 5 attemps to open a modal"); - assert.containsOnce($('body'), '.modal', "there should be only one open modal"); - - calendar.destroy(); - testUtils.mock.unpatch(ViewDialogs.FormViewDialog); - }); - - QUnit.test('create event with timezone in week mode European locale', async function (assert) { - assert.expect(5); - - this.data.event.records = []; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return 120; - }, - }, - translateParameters: { // Avoid issues due to localization formats - time_format: "%H:%M:%S", - }, - mockRPC: function (route, args) { - if (args.method === "create") { - assert.deepEqual(args.kwargs.context, { - "default_start": "2016-12-13 06:00:00", - "default_stop": "2016-12-13 08:00:00", - "default_allday": null - }, - "should send the context to create events"); - } - return this._super(route, args); - }, - }, {positionalClicks: true}); - - var top = calendar.$('.fc-axis:contains(8:00)').offset().top + 5; - var left = calendar.$('.fc-day:eq(2)').offset().left + 5; - - try { - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - } catch (_e) { - calendar.destroy(); - throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); - } - - testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mousemove"); - - assert.strictEqual(calendar.$('.fc-content .fc-time').text(), "8:00 - 10:00", - "should display the time in the calendar sticker"); - - await testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); - await testUtils.nextTick(); - await testUtils.fields.editInput($('.modal input:first'), 'new event'); - await testUtils.dom.click($('.modal button.btn:contains(Create)')); - var $newevent = calendar.$('.fc-event:contains(new event)'); - - assert.strictEqual($newevent.find('.o_event_title').text(), "new event", - "should display the new event with title"); - - assert.deepEqual($newevent[0].fcSeg.eventRange.def.extendedProps.record, - { - display_name: "new event", - start: fieldUtils.parse.datetime("2016-12-13 06:00:00", this.data.event.fields.start, {isUTC: true}), - stop: fieldUtils.parse.datetime("2016-12-13 08:00:00", this.data.event.fields.stop, {isUTC: true}), - allday: false, - name: "new event", - id: 1 - }, - "the new record should have the utc datetime (quickCreate)"); - - // delete record - - await testUtils.dom.click($newevent); - await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_delete')); - await testUtils.dom.click($('.modal button.btn-primary:contains(Ok)')); - assert.containsNone(calendar, '.fc-content', "should delete the record"); - - calendar.destroy(); - }); - - QUnit.test('default week start (US)', function (assert) { - // if not given any option, default week start is on Sunday - assert.expect(3); - var done = assert.async(); - - createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - '', - archs: archs, - - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - if (args.method === 'search_read' && args.model === 'event') { - assert.deepEqual(args.kwargs.domain, [ - ["start","<=","2016-12-17 23:59:59"], - ["stop",">=","2016-12-11 00:00:00"] - ], - 'The domain to search events in should be correct'); - } - return this._super.apply(this, arguments); - } - }).then(function (calendar) { - assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Sun 11", - "The first day of the week should be Sunday"); - assert.strictEqual(calendar.$('.fc-day-header').last().text(), "Sat 17", - "The last day of the week should be Saturday"); - calendar.destroy(); - done(); - }); - }); - - QUnit.test('European week start', function (assert) { - // the week start depends on the locale - assert.expect(3); - var done = assert.async(); - - createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - '', - archs: archs, - - viewOptions: { - initialDate: initialDate, - }, - translateParameters: { - week_start: 1, - }, - mockRPC: function (route, args) { - if (args.method === 'search_read' && args.model === 'event') { - assert.deepEqual(args.kwargs.domain, [ - ["start","<=","2016-12-18 23:59:59"], - ["stop",">=","2016-12-12 00:00:00"] - ], - 'The domain to search events in should be correct'); - } - return this._super.apply(this, arguments); - } - }).then(function (calendar) { - assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Mon 12", - "The first day of the week should be Monday"); - assert.strictEqual(calendar.$('.fc-day-header').last().text(), "Sun 18", - "The last day of the week should be Sunday"); - calendar.destroy(); - done(); - }); - }); - - QUnit.test('week numbering', function (assert) { - // week number depends on the week start, which depends on the locale - // the calendar library uses numbers [0 .. 6], while Odoo uses [1 .. 7] - // so if the modulo is not done, the week number is incorrect - assert.expect(1); - var done = assert.async(); - - createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - '', - archs: archs, - - viewOptions: { - initialDate: initialDate, - }, - translateParameters: { - week_start: 7, - }, - }).then(function (calendar) { - assert.strictEqual(calendar.$('.fc-week-number').text(), "Week 51", - "We should be on the 51st week"); - calendar.destroy(); - done(); - }); - }); - - QUnit.test('render popover', async function (assert) { - assert.expect(14); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - await testUtils.dom.click($('.fc-event:contains(event 4)')); - - assert.containsOnce(calendar, '.o_cw_popover', "should open a popover clicking on event"); - assert.strictEqual(calendar.$('.o_cw_popover .popover-header').text(), 'event 4', "popover should have a title 'event 4'"); - assert.containsOnce(calendar, '.o_cw_popover .o_cw_popover_edit', "popover should have an edit button"); - assert.containsOnce(calendar, '.o_cw_popover .o_cw_popover_delete', "popover should have a delete button"); - assert.containsOnce(calendar, '.o_cw_popover .o_cw_popover_close', "popover should have a close button"); - - assert.strictEqual(calendar.$('.o_cw_popover .list-group-item:first b.text-capitalize').text(), 'Wednesday, December 14, 2016', "should display date 'Wednesday, December 14, 2016'"); - assert.containsN(calendar, '.o_cw_popover .o_cw_popover_fields_secondary .list-group-item', 2, "popover should have a two fields"); - - assert.containsOnce(calendar, '.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:first .o_field_char', "should apply char widget"); - assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:first strong').text(), 'Custom Name : ', "label should be a 'Custom Name'"); - assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:first .o_field_char').text(), 'event 4', "value should be a 'event 4'"); - - assert.containsOnce(calendar, '.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:last .o_form_uri', "should apply m20 widget"); - assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:last strong').text(), 'user : ', "label should be a 'user'"); - assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:last .o_form_uri').text(), 'partner 1', "value should be a 'partner 1'"); - - await testUtils.dom.click($('.o_cw_popover .o_cw_popover_close')); - assert.containsNone(calendar, '.o_cw_popover', "should close a popover"); - - calendar.destroy(); - }); - - QUnit.test('render popover with modifiers', async function (assert) { - assert.expect(5); - - this.data.event.fields.priority = {string: "Priority", type: "selection", selection: [['0', 'Normal'], ['1', 'Important']],}; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - ' - - - `, - archs, - viewOptions: { - initialDate, - }, - }); - - const event4 = document.querySelectorAll(".fc-event")[0]; - await testUtils.dom.click(event4); - assert.containsOnce(calendar, '.o_cw_popover', "should open a popover clicking on event"); - assert.verifySteps(["_fetchSpecialDataForMyWidget"]); - - calendar.destroy(); - await testUtils.mock.unpatch(BasicModel); - }); - - QUnit.test('attributes hide_date and hide_time', async function (assert) { - assert.expect(1); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - await testUtils.dom.click($('.fc-event:contains(event 4)')); - assert.containsNone(calendar, '.o_cw_popover .list-group-item', "popover should not contain date/time"); - - calendar.destroy(); - }); - - QUnit.test('create event with timezone in week mode with formViewDialog European locale', async function (assert) { - assert.expect(8); - - this.data.event.records = []; - this.data.event.onchanges = { - allday: function (obj) { - if (obj.allday) { - obj.start_date = obj.start && obj.start.split(' ')[0] || obj.start_date; - obj.stop_date = obj.stop && obj.stop.split(' ')[0] || obj.stop_date || obj.start_date; - } else { - obj.start = obj.start_date && (obj.start_date + ' 00:00:00') || obj.start; - obj.stop = obj.stop_date && (obj.stop_date + ' 00:00:00') || obj.stop || obj.start; - } - } - }; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return 120; - }, - }, - translateParameters: { // Avoid issues due to localization formats - time_format: "%H:%M:%S", - }, - mockRPC: function (route, args) { - if (args.method === "create") { - assert.deepEqual(args.kwargs.context, { - "default_name": "new event", - "default_start": "2016-12-13 06:00:00", - "default_stop": "2016-12-13 08:00:00", - "default_allday": null - }, - "should send the context to create events"); - } - if (args.method === "write") { - assert.deepEqual(args.args[1], expectedEvent, - "should move the event"); - } - return this._super(route, args); - }, - }, {positionalClicks: true}); - - var top = calendar.$('.fc-axis:contains(8:00)').offset().top + 5; - var left = calendar.$('.fc-day:eq(2)').offset().left + 5; - - try { - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - } catch (_e) { - calendar.destroy(); - throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); - } - testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); - await testUtils.nextTick(); - await testUtils.fields.editInput($('.modal input:first'), 'new event'); - await testUtils.dom.click($('.modal button.btn:contains(Edit)')); - - assert.strictEqual($('.o_field_widget[name="start"] input').val(), - "12/13/2016 08:00:00", "should display the datetime"); - - await testUtils.dom.click($('.modal-lg .o_field_boolean[name="allday"] input')); - await testUtils.nextTick(); - assert.strictEqual($('input[name="start_date"]').val(), - "12/13/2016", "should display the date"); - - await testUtils.dom.click($('.modal-lg .o_field_boolean[name="allday"] input')); - - assert.strictEqual($('.o_field_widget[name="start"] input').val(), - "12/13/2016 02:00:00", "should display the datetime from the date with the timezone"); - - // use datepicker to enter a date: 12/13/2016 08:00:00 - testUtils.dom.openDatepicker($('.o_field_widget[name="start"].o_datepicker')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(08)')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]')); - - // use datepicker to enter a date: 12/13/2016 10:00:00 - testUtils.dom.openDatepicker($('.o_field_widget[name="stop"].o_datepicker')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(10)')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]')); - - await testUtils.dom.click($('.modal-lg button.btn:contains(Save)')); - var $newevent = calendar.$('.fc-event:contains(new event)'); - - assert.strictEqual($newevent.find('.o_event_title').text(), "new event", - "should display the new event with title"); - - assert.deepEqual($newevent[0].fcSeg.eventRange.def.extendedProps.record, - { - display_name: "new event", - start: fieldUtils.parse.datetime("2016-12-13 06:00:00", this.data.event.fields.start, {isUTC: true}), - stop: fieldUtils.parse.datetime("2016-12-13 08:00:00", this.data.event.fields.stop, {isUTC: true}), - allday: false, - name: "new event", - id: 1 - }, - "the new record should have the utc datetime (formViewDialog)"); - - var pos = calendar.$('.fc-content').offset(); - left = pos.left + 5; - top = pos.top + 5; - - // Mode this event to another day - var expectedEvent = { - "allday": false, - "start": "2016-12-12 06:00:00", - "stop": "2016-12-12 08:00:00" - }; - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - left = calendar.$('.fc-day:eq(1)').offset().left + 15; - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); - await testUtils.nextTick(); - - // Move to "All day" - expectedEvent = { - "allday": true, - "start": "2016-12-12 00:00:00", - "stop": "2016-12-12 00:00:00" - }; - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - top = calendar.$('.fc-day:eq(1)').offset().top + 15; - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); - await testUtils.nextTick(); - - calendar.destroy(); - }); - - QUnit.test('create event with timezone in week mode American locale', async function (assert) { - assert.expect(5); - - this.data.event.records = []; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return 120; - }, - }, - translateParameters: { // Avoid issues due to localization formats - time_format: "%I:%M:%S", - }, - mockRPC: function (route, args) { - if (args.method === "create") { - assert.deepEqual(args.kwargs.context, { - "default_start": "2016-12-13 06:00:00", - "default_stop": "2016-12-13 08:00:00", - "default_allday": null - }, - "should send the context to create events"); - } - return this._super(route, args); - }, - }, {positionalClicks: true}); - - var top = calendar.$('.fc-axis:contains(8am)').offset().top + 5; - var left = calendar.$('.fc-day:eq(2)').offset().left + 5; - - try { - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - } catch (_e) { - calendar.destroy(); - throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); - } - - testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mousemove"); - - assert.strictEqual(calendar.$('.fc-content .fc-time').text(), "8:00 - 10:00", - "should display the time in the calendar sticker"); - - testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); - await testUtils.nextTick(); - testUtils.fields.editInput($('.modal input:first'), 'new event'); - await testUtils.dom.click($('.modal button.btn:contains(Create)')); - var $newevent = calendar.$('.fc-event:contains(new event)'); - - assert.strictEqual($newevent.find('.o_event_title').text(), "new event", - "should display the new event with title"); - - assert.deepEqual($newevent[0].fcSeg.eventRange.def.extendedProps.record, - { - display_name: "new event", - start: fieldUtils.parse.datetime("2016-12-13 06:00:00", this.data.event.fields.start, {isUTC: true}), - stop: fieldUtils.parse.datetime("2016-12-13 08:00:00", this.data.event.fields.stop, {isUTC: true}), - allday: false, - name: "new event", - id: 1 - }, - "the new record should have the utc datetime (quickCreate)"); - - // delete record - - await testUtils.dom.click($newevent); - await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_delete')); - await testUtils.dom.click($('.modal button.btn-primary:contains(Ok)')); - assert.containsNone(calendar, '.fc-content', "should delete the record"); - - calendar.destroy(); - }); - - QUnit.test('fetch event when being in timezone', async function (assert) { - assert.expect(3); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return 660; - }, - }, - - mockRPC: async function (route, args) { - if (args.method === 'search_read' && args.model === 'event') { - assert.deepEqual(args.kwargs.domain, [ - ["start", "<=", "2016-12-17 12:59:59"], // in UTC. which is 2016-12-17 23:59:59 in TZ Sydney 11 hours later - ["stop", ">=", "2016-12-10 13:00:00"] // in UTC. which is 2016-12-11 00:00:00 in TZ Sydney 11 hours later - ], 'The domain should contain the right range'); - } - return this._super(route, args); - }, - }); - - assert.strictEqual(calendar.$('.fc-day-header:first').text(), 'Sun 11', - 'The calendar start date should be 2016-12-11'); - assert.strictEqual(calendar.$('.fc-day-header:last()').text(), 'Sat 17', - 'The calendar start date should be 2016-12-17'); - - calendar.destroy(); - }); - - QUnit.test('create event with timezone in week mode with formViewDialog American locale', async function (assert) { - assert.expect(8); - - this.data.event.records = []; - this.data.event.onchanges = { - allday: function (obj) { - if (obj.allday) { - obj.start_date = obj.start && obj.start.split(' ')[0] || obj.start_date; - obj.stop_date = obj.stop && obj.stop.split(' ')[0] || obj.stop_date || obj.start_date; - } else { - obj.start = obj.start_date && (obj.start_date + ' 00:00:00') || obj.start; - obj.stop = obj.stop_date && (obj.stop_date + ' 00:00:00') || obj.stop || obj.start; - } - } - }; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return 120; - }, - }, - translateParameters: { // Avoid issues due to localization formats - time_format: "%I:%M:%S", - }, - mockRPC: function (route, args) { - if (args.method === "create") { - assert.deepEqual(args.kwargs.context, { - "default_name": "new event", - "default_start": "2016-12-13 06:00:00", - "default_stop": "2016-12-13 08:00:00", - "default_allday": null - }, - "should send the context to create events"); - } - if (args.method === "write") { - assert.deepEqual(args.args[1], expectedEvent, - "should move the event"); - } - return this._super(route, args); - }, - }, {positionalClicks: true}); - - var top = calendar.$('.fc-axis:contains(8am)').offset().top + 5; - var left = calendar.$('.fc-day:eq(2)').offset().left + 5; - - try { - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - } catch (_e) { - calendar.destroy(); - throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); - } - testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); - await testUtils.nextTick(); - testUtils.fields.editInput($('.modal input:first'), 'new event'); - await testUtils.dom.click($('.modal button.btn:contains(Edit)')); - - assert.strictEqual($('.o_field_widget[name="start"] input').val(), "12/13/2016 08:00:00", - "should display the datetime"); - - await testUtils.dom.click($('.modal-lg .o_field_boolean[name="allday"] input')); - - assert.strictEqual($('.o_field_widget[name="start_date"] input').val(), "12/13/2016", - "should display the date"); - - await testUtils.dom.click($('.modal-lg .o_field_boolean[name="allday"] input')); - - assert.strictEqual($('.o_field_widget[name="start"] input').val(), "12/13/2016 02:00:00", - "should display the datetime from the date with the timezone"); - - // use datepicker to enter a date: 12/13/2016 08:00:00 - testUtils.dom.openDatepicker($('.o_field_widget[name="start"].o_datepicker')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(08)')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]')); - - // use datepicker to enter a date: 12/13/2016 10:00:00 - testUtils.dom.openDatepicker($('.o_field_widget[name="stop"].o_datepicker')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(10)')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]')); - - await testUtils.dom.click($('.modal-lg button.btn:contains(Save)')); - var $newevent = calendar.$('.fc-event:contains(new event)'); - - assert.strictEqual($newevent.find('.o_event_title').text(), "new event", - "should display the new event with title"); - - assert.deepEqual($newevent[0].fcSeg.eventRange.def.extendedProps.record, - { - display_name: "new event", - start: fieldUtils.parse.datetime("2016-12-13 06:00:00", this.data.event.fields.start, {isUTC: true}), - stop: fieldUtils.parse.datetime("2016-12-13 08:00:00", this.data.event.fields.stop, {isUTC: true}), - allday: false, - name: "new event", - id: 1 - }, - "the new record should have the utc datetime (formViewDialog)"); - - var pos = calendar.$('.fc-content').offset(); - left = pos.left + 5; - top = pos.top + 5; - - // Mode this event to another day - var expectedEvent = { - "allday": false, - "start": "2016-12-12 06:00:00", - "stop": "2016-12-12 08:00:00" - }; - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - left = calendar.$('.fc-day:eq(1)').offset().left + 15; - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); - await testUtils.nextTick(); - - // Move to "All day" - expectedEvent = { - "allday": true, - "start": "2016-12-12 00:00:00", - "stop": "2016-12-12 00:00:00" - }; - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - top = calendar.$('.fc-day:eq(1)').offset().top + 15; - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); - await testUtils.nextTick(); - - calendar.destroy(); - }); - - QUnit.test('check calendar week column timeformat', async function (assert) { - assert.expect(2); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - translateParameters: { - time_format: "%I:%M:%S", - }, - }); - - assert.strictEqual(calendar.$('.fc-axis:contains(8am)').length, 1, "calendar should show according to timeformat"); - assert.strictEqual(calendar.$('.fc-axis:contains(11pm)').length, 1, - "event time format should 12 hour"); - - calendar.destroy(); - }); - - QUnit.test('create all day event in week mode', async function (assert) { - assert.expect(3); - - this.data.event.records = []; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return 120; - }, - }, - }, {positionalClicks: true}); - - var pos = calendar.$('.fc-bg td:eq(4)').offset(); - try { - testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); - } catch (_e) { - calendar.destroy(); - throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); - } - pos = calendar.$('.fc-bg td:eq(5)').offset(); - testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); - await testUtils.nextTick(); - - testUtils.fields.editInput($('.modal input:first'), 'new event'); - await testUtils.dom.click($('.modal button.btn:contains(Create)')); - var $newevent = calendar.$('.fc-event:contains(new event)'); - - assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "newevent", - "should display the new event with time and title"); - assert.hasAttrValue($newevent.parent(), 'colspan', "2", - "should appear over two days."); - - assert.deepEqual($newevent[0].fcSeg.eventRange.def.extendedProps.record, - { - display_name: "new event", - start: fieldUtils.parse.datetime("2016-12-14 00:00:00", this.data.event.fields.start, {isUTC: true}), - stop: fieldUtils.parse.datetime("2016-12-15 00:00:00", this.data.event.fields.stop, {isUTC: true}), - allday: true, - name: "new event", - id: 1 - }, - "the new record should have the utc datetime (quickCreate)"); - - calendar.destroy(); - }); - - QUnit.test('create event with default context (no quickCreate)', async function (assert) { - assert.expect(3); - - this.data.event.records = []; - - const calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ``, - archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset() { - return 120; - }, - }, - context: { - default_name: 'New', - }, - intercepts: { - do_action(ev) { - assert.step('do_action'); - assert.deepEqual(ev.data.action.context, { - default_name: "New", - default_start: "2016-12-14 00:00:00", - default_stop: "2016-12-15 00:00:00", - default_allday: true, - }, - "should send the correct data to create events"); - }, - }, - }, { positionalClicks: true }); - - var pos = calendar.$('.fc-bg td:eq(4)').offset(); - try { - testUtils.dom.triggerPositionalMouseEvent(pos.left + 15, pos.top + 15, "mousedown"); - } catch (_e) { - calendar.destroy(); - throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); - } - pos = calendar.$('.fc-bg td:eq(5)').offset(); - testUtils.dom.triggerPositionalMouseEvent(pos.left + 15, pos.top + 15, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(pos.left + 15, pos.top + 15, "mouseup"); - assert.verifySteps(['do_action']); - - calendar.destroy(); - }); - - QUnit.test('create all day event in week mode (no quickCreate)', async function (assert) { - assert.expect(1); - - this.data.event.records = []; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return 120; - }, - }, - intercepts: { - do_action: function (event) { - assert.deepEqual(event.data.action.context, { - default_start: "2016-12-14 00:00:00", - default_stop: "2016-12-15 00:00:00", - default_allday: true, - }, - "should send the correct data to create events"); - }, - }, - }, {positionalClicks: true}); - - var pos = calendar.$('.fc-bg td:eq(4)').offset(); - try { - testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); - } catch (_e) { - calendar.destroy(); - throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); - } - pos = calendar.$('.fc-bg td:eq(5)').offset(); - testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); - - calendar.destroy(); - }); - - QUnit.test('create event in month mode', async function (assert) { - assert.expect(4); - - this.data.event.records = []; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return 120; - }, - }, - mockRPC: function (route, args) { - if (args.method === "create") { - assert.deepEqual(args.args[0], { - "name": "new event", - "start": "2016-12-14 05:00:00", - "stop": "2016-12-15 17:00:00", - }, - "should send the correct data to create events"); - } - return this._super(route, args); - }, - }, {positionalClicks: true}); - - var pos = calendar.$('.fc-bg td:eq(17)').offset(); - try { - testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); - } catch (_e) { - calendar.destroy(); - throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); - } - pos = calendar.$('.fc-bg td:eq(18)').offset(); - testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); - await testUtils.nextTick(); - - testUtils.fields.editInput($('.modal input:first'), 'new event'); - await testUtils.dom.click($('.modal button.btn:contains(Create)')); - var $newevent = calendar.$('.fc-event:contains(new event)'); - - assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "newevent", - "should display the new event with time and title"); - assert.hasAttrValue($newevent.parent(), 'colspan', "2", - "should appear over two days."); - - assert.deepEqual($newevent[0].fcSeg.eventRange.def.extendedProps.record, { - display_name: "new event", - start: fieldUtils.parse.datetime("2016-12-14 05:00:00", this.data.event.fields.start, {isUTC: true}), - stop: fieldUtils.parse.datetime("2016-12-15 17:00:00", this.data.event.fields.stop, {isUTC: true}), - name: "new event", - id: 1 - }, "the new record should have the utc datetime (quickCreate)"); - - calendar.destroy(); - }); - - QUnit.test('use mini calendar', async function (assert) { - assert.expect(12); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return 120; - }, - }, - }); - - assert.containsOnce(calendar, '.fc-timeGridWeek-view', "should be in week mode"); - assert.containsN(calendar, '.fc-event', 9, "should display 9 events on the week (4 event + 5 days event)"); - await testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(19)')); - // Clicking on a day in another week should switch to the other week view - assert.containsOnce(calendar, '.fc-timeGridWeek-view', "should be in week mode"); - assert.containsN(calendar, '.fc-event', 4, "should display 4 events on the week (1 event + 3 days event)"); - // Clicking on a day in the same week should switch to that particular day view - await testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); - assert.containsOnce(calendar, '.fc-timeGridDay-view', "should be in day mode"); - assert.containsN(calendar, '.fc-event', 2, "should display 2 events on the day"); - // Clicking on the same day should toggle between day, month and week views - await testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); - assert.containsOnce(calendar, '.fc-dayGridMonth-view', "should be in month mode"); - assert.containsN(calendar, '.fc-event', 7, "should display 7 events on the month (event 5 is on multiple weeks and generates to .fc-event)"); - await testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); - assert.containsOnce(calendar, '.fc-timeGridWeek-view', "should be in week mode"); - assert.containsN(calendar, '.fc-event', 4, "should display 4 events on the week (1 event + 3 days event)"); - await testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); - assert.containsOnce(calendar, '.fc-timeGridDay-view', "should be in day mode"); - assert.containsN(calendar, '.fc-event', 2, "should display 2 events on the day"); - - calendar.destroy(); - }); - - QUnit.test('rendering, with many2many', async function (assert) { - assert.expect(5); - - this.data.event.fields.partner_ids.type = 'many2many'; - this.data.event.records[0].partner_ids = [1,2,3,4,5]; - this.data.partner.records.push({id: 5, display_name: "partner 5", image: 'EEE'}); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ' '+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - assert.containsN(calendar, '.o_calendar_filter_items .o_cw_filter_avatar', 3, - "should have 3 avatars in the side bar"); - - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=all] input')); - - // Event 1 - await testUtils.dom.click(calendar.$('.fc-event:first')); - assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event"); - assert.strictEqual(calendar.$('.o_cw_popover').find('img').length, 1, "should have 1 avatar"); - - // Event 2 - await testUtils.dom.click(calendar.$('.fc-event:eq(1)')); - assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event"); - assert.strictEqual(calendar.$('.o_cw_popover').find('img').length, 5, "should have 5 avatar"); - - calendar.destroy(); - }); - - QUnit.test('open form view', async function (assert) { - assert.expect(3); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - if (args.method === "get_formview_id") { - return Promise.resolve('A view'); - } - return this._super(route, args); - }, - }); - - // click on an existing event to open the form view - - testUtils.mock.intercept(calendar, 'do_action', function (event) { - assert.deepEqual(event.data.action, - { - type: "ir.actions.act_window", - res_id: 4, - res_model: "event", - views: [['A view', "form"]], - target: "current", - context: {} - }, - "should open the form view"); - }); - await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content')); - await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_edit')); - - // create a new event and edit it - - var $cell = calendar.$('.fc-day-grid .fc-row:eq(4) .fc-day:eq(2)'); - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); - - testUtils.mock.intercept(calendar, 'do_action', function (event) { - assert.deepEqual(event.data.action, - { - type: "ir.actions.act_window", - res_model: "event", - views: [[false, "form"]], - target: "current", - context: { - "default_name": "coucou", - "default_start": "2016-12-27 00:00:00", - "default_stop": "2016-12-27 00:00:00", - "default_allday": true - } - }, - "should open the form view with the context default values"); - }); - - testUtils.dom.click($('.modal button.btn:contains(Edit)')); - - calendar.destroy(); - - assert.strictEqual($('#ui-datepicker-div:empty').length, 0, "should have a clean body"); - }); - - QUnit.test('create and edit event in month mode (all_day: false)', async function (assert) { - assert.expect(2); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return -240; - }, - }, - }); - - // create a new event and edit it - var $cell = calendar.$('.fc-day-grid .fc-row:eq(4) .fc-day:eq(2)'); - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - await testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); - - testUtils.mock.intercept(calendar, 'do_action', function (event) { - assert.deepEqual(event.data.action, - { - type: "ir.actions.act_window", - res_model: "event", - views: [[false, "form"]], - target: "current", - context: { - "default_name": "coucou", - "default_start": "2016-12-27 11:00:00", // 7:00 + 4h - "default_stop": "2016-12-27 23:00:00", // 19:00 + 4h - } - }, - "should open the form view with the context default values"); - }); - - await testUtils.dom.click($('.modal button.btn:contains(Edit)')); - - calendar.destroy(); - assert.strictEqual($('#ui-datepicker-div:empty').length, 0, "should have a clean body"); - }); - - QUnit.test('show start time of single day event for month mode', async function (assert) { - assert.expect(4); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return -240; - }, - }, - }); - - assert.strictEqual(calendar.$('.fc-event:contains(event 2) .fc-content .fc-time').text(), "06:55", - "should have a correct time 06:55 AM in month mode"); - assert.strictEqual(calendar.$('.fc-event:contains(event 4) .fc-content .fc-time').text(), "", - "should not display a time for all day event"); - assert.strictEqual(calendar.$('.fc-event:contains(event 5) .fc-content .fc-time').text(), "", - "should not display a time for multiple days event"); - // switch to week mode - await testUtils.dom.click(calendar.$buttons.find('.scale_button_selection')); - await testUtils.dom.click(calendar.$('.o_calendar_button_week')); - assert.strictEqual(calendar.$('.fc-event:contains(event 2) .fc-content .fc-time').text(), "", - "should not show time in week mode as week mode already have time on y-axis"); - - calendar.destroy(); - }); - - QUnit.test('start time should not shown for date type field', async function (assert) { - assert.expect(1); - - this.data.event.fields.start.type = "date"; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return -240; - }, - }, - }); - - assert.strictEqual(calendar.$('.fc-event:contains(event 2) .fc-content .fc-time').text(), "", - "should not show time for date type field"); - - calendar.destroy(); - }); - - QUnit.test('start time should not shown in month mode if hide_time is true', async function (assert) { - assert.expect(1); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return -240; - }, - }, - }); - - assert.strictEqual(calendar.$('.fc-event:contains(event 2) .fc-content .fc-time').text(), "", - "should not show time for hide_time attribute"); - - calendar.destroy(); - }); - - QUnit.test('readonly date_start field', async function (assert) { - assert.expect(4); - - this.data.event.fields.start.readonly = true; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - if (args.method === "get_formview_id") { - return Promise.resolve(false); - } - return this._super(route, args); - }, - }); - - assert.containsNone(calendar, '.fc-resizer', "should not have resize button"); - - // click on an existing event to open the form view - - testUtils.mock.intercept(calendar, 'do_action', function (event) { - assert.deepEqual(event.data.action, - { - type: "ir.actions.act_window", - res_id: 4, - res_model: "event", - views: [[false, "form"]], - target: "current", - context: {} - }, - "should open the form view"); - }); - await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content')); - await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_edit')); - - // create a new event and edit it - - var $cell = calendar.$('.fc-day-grid .fc-row:eq(4) .fc-day:eq(2)'); - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - await testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); - - testUtils.mock.intercept(calendar, 'do_action', function (event) { - assert.deepEqual(event.data.action, - { - type: "ir.actions.act_window", - res_model: "event", - views: [[false, "form"]], - target: "current", - context: { - "default_name": "coucou", - "default_start": "2016-12-27 00:00:00", - "default_stop": "2016-12-27 00:00:00", - "default_allday": true - } - }, - "should open the form view with the context default values"); - }); - - await testUtils.dom.click($('.modal button.btn:contains(Edit)')); - - calendar.destroy(); - - assert.strictEqual($('#ui-datepicker-div:empty').length, 0, "should have a clean body"); - }); - - QUnit.test('check filters with filter_field specified', async function (assert) { - assert.expect(5); - - const calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: ` - - - `, - }); - - assert.containsOnce(calendar, '.o_calendar_filter_item[data-id="2"] input:checked', - "checkbox should be checked"); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-id="2"] input')); - assert.containsNone(calendar, '.o_calendar_filter_item[data-id="2"] input:checked', - "checkbox should not be checked"); - assert.strictEqual(this.data.filter_partner.records.find(r => r.id === 2).partner_checked, false, - "the status of this filter should now be false"); - await calendar.reload(); - assert.containsNone(calendar, '.o_calendar_filter_item[data-id="2"] input:checked', - "checkbox should not be checked after the reload"); - assert.strictEqual(this.data.filter_partner.records.find(r => r.id === 2).partner_checked, false, - "the status of this filter should still be false after the reload"); - calendar.destroy(); - }); - - QUnit.test('"all" filter', async function (assert) { - assert.expect(8); - - var interval = [ - ["start", "<=", "2016-12-17 23:59:59"], - ["stop", ">=", "2016-12-11 00:00:00"], - ]; - - var domains = [ - interval.concat([["partner_ids", "in", []]]), - interval.concat([["partner_ids", "in", [1]]]), - interval.concat([["partner_ids", "in", [2,1]]]), - interval, - ]; - - var i = 0; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - '', - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - if (args.method === 'search_read' && args.model === 'event') { - assert.deepEqual(args.kwargs.domain, domains[i]); - i++; - } - return this._super.apply(this, arguments); - }, - }); - // By default, no user is selected - assert.containsN(calendar, '.fc-event', 0, - "should display 0 events on the week"); - - // Select the events only associated with partner 2 - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-id=1] input')); - assert.containsN(calendar, '.fc-event', 4, - "should display 4 events on the week"); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=2] input')); - assert.containsN(calendar, '.fc-event', 9, - "should display 9 events on the week"); - // Click on the 'all' filter to reload all events - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=all] input')); - assert.containsN(calendar, '.fc-event', 9, - "should display 9 events on the week"); - - calendar.destroy(); - }); - - QUnit.test('Add filters and specific color', async function (assert) { - assert.expect(10); - - this.data.event_type.records.push( - {id: 4, display_name: "Event Type no color", color: 0}, - ); - - this.data.event.records.push( - {id: 8, user_id: 4, partner_id: 1, name: "event 8", start: "2016-12-11 09:00:00", stop: "2016-12-11 10:00:00", allday: false, partner_ids: [1,2,3], event_type_id: 3, color: 4}, - {id: 9, user_id: 4, partner_id: 1, name: "event 9", start: "2016-12-11 19:00:00", stop: "2016-12-11 20:00:00", allday: false, partner_ids: [1,2,3], event_type_id: 1, color: 1}, - {id: 10, user_id: 4, partner_id: 1, name: "event 10", start: "2016-12-11 12:00:00", stop: "2016-12-11 13:00:00", allday: false, partner_ids: [1,2,3], event_type_id: 4, color: 0}, - ); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - '', - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - var result = this._super(route, args); - if (args.method === "search_read" && args.model === "event_type" && args.args[1][0] === "color") { - assert.step('color_search_read'); - } - return result; - }, - }); - // By default no filter is selected. We check before continuing. - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=1] input')); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=2] input')); - - assert.containsN(calendar, '.o_calendar_filter', 2, "should display 2 filters"); - - var $typeFilter = calendar.$('.o_calendar_filter:has(span:contains(Event Type))'); - assert.ok($typeFilter.length, "should display 'Event Type' filter"); - assert.containsOnce($typeFilter, '#o_cw_filter_collapse_EventType', "Id should be equals to o_cw_filter_collapse_EventType for 'Event Type'"); - assert.containsN($typeFilter, '.o_calendar_filter_item', 4, "should display 4 filter items for 'Event Type'"); - - assert.containsOnce($typeFilter, '.o_calendar_filter_item[data-value=3].o_cw_filter_color_4', "Filter for event type 3 must have the color 4"); - - assert.containsOnce(calendar, '.fc-event[data-event-id=8].o_calendar_color_4', "Event of event type 3 must have the color 4"); - assert.containsOnce(calendar, '.fc-event[data-event-id=10].o_calendar_color_1', "The first color is used when none is provided (default int field value being 0)") - assert.verifySteps(['color_search_read', 'color_search_read'], "The color attribute on a field should trigger a search_read") - calendar.destroy(); - }); - - QUnit.test('create event with filters', async function (assert) { - assert.expect(7); - - this.data.event.fields.user_id.default = 5; - this.data.event.fields.partner_id.default = 3; - this.data.user.records.push({id: 5, display_name: "user 5", partner_id: 3}); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - ''+ - '', - viewOptions: { - initialDate: initialDate, - }, - }, {positionalClicks: true}); - - // By default only - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=1] input')); - assert.containsN(calendar, '.o_calendar_filter_item', 5, "should display 5 filter items"); - assert.containsN(calendar, '.fc-event', 4, "should display 4 events"); - - // quick create a record - var left = calendar.$('.fc-bg td:eq(4)').offset().left+15; - var top = calendar.$('.fc-slats tr:eq(12) td:first').offset().top+15; - try { - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - } catch (_e) { - calendar.destroy(); - throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); - } - testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mouseup"); - await testUtils.nextTick(); - - await testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); - await testUtils.dom.click($('.modal-footer button.btn:contains(Create)')); - - assert.containsN(calendar, '.o_calendar_filter_item', 6, "should add the missing filter (active)"); - assert.containsN(calendar, '.fc-event', 5, "should display the created item"); - await testUtils.nextTick(); - - // change default value for quick create an hide record - this.data.event.fields.user_id.default = 4; - this.data.event.fields.partner_id.default = 4; - // Disable our filter to create a record without displaying it - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); - // quick create and other record - left = calendar.$('.fc-bg td:eq(3)').offset().left+15; - top = calendar.$('.fc-slats tr:eq(12) td:first').offset().top+15; - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mouseup"); - await testUtils.nextTick(); - - testUtils.fields.editInput($('.modal-body input:first'), 'coucou 2'); - await testUtils.dom.click($('.modal-footer button.btn:contains(Create)')); - - assert.containsN(calendar, '.o_calendar_filter_item', 6, "should have the same filters"); - assert.containsN(calendar, '.fc-event', 4, "should not display the created item"); - - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=2] input')); - - assert.containsN(calendar, '.fc-event', 11, "should display all records"); - - calendar.destroy(); - }); - - QUnit.test('create event with filters (no quickCreate)', async function (assert) { - assert.expect(4); - - this.data.event.fields.user_id.default = 5; - this.data.event.fields.partner_id.default = 3; - this.data.user.records.push({ - id: 5, - display_name: "user 5", - partner_id: 3 - }); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - ''+ - '', - archs: { - "event,false,form": - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - '', - }, - viewOptions: { - initialDate: initialDate, - }, - }, {positionalClicks: true}); - // dislay all attendee calendars - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=1] input')); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=2] input')); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); - - assert.containsN(calendar, '.o_calendar_filter_item', 5, "should display 5 filter items"); - assert.containsN(calendar, '.fc-event', 3, "should display 3 events"); - await testUtils.nextTick(); - - // quick create a record - var left = calendar.$('.fc-bg td:eq(4)').offset().left+15; - var top = calendar.$('.fc-slats tr:eq(12) td:first').offset().top+15; - try { - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - } catch (_e) { - calendar.destroy(); - throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); - } - testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mousemove"); - testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mouseup"); - await testUtils.nextTick(); - - await testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); - - await testUtils.dom.click($('.modal-footer button.btn:contains(Edit)')); - await testUtils.dom.click($('.modal-footer button.btn:contains(Save)')); - - assert.containsN(calendar, '.o_calendar_filter_item', 6, "should add the missing filter (active)"); - assert.containsN(calendar, '.fc-event', 4, "should display the created item"); - - calendar.destroy(); - }); - - QUnit.test('Update event with filters', async function (assert) { - assert.expect(12); - - var records = this.data.user.records; - records.push({ - id: 5, - display_name: "user 5", - partner_id: 3 - }); - - this.data.event.onchanges = { - user_id: function (obj) { - obj.partner_id = _.findWhere(records, {id:obj.user_id}).partner_id; - } - }; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - ''+ - '', - archs: { - "event,false,form": - '
'+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - '
', - }, - viewOptions: { - initialDate: initialDate, - }, - }); - // select needed partner filters - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=1] input')); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); - - assert.containsN(calendar, '.o_calendar_filter_item', 5, "should display 5 filter items"); - assert.containsN(calendar, '.fc-event', 3, "should display 3 events"); - - await testUtils.dom.click(calendar.$('.fc-event:contains(event 2) .fc-content')); - assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event"); - await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_edit')); - assert.strictEqual($('.modal .modal-title').text(), 'Open: event 2', "dialog should have a valid title"); - await testUtils.dom.click($('.modal .o_field_widget[name="user_id"] input')); - await testUtils.dom.click($('.ui-menu-item a:contains(user 5)').trigger('mouseenter')); - await testUtils.dom.click($('.modal button.btn:contains(Save)')); - - assert.containsN(calendar, '.o_calendar_filter_item', 6, "should add the missing filter (active)"); - assert.containsN(calendar, '.fc-event', 3, "should display the updated item"); - - // test the behavior of the 'select all' input checkbox - assert.containsN(calendar, '.o_calendar_filter_item input:checked', 3, "should display 3 true checkbox"); - assert.containsN(calendar, '.o_calendar_filter_item input:not(:checked)', 3, "should display 3 false checkbox"); - // Click to select all users - await testUtils.dom.click(calendar.$('.o_calendar_filter_items_checkall:eq(1) input')); - // should contains 4 events - assert.containsN(calendar, '.fc-event', 4, "should display the updated events"); - // Should have 4 checked boxes - assert.containsN(calendar, '.o_calendar_filter_item input:checked', 4, "should display 4 true checkbox"); - // unselect all user - await testUtils.dom.click(calendar.$('.o_calendar_filter_items_checkall:eq(1) input')); - assert.containsN(calendar, '.fc-event', 0, "should not display any event"); - assert.containsN(calendar, '.o_calendar_filter_item input:checked', 1, "should display 1 true checkbox"); - calendar.destroy(); - }); - - QUnit.test('change pager with filters', async function (assert) { - assert.expect(3); - - this.data.user.records.push({ - id: 5, - display_name: "user 5", - partner_id: 3 - }); - this.data.event.records.push({ - id: 8, - user_id: 5, - partner_id: 3, - name: "event 8", - start: "2016-12-06 04:00:00", - stop: "2016-12-06 08:00:00", - allday: false, - partner_ids: [1,2,3], - type: 1 - }, { - id: 9, - user_id: session.uid, - partner_id: 1, - name: "event 9", - start: "2016-12-07 04:00:00", - stop: "2016-12-07 08:00:00", - allday: false, - partner_ids: [1,2,3], - type: 1 - },{ - id: 10, - user_id: 4, - partner_id: 4, - name: "event 10", - start: "2016-12-08 04:00:00", - stop: "2016-12-08 08:00:00", - allday: false, - partner_ids: [1,2,3], - type: 1 - }); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - ''+ - '', - viewOptions: { - initialDate: initialDate, - }, - }); - // select filter for partner 1, 2 and 4 - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=1] input')[0]); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=2] input')); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); - - await testUtils.dom.click($('.o_calendar_button_prev')); - - assert.containsN(calendar, '.o_calendar_filter_item', 6, "should display 6 filter items"); - assert.containsN(calendar, '.fc-event', 2, "should display 2 events"); - assert.strictEqual(calendar.$('.fc-event .o_event_title').text().replace(/\s/g, ''), "event8event9", - "should display 2 events"); - - calendar.destroy(); - }); - - QUnit.test('ensure events are still shown if filters give an empty domain', async function (assert) { - assert.expect(2); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: '' + - '' + - '', - viewOptions: { - initialDate: initialDate, - }, - }); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=1] input')); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=2] input')); - assert.containsN(calendar, '.fc-event', 5, - "should display 5 events"); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=all] input[type=checkbox]')); - assert.containsN(calendar, '.fc-event', 5, - "should display 5 events"); - calendar.destroy(); - }); - - QUnit.test('events starting at midnight', async function (assert) { - assert.expect(3); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: '', - viewOptions: { - initialDate: initialDate, - }, - translateParameters: { // Avoid issues due to localization formats - time_format: "%H:%M:%S", - }, - }, {positionalClicks: true}); - - // Reset the scroll to 0 as we want to create an event from midnight - assert.ok(calendar.$('.fc-scroller')[0].scrollTop > 0, - "should scroll to 6:00 by default (this is true at least for resolutions up to 1900x1600)"); - calendar.$('.fc-scroller')[0].scrollTop = 0; - - // Click on Tuesday 12am - var top = calendar.$('.fc-axis:contains(0:00)').offset().top + 5; - var left = calendar.$('.fc-day:eq(2)').offset().left + 5; - try { - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); - await testUtils.nextTick(); - } catch (_e) { - calendar.destroy(); - throw new Error('The test failed to simulate a click on the screen.' + - 'Your screen is probably too small or your dev tools are open.'); - } - assert.ok($('.modal-dialog.modal-sm').length, - "should open the quick create dialog"); - - // Creating the event - testUtils.fields.editInput($('.modal-body input:first'), 'new event in quick create'); - await testUtils.dom.click($('.modal-footer button.btn:contains(Create)')); - assert.strictEqual(calendar.$('.fc-event:contains(new event in quick create)').length, 1, - "should display the new record after quick create dialog"); - - calendar.destroy(); - }); - - QUnit.test('set event as all day when field is date', async function (assert) { - assert.expect(2); - - this.data.event.records[0].start_date = "2016-12-14"; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return -480; - } - }, - }); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=1] input')); - assert.containsOnce(calendar, '.fc-day-grid .fc-event-container', - "should be one event in the all day row"); - assert.strictEqual(moment(calendar.model.data.data[0].r_start).date(), 14, - "the date should be 14"); - calendar.destroy(); - }); - - QUnit.test('set event as all day when field is date (without all_day mapping)', async function (assert) { - assert.expect(1); - - this.data.event.records[0].start_date = "2016-12-14"; - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: ``, - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - assert.containsOnce(calendar, '.fc-day-grid .fc-event-container', - "should be one event in the all day row"); - calendar.destroy(); - }); - - QUnit.test('quickcreate avoid double event creation', async function (assert) { - assert.expect(1); - var createCount = 0; - var prom = testUtils.makeTestPromise(); - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - var result = this._super(route, args); - if (args.method === "create") { - createCount++; - return prom.then(_.constant(result)); - } - return result; - }, - }); - - // create a new event - var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - - var $input = $('.modal input:first'); - await testUtils.fields.editInput($input, 'new event in quick create'); - // Simulate ENTER pressed on Create button (after a TAB) - $input.trigger($.Event('keyup', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - await testUtils.nextTick(); - await testUtils.dom.click($('.modal-footer button:first')); - prom.resolve(); - await testUtils.nextTick(); - assert.strictEqual(createCount, 1, - "should create only one event"); - - calendar.destroy(); - }); - - QUnit.test('check if the view destroys all widgets and instances', async function (assert) { - assert.expect(2); - - var instanceNumber = 0; - testUtils.mock.patch(mixins.ParentedMixin, { - init: function () { - instanceNumber++; - return this._super.apply(this, arguments); - }, - destroy: function () { - if (!this.isDestroyed()) { - instanceNumber--; - } - return this._super.apply(this, arguments); - } - }); - - var params = { - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }; - - var calendar = await createCalendarView(params); - assert.ok(instanceNumber > 0); - - calendar.destroy(); - assert.strictEqual(instanceNumber, 0); - - testUtils.mock.unpatch(mixins.ParentedMixin); - }); - - QUnit.test('create an event (async dialog) [REQUIRE FOCUS]', async function (assert) { - assert.expect(3); - - var prom = testUtils.makeTestPromise(); - testUtils.mock.patch(Dialog, { - open: function () { - var _super = this._super.bind(this); - prom.then(_super); - return this; - }, - }); - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - // create an event - var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - - assert.strictEqual($('.modal').length, 0, - "should not have opened the dialog yet"); - - prom.resolve(); - await testUtils.nextTick(); - - assert.strictEqual($('.modal').length, 1, - "should have opened the dialog"); - assert.strictEqual($('.modal input')[0], document.activeElement, - "should focus the input in the dialog"); - - calendar.destroy(); - testUtils.mock.unpatch(Dialog); - }); - - QUnit.test('calendar is configured to have no groupBy menu', async function (assert) { - assert.expect(1); - - serverData.views = { - 'event,1,calendar': '', - 'event,false,search': '', - }; - - serverData.actions = { - 1: { - id: 1, - name: 'some action', - res_model: 'event', - type: 'ir.actions.act_window', - views: [[1, 'calendar']] - }, - }; - - const target = getFixture(); - const webClient = await createWebClient({ serverData }); - - await doAction(webClient, 1); - assert.containsNone(target, ".o_control_panel .o_search_options .o_group_by_menu", - "the control panel has no groupBy menu"); - }); - - QUnit.test('timezone does not affect current day', async function (assert) { - assert.expect(2); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - session: { - getTZOffset: function () { - return -2400; // 40 hours timezone - }, - }, - - }); - - var $sidebar = calendar.$('.o_calendar_sidebar'); - - assert.strictEqual($sidebar.find('.ui-datepicker-current-day').text(), "12", "should highlight the target day"); - - // go to previous day - await testUtils.dom.click($sidebar.find('.ui-datepicker-current-day').prev()); - - assert.strictEqual($sidebar.find('.ui-datepicker-current-day').text(), "11", "should highlight the selected day"); - - calendar.destroy(); - }); - - QUnit.test('timezone does not affect drag and drop', async function (assert) { - assert.expect(10); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - if (args.method === "write") { - assert.deepEqual(args.args[0], [6], "event 6 is moved") - assert.deepEqual(args.args[1].start, "2016-11-29 08:00:00", - "event moved to 27th nov 16h00 +40 hours timezone") - } - return this._super(route, args); - }, - session: { - getTZOffset: function () { - return -2400; // 40 hours timezone - }, - }, - }); - - assert.strictEqual(calendar.$('.fc-event:eq(0)').text().replace(/\s/g, ''), "08:00event1"); - await testUtils.dom.click(calendar.$('.fc-event:eq(0)')); - assert.strictEqual(calendar.$('.o_field_widget[name="start"]').text(), "12/09/2016 08:00:00"); - - assert.strictEqual(calendar.$('.fc-event:eq(5)').text().replace(/\s/g, ''), "16:00event6"); - await testUtils.dom.click(calendar.$('.fc-event:eq(5)')); - assert.strictEqual(calendar.$('.o_field_widget[name="start"]').text(), "12/16/2016 16:00:00"); - - // Move event 6 as on first day of month view (27th november 2016) - await testUtils.dom.dragAndDrop( - calendar.$('.fc-event').eq(5), - calendar.$('.fc-day-top').first() - ); - await testUtils.nextTick(); - - assert.strictEqual(calendar.$('.fc-event:eq(0)').text().replace(/\s/g, ''), "16:00event6"); - await testUtils.dom.click(calendar.$('.fc-event:eq(0)')); - assert.strictEqual(calendar.$('.o_field_widget[name="start"]').text(), "11/27/2016 16:00:00"); - - assert.strictEqual(calendar.$('.fc-event:eq(1)').text().replace(/\s/g, ''), "08:00event1"); - await testUtils.dom.click(calendar.$('.fc-event:eq(1)')); - assert.strictEqual(calendar.$('.o_field_widget[name="start"]').text(), "12/09/2016 08:00:00"); - - calendar.destroy(); - }); - - QUnit.test('timzeone does not affect calendar with date field', async function (assert) { - assert.expect(11); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - if (args.method === "create") { - assert.strictEqual(args.args[0].start_date, "2016-12-20 00:00:00"); - } - if (args.method === "write") { - assert.step(args.args[1].start_date); - } - return this._super(route, args); - }, - session: { - getTZOffset: function () { - return 120; // 2 hours timezone - }, - }, - }); - - // Create event (on 20 december) - var $cell = calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day:eq(2)'); - await testUtils.dom.triggerMouseEvent($cell, "mousedown"); - await testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - var $input = $('.modal-body input:first'); - await testUtils.fields.editInput($input, "An event"); - await testUtils.dom.click($('.modal button.btn:contains(Create)')); - await testUtils.nextTick(); - - await testUtils.dom.click(calendar.$('.fc-event:contains(An event)')); - assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event"); - assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:last .o_field_date').text(), '12/20/2016', "should have correct start date"); - - // Move event to another day (on 27 november) - await testUtils.dom.dragAndDrop( - calendar.$('.fc-event').first(), - calendar.$('.fc-day-top').first() - ); - await testUtils.nextTick(); - assert.verifySteps(["2016-11-27 00:00:00"]); - await testUtils.dom.click(calendar.$('.fc-event:contains(An event)')); - assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event"); - assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:last .o_field_date').text(), '11/27/2016', "should have correct start date"); - - // Move event to last day (on 7 january) - await testUtils.dom.dragAndDrop( - calendar.$('.fc-event').first(), - calendar.$('.fc-day-top').last() - ); - await testUtils.nextTick(); - assert.verifySteps(["2017-01-07 00:00:00"]); - await testUtils.dom.click(calendar.$('.fc-event:contains(An event)')); - assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event"); - assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:last .o_field_date').text(), '01/07/2017', "should have correct start date"); - calendar.destroy(); - }); - - QUnit.test("drag and drop on month mode", async function (assert) { - assert.expect(3); - - const calendar = await createCalendarView({ - arch: - ` - - - `, - archs: archs, - data: this.data, - model: 'event', - View: CalendarView, - viewOptions: { initialDate: initialDate }, - }); - - // Create event (on 20 december) - var $cell = calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day:eq(2)'); - await testUtils.dom.triggerMouseEvent($cell, "mousedown"); - await testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - var $input = $('.modal-body input:first'); - await testUtils.fields.editInput($input, "An event"); - await testUtils.dom.click($('.modal button.btn-primary')); - await testUtils.nextTick(); - - await testUtils.dom.dragAndDrop( - calendar.$('.fc-event:contains("event 1")'), - calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day-top:eq(1)'), - { disableDrop: true }, - ); - assert.hasClass(calendar.$('.o_calendar_widget > [data-event-id="1"]'), 'dayGridMonth', - "should have dayGridMonth class"); - - // Move event to another day (on 19 december) - await testUtils.dom.dragAndDrop( - calendar.$('.fc-event:contains("An event")'), - calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day-top:eq(1)') - ); - await testUtils.nextTick(); - await testUtils.dom.click(calendar.$('.fc-event:contains("An event")')); - - assert.containsOnce(calendar, '.popover:contains("07:00")', - "start hour shouldn't have been changed"); - assert.containsOnce(calendar, '.popover:contains("19:00")', - "end hour shouldn't have been changed"); - - calendar.destroy(); - }); - - QUnit.test("drag and drop on month mode with all_day mapping", async function (assert) { - // Same test as before but in calendarEventToRecord (calendar_model.js) there is - // different condition branching with all_day mapping or not - assert.expect(2); - - const calendar = await createCalendarView({ - arch: - ` - - - `, - archs: archs, - data: this.data, - model: 'event', - View: CalendarView, - viewOptions: { initialDate: initialDate }, - }); - - // Create event (on 20 december) - var $cell = calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day:eq(2)'); - await testUtils.dom.triggerMouseEvent($cell, "mousedown"); - await testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - var $input = $('.modal-body input:first'); - await testUtils.fields.editInput($input, "An event"); - await testUtils.dom.click($('.o_field_widget[name="allday"] input')); - await testUtils.nextTick(); - - // use datepicker to enter a date: 12/20/2016 07:00:00 - testUtils.dom.openDatepicker($('.o_field_widget[name="start"].o_datepicker')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(07)')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]')); - - // use datepicker to enter a date: 12/20/2016 19:00:00 - testUtils.dom.openDatepicker($('.o_field_widget[name="stop"].o_datepicker')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(19)')); - await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]')); - - await testUtils.dom.click($('.modal button.btn-primary')); - await testUtils.nextTick(); - - // Move event to another day (on 19 december) - await testUtils.dom.dragAndDrop( - calendar.$('.fc-event:contains("An event")'), - calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day-top:eq(1)') - ); - await testUtils.nextTick(); - await testUtils.dom.click(calendar.$('.fc-event:contains("An event")')); - - assert.containsOnce(calendar, '.popover:contains("07:00")', - "start hour shouldn't have been changed"); - assert.containsOnce(calendar, '.popover:contains("19:00")', - "end hour shouldn't have been changed"); - - calendar.destroy(); - }); - - QUnit.test('drag and drop on month mode with date_start and date_delay', async function (assert) { - assert.expect(1); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - ''+ - ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - if (args.method === "write") { - // delay should not be written at drag and drop - assert.equal(args.args[1].delay, undefined) - } - return this._super(route, args); - }, - }); - - // Create event (on 20 december) - var $cell = calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day:eq(2)'); - await testUtils.dom.triggerMouseEvent($cell, "mousedown"); - await testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - var $input = $('.modal-body input:first'); - await testUtils.fields.editInput($input, "An event"); - await testUtils.dom.click($('.modal button.btn:contains(Create)')); - await testUtils.nextTick(); - - // Move event to another day (on 27 november) - await testUtils.dom.dragAndDrop( - calendar.$('.fc-event').first(), - calendar.$('.fc-day-top').first() - ); - await testUtils.nextTick(); - - calendar.destroy(); - }); - - QUnit.test('form_view_id attribute works (for creating events)', async function (assert) { - assert.expect(1); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - if (args.method === "create") { - // we simulate here the case where a create call with just - // the field name fails. This is a normal flow, the server - // reject the create rpc (quick create), then the web client - // fall back to a form view. This happens typically when a - // model has required fields - return Promise.reject('None shall pass!'); - } - return this._super(route, args); - }, - intercepts: { - do_action: function (event) { - assert.strictEqual(event.data.action.views[0][0], 42, - "should do a do_action with view id 42"); - }, - }, - }); - - var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - await testUtils.dom.triggerMouseEvent($cell, "mousedown"); - await testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - - var $input = $('.modal-body input:first'); - await testUtils.fields.editInput($input, "It's just a fleshwound"); - await testUtils.dom.click($('.modal button.btn:contains(Create)')); - await testUtils.nextTick(); // wait a little before to finish the test - calendar.destroy(); - }); - - QUnit.test('form_view_id attribute works with popup (for creating events)', async function (assert) { - assert.expect(1); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: ''+ - ''+ - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - mockRPC: function (route, args) { - if (args.method === "get_views") { - assert.strictEqual(args.kwargs.views[0][0], 1, - "should load view with id 1"); - } - return this._super(route, args); - }, - }); - - var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - await testUtils.dom.triggerMouseEvent($cell, "mousedown"); - await testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - calendar.destroy(); - }); - - QUnit.test('calendar fallback to form view id in action if necessary', async function (assert) { - assert.expect(1); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: '', - archs: archs, - viewOptions: { - initialDate: initialDate, - action: {views: [{viewID: 1, type: 'kanban'}, {viewID: 43, type: 'form'}]} - }, - mockRPC: function (route, args) { - if (args.method === "create") { - // we simulate here the case where a create call with just - // the field name fails. This is a normal flow, the server - // reject the create rpc (quick create), then the web client - // fall back to a form view. This happens typically when a - // model has required fields - return Promise.reject('None shall pass!'); - } - return this._super(route, args); - }, - intercepts: { - do_action: function (event) { - assert.strictEqual(event.data.action.views[0][0], 43, - "should do a do_action with view id 43"); - }, - }, - }); - - var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - - var $input = $('.modal-body input:first'); - await testUtils.fields.editInput($input, "It's just a fleshwound"); - await testUtils.dom.click($('.modal button.btn:contains(Create)')); - calendar.destroy(); - }); - - QUnit.test('fullcalendar initializes with right locale', async function (assert) { - assert.expect(1); - - var initialLocale = moment.locale(); - // This will set the locale to zz - moment.defineLocale('zz', { - longDateFormat: { - L: 'DD/MM/YYYY' - }, - weekdaysShort: ["zz1.", "zz2.", "zz3.", "zz4.", "zz5.", "zz6.", "zz7."], - }); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: '', - archs: archs, - viewOptions: { - initialDate: initialDate, - action: {views: [{viewID: 1, type: 'kanban'}, {viewID: 43, type: 'form'}]} - }, - - }); - - assert.strictEqual(calendar.$('.fc-day-header:first').text(), "zz1. 11", - 'The day should be in the given locale specific format'); - - moment.locale(initialLocale); - moment.defineLocale('zz', null); - - calendar.destroy(); - }); - - QUnit.test('initial_date given in the context', async function (assert) { - assert.expect(1); - - serverData.views = { - 'event,1,calendar': '', - 'event,false,search': '', - }; - - serverData.actions = { - 1: { - id: 1, - name: 'context initial date', - res_model: 'event', - type: 'ir.actions.act_window', - views: [[1, 'calendar']], - context: {initial_date: initialDate} - }, - }; - - const webClient = await createWebClient({ serverData }); - await doAction(webClient, 1); - await testUtils.nextTick(); - assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), - 'context initial date (December 12, 2016)', "should display day passed in the context"); - }); - - QUnit.test('default week start (US) month mode', async function (assert) { - // if not given any option, default week start is on Sunday - assert.expect(8); - - // 2019-09-12 08:00:00 - var initDate = new Date(2019, 8, 12, 8, 0, 0); - initDate = new Date(initDate.getTime() - initDate.getTimezoneOffset()*60*1000); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - '', - archs: archs, - - viewOptions: { - initialDate: initDate, - }, - mockRPC: function (route, args) { - if (args.method === 'search_read' && args.model === 'event') { - assert.deepEqual(args.kwargs.domain, [ - ["start","<=","2019-10-12 23:59:59"], - ["stop",">=","2019-09-01 00:00:00"] - ], - 'The domain to search events in should be correct'); - } - return this._super.apply(this, arguments); - } - }); - - assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Sunday", - "The first day of the week should be Sunday"); - assert.strictEqual(calendar.$('.fc-day-header').last().text(), "Saturday", - "The last day of the week should be Saturday"); - - var $firstDay = calendar.$('.fc-day-top').first(); - - assert.strictEqual($firstDay.find('.fc-week-number').text(), "36", - "The number of the week should be correct"); - assert.strictEqual($firstDay.find('.fc-day-number').text(), "1", - "The first day of the week should be 2019-09-01"); - assert.strictEqual($firstDay.data('date'), "2019-09-01", - "The first day of the week should be 2019-09-01"); - - var $lastDay = calendar.$('.fc-day-top').last(); - assert.strictEqual($lastDay.text(), "12", - "The last day of the week should be 2019-10-12"); - assert.strictEqual($lastDay.data('date'), "2019-10-12", - "The last day of the week should be 2019-10-12"); - - calendar.destroy(); - }); - - QUnit.test('European week start month mode', async function (assert) { - assert.expect(8); - - // 2019-09-12 08:00:00 - var initDate = new Date(2019, 8, 15, 8, 0, 0); - initDate = new Date(initDate.getTime() - initDate.getTimezoneOffset()*60*1000); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - '', - archs: archs, - - viewOptions: { - initialDate: initDate, - }, - translateParameters: { - week_start: 1, - }, - mockRPC: function (route, args) { - if (args.method === 'search_read' && args.model === 'event') { - assert.deepEqual(args.kwargs.domain, [ - ["start","<=","2019-10-06 23:59:59"], - ["stop",">=","2019-08-26 00:00:00"] - ], - 'The domain to search events in should be correct'); - } - return this._super.apply(this, arguments); - } - }); - - assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Monday", - "The first day of the week should be Monday"); - assert.strictEqual(calendar.$('.fc-day-header').last().text(), "Sunday", - "The last day of the week should be Sunday"); - - var $firstDay = calendar.$('.fc-day-top').first(); - assert.strictEqual($firstDay.find('.fc-week-number').text(), "35", - "The number of the week should be correct"); - assert.strictEqual($firstDay.find('.fc-day-number').text(), "26", - "The first day of the week should be 2019-09-01"); - assert.strictEqual($firstDay.data('date'), "2019-08-26", - "The first day of the week should be 2019-08-26"); - - var $lastDay = calendar.$('.fc-day-top').last(); - assert.strictEqual($lastDay.text(), "6", - "The last day of the week should be 2019-10-06"); - assert.strictEqual($lastDay.data('date'), "2019-10-06", - "The last day of the week should be 2019-10-06"); - - calendar.destroy(); - }); - - QUnit.test('Monday week start week mode', async function (assert) { - assert.expect(3); - - // 2019-09-12 08:00:00 - var initDate = new Date(2019, 8, 15, 8, 0, 0); - initDate = new Date(initDate.getTime() - initDate.getTimezoneOffset()*60*1000); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - '', - archs: archs, - - viewOptions: { - initialDate: initDate, - }, - translateParameters: { - week_start: 1, - }, - mockRPC: function (route, args) { - if (args.method === 'search_read' && args.model === 'event') { - assert.deepEqual(args.kwargs.domain, [ - ["start","<=","2019-09-15 23:59:59"], - ["stop",">=","2019-09-09 00:00:00"] - ], - 'The domain to search events in should be correct'); - } - return this._super.apply(this, arguments); - } - }); - - assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Mon 9", - "The first day of the week should be Monday the 9th"); - assert.strictEqual(calendar.$('.fc-day-header').last().text(), "Sun 15", - "The last day of the week should be Sunday the 15th"); - - calendar.destroy(); - }); - - QUnit.test('Saturday week start week mode', async function (assert) { - assert.expect(3); - - // 2019-09-12 08:00:00 - var initDate = new Date(2019, 8, 12, 8, 0, 0); - initDate = new Date(initDate.getTime() - initDate.getTimezoneOffset()*60*1000); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ''+ - '', - archs: archs, - - viewOptions: { - initialDate: initDate, - }, - translateParameters: { - week_start: 6, - }, - mockRPC: function (route, args) { - if (args.method === 'search_read' && args.model === 'event') { - assert.deepEqual(args.kwargs.domain, [ - ["start","<=","2019-09-13 23:59:59"], - ["stop",">=","2019-09-07 00:00:00"] - ], - 'The domain to search events in should be correct'); - } - return this._super.apply(this, arguments); - } - }); - - assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Sat 7", - "The first day of the week should be Saturday the 7th"); - assert.strictEqual(calendar.$('.fc-day-header').last().text(), "Fri 13", - "The last day of the week should be Friday the 13th"); - - calendar.destroy(); - }); - - QUnit.test('edit record and attempt to create a record with "create" attribute set to false', async function (assert) { - assert.expect(8); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - mockRPC: function (route, args) { - if (args.method === 'write') { - assert.deepEqual(args.args[1], {name: 'event 4 modified'}, "should update the record"); - } - return this._super.apply(this, arguments); - }, - viewOptions: { - initialDate: initialDate, - }, - }); - - // editing existing events should still be possible - // click on an existing event to open the formViewDialog - - await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content')); - - assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event"); - assert.ok(calendar.$('.o_cw_popover .o_cw_popover_edit').length, "popover should have an edit button"); - assert.ok(calendar.$('.o_cw_popover .o_cw_popover_delete').length, "popover should have a delete button"); - assert.ok(calendar.$('.o_cw_popover .o_cw_popover_close').length, "popover should have a close button"); - - await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_edit')); - - assert.ok($('.modal-body').length, "should open the form view in dialog when click on edit"); - - await testUtils.fields.editInput($('.modal-body input:first'), 'event 4 modified'); - await testUtils.dom.click($('.modal-footer button.btn:contains(Save)')); - - assert.notOk($('.modal-body').length, "save button should close the modal"); - - // creating an event should not be possible - // attempt to create a new event with create set to false - - var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - - assert.notOk($('.modal-sm').length, "shouldn't open a quick create dialog for creating a new event with create attribute set to false"); - - calendar.destroy(); - }); - - - QUnit.test('attempt to create record with "create" and "quick_add" attributes set to false', async function (assert) { - assert.expect(1); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - // attempt to create a new event with create set to false - - var $cell = calendar.$('.fc-day-grid .fc-row:eq(5) .fc-day:eq(2)'); - - testUtils.dom.triggerMouseEvent($cell, "mousedown"); - testUtils.dom.triggerMouseEvent($cell, "mouseup"); - await testUtils.nextTick(); - - assert.strictEqual($('.modal').length, 0, "shouldn't open a form view for creating a new event with create attribute set to false"); - - calendar.destroy(); - }); - - QUnit.test('attempt to create multiples events and the same day and check the ordering on month view', async function (assert) { - assert.expect(3); - /* - This test aims to verify that the order of the event in month view is coherent with their start date. - */ - var initDate = new Date(2020, 2, 12, 8, 0, 0); //12 of March - this.data.event.records = [ - {id: 1, name: "Second event", start: "2020-03-12 05:00:00", stop: "2020-03-12 07:00:00", allday: false}, - {id: 2, name: "First event", start: "2020-03-12 02:00:00", stop: "2020-03-12 03:00:00", allday: false}, - {id: 3, name: "Third event", start: "2020-03-12 08:00:00", stop: "2020-03-12 09:00:00", allday: false}, - ]; - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: ``, - archs: archs, - viewOptions: { - initialDate: initDate, - }, - }); - assert.ok(calendar.$('.o_legacy_calendar_view').find('.fc-view-container').length, "should display in the calendar"); // OK - // Testing the order of the events: by start date - assert.strictEqual(calendar.$('.o_event_title').length, 3, "3 events should be available"); // OK - assert.strictEqual(calendar.$('.o_event_title').first().text(), 'First event', "First event should be on top"); - calendar.destroy(); - }); - - QUnit.test("drag and drop 24h event on week mode", async function (assert) { - assert.expect(1); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: ` - - `, - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }, {positionalClicks: true}); - - var top = calendar.$('.fc-axis:contains(8:00)').offset().top + 5; - var left = calendar.$('.fc-day:eq(2)').offset().left + 5; - - try { - testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); - } catch (_e) { - calendar.destroy(); - throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); - } - - top = calendar.$('.fc-axis:contains(8:00)').offset().top - 5; - var leftNextDay = calendar.$('.fc-day:eq(3)').offset().left + 5; - testUtils.dom.triggerPositionalMouseEvent(leftNextDay, top, "mousemove"); - await testUtils.dom.triggerPositionalMouseEvent(leftNextDay, top, "mouseup"); - await testUtils.nextTick(); - assert.equal($('.o_field_boolean.o_field_widget[name=allday] input').is(':checked'), false, - "The event must not have the all_day active"); - calendar.destroy(); - }); - - QUnit.test('correctly display year view', async function (assert) { - assert.expect(28); - - const calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: ` - - - - - `, - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }, {positionalClicks: true}); - - // select all partner filters - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=1] input')); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=2] input')); - - // Check view - assert.containsN(calendar, '.fc-month', 12); - assert.strictEqual( - calendar.el.querySelector('.fc-month:first-child .fc-header-toolbar').textContent, - 'Jan 2016' - ); - assert.containsN(calendar.el, '.fc-bgevent', 7, - 'There should be 6 events displayed but there is 1 split on 2 weeks'); - - assert.containsN(calendar.el, '.o_calendar_hatched', 3, - 'There should be 3 events that are hatched') - - async function clickDate(date) { - const el = calendar.el.querySelector(`.fc-day-top[data-date="${date}"]`); - el.scrollIntoView(); // scroll to it as the calendar could be too small - - testUtils.dom.triggerMouseEvent(el, "mousedown"); - testUtils.dom.triggerMouseEvent(el, "mouseup"); - - await testUtils.nextTick(); - } - - assert.notOk(calendar.el.querySelector('.fc-day-top[data-date="2016-11-17"]') - .classList.contains('fc-has-event')); - await clickDate('2016-11-17'); - assert.containsNone(calendar, '.o_cw_popover'); - - assert.ok(calendar.el.querySelector('.fc-day-top[data-date="2016-11-16"]') - .classList.contains('fc-has-event')); - await clickDate('2016-11-16'); - assert.containsOnce(calendar, '.o_cw_popover'); - let popoverText = calendar.el.querySelector('.o_cw_popover') - .textContent.replace(/\s{2,}/g, ' ').trim(); - assert.strictEqual(popoverText, 'November 14-16, 2016 event 7'); - await testUtils.dom.click(calendar.el.querySelector('.o_cw_popover_close')); - assert.containsNone(calendar, '.o_cw_popover'); - - assert.ok(calendar.el.querySelector('.fc-day-top[data-date="2016-11-14"]') - .classList.contains('fc-has-event')); - await clickDate('2016-11-14'); - assert.containsOnce(calendar, '.o_cw_popover'); - popoverText = calendar.el.querySelector('.o_cw_popover') - .textContent.replace(/\s{2,}/g, ' ').trim(); - assert.strictEqual(popoverText, 'November 14-16, 2016 event 7'); - await testUtils.dom.click(calendar.el.querySelector('.o_cw_popover_close')); - assert.containsNone(calendar, '.o_cw_popover'); - - assert.notOk(calendar.el.querySelector('.fc-day-top[data-date="2016-11-13"]') - .classList.contains('fc-has-event')); - await clickDate('2016-11-13'); - assert.containsNone(calendar, '.o_cw_popover'); - - assert.notOk(calendar.el.querySelector('.fc-day-top[data-date="2016-12-10"]') - .classList.contains('fc-has-event')); - await clickDate('2016-12-10'); - assert.containsNone(calendar, '.o_cw_popover'); - - assert.ok(calendar.el.querySelector('.fc-day-top[data-date="2016-12-12"]') - .classList.contains('fc-has-event')); - await clickDate('2016-12-12'); - assert.containsOnce(calendar, '.o_cw_popover'); - popoverText = calendar.el.querySelector('.o_cw_popover') - .textContent.replace(/\s{2,}/g, ' ').trim(); - assert.strictEqual(popoverText, 'December 12, 2016 10:55 event 2 15:55 event 3'); - await testUtils.dom.click(calendar.el.querySelector('.o_cw_popover_close')); - assert.containsNone(calendar, '.o_cw_popover'); - - assert.ok(calendar.el.querySelector('.fc-day-top[data-date="2016-12-14"]') - .classList.contains('fc-has-event')); - await clickDate('2016-12-14'); - assert.containsOnce(calendar, '.o_cw_popover'); - popoverText = calendar.el.querySelector('.o_cw_popover') - .textContent.replace(/\s{2,}/g, ' ').trim(); - assert.strictEqual(popoverText, - 'December 14, 2016 event 4 December 13-20, 2016 event 5'); - await testUtils.dom.click(calendar.el.querySelector('.o_cw_popover_close')); - assert.containsNone(calendar, '.o_cw_popover'); - - assert.notOk(calendar.el.querySelector('.fc-day-top[data-date="2016-12-21"]') - .classList.contains('fc-has-event')); - await clickDate('2016-12-21'); - assert.containsNone(calendar, '.o_cw_popover'); - - calendar.destroy(); - }); - - QUnit.test('toggle filters in year view', async function (assert) { - assert.expect(42); - - const calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: ` - - - - '`, - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - // activate partner filter - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=1] input')); - await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=2] input')); - - function checkEvents(countMap) { - for (const [id, count] of Object.entries(countMap)) { - assert.containsN(calendar, `.fc-bgevent[data-event-id="${id}"]`, count); - } - } - - checkEvents({ 1: 1, 2: 1, 3: 1, 4: 1, 5: 2, 7: 1, }); - await testUtils.dom.click(calendar.el.querySelector( - '#o_cw_filter_collapse_attendees .o_calendar_filter_item[data-value="2"] label')); - checkEvents({ 1: 1, 2: 1, 3: 1, 4: 1, 5: 0, 7: 0, }); - await testUtils.dom.click(calendar.el.querySelector( - '#o_cw_filter_collapse_user .o_calendar_filter_item[data-value="1"] label')); - checkEvents({ 1: 0, 2: 0, 3: 1, 4: 0, 5: 0, 7: 0, }); - await testUtils.dom.click(calendar.el.querySelector( - '#o_cw_filter_collapse_user .o_calendar_filter_item[data-value="4"] label')); - checkEvents({ 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 7: 0, }); - await testUtils.dom.click(calendar.el.querySelector( - '#o_cw_filter_collapse_attendees .o_calendar_filter_item[data-value="1"] label')); - checkEvents({ 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 7: 0, }); - await testUtils.dom.click(calendar.el.querySelector( - '#o_cw_filter_collapse_attendees .o_calendar_filter_item[data-value="2"] label')); - checkEvents({ 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 7: 0, }); - await testUtils.dom.click(calendar.el.querySelector( - '#o_cw_filter_collapse_user .o_calendar_filter_item[data-value="4"] label')); - checkEvents({ 1: 0, 2: 0, 3: 0, 4: 0, 5: 2, 7: 0, }); - - calendar.destroy(); - }); - - QUnit.test('allowed scales', async function (assert) { - assert.expect(8); - - let calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ``, - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - assert.containsOnce(calendar, '.o_calendar_scale_buttons .o_calendar_button_day'); - assert.containsOnce(calendar, '.o_calendar_scale_buttons .o_calendar_button_week'); - assert.containsOnce(calendar, '.o_calendar_scale_buttons .o_calendar_button_month'); - assert.containsOnce(calendar, '.o_calendar_scale_buttons .o_calendar_button_year'); - - calendar.destroy(); - - calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ``, - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - assert.containsOnce(calendar, '.o_calendar_scale_buttons .o_calendar_button_day'); - assert.containsOnce(calendar, '.o_calendar_scale_buttons .o_calendar_button_week'); - assert.containsNone(calendar, '.o_calendar_scale_buttons .o_calendar_button_month'); - assert.containsNone(calendar, '.o_calendar_scale_buttons .o_calendar_button_year'); - - calendar.destroy(); - }); - - QUnit.test('click outside the popup should close it', async function (assert) { - assert.expect(4); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ``, - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - assert.containsNone(calendar, '.o_cw_popover'); - - await testUtils.dom.click(calendar.el.querySelector('.fc-event .fc-content')); - assert.containsOnce(calendar, '.o_cw_popover', - 'open popup when click on event'); - - await testUtils.dom.click(calendar.el.querySelector('.o_cw_body')); - assert.containsOnce(calendar, '.o_cw_popover', - 'keep popup openned when click inside popup'); - - await testUtils.dom.click(calendar.el.querySelector('.o_content')); - assert.containsNone(calendar, '.o_cw_popover', - 'close popup when click outside popup'); - - calendar.destroy(); - }); - - QUnit.test('calendar: disableQuickCreate in data event', async function (assert) { - assert.expect(3); - - testUtils.mock.patch(CalendarRenderer, { - _preOpenCreate: function (data) { - data = Object.assign({}, data, { - extendedProps: {disableQuickCreate: true}, - }); - return this._super.call(this, data); - }, - }); - - let calendar = await testUtils.createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: ``, - viewOptions: { - initialDate: initialDate, - }, - intercepts: { - do_action(ev) { - assert.step('do_action'); - assert.deepEqual(ev.data.action.context, { - default_start: "2016-12-12 06:30:00", - default_stop: "2016-12-12 11:00:00", - }, "should send the correct data to create events"); - }, - }, - }, {positionalClicks: true}); - - // Create event - var $initCell = calendar.$('.fc-time-grid .fc-minor[data-time="06:30:00"] .fc-widget-content:last-child'); - var $endCell = calendar.$('.fc-time-grid .fc-minor[data-time="10:30:00"] .fc-widget-content:last-child'); - - await testUtils.dom.triggerMouseEvent($initCell, "mousedown"); - await testUtils.dom.triggerMouseEvent($endCell, "mousemove"); - await testUtils.dom.triggerMouseEvent($endCell, "mouseup"); - await testUtils.nextTick(); - - assert.verifySteps(['do_action']); - - calendar.destroy(); - - testUtils.mock.unpatch(CalendarRenderer); - }); - - QUnit.test("fields are added in the right order in popover", async function (assert) { - assert.expect(3); - - const def = testUtils.makeTestPromise(); - const DeferredWidget = AbstractField.extend({ - async start() { - await this._super(...arguments); - await def; - } - }); - fieldRegistry.add("deferred_widget", DeferredWidget); - - const calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - ` - - - `, - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - await testUtils.dom.click(calendar.$(`[data-event-id="4"]`)); - assert.containsNone(calendar, ".o_cw_popover"); - - def.resolve(); - await testUtils.nextTick(); - assert.containsOnce(calendar, ".o_cw_popover"); - - assert.strictEqual( - calendar.$(".o_cw_popover .o_cw_popover_fields_secondary").text(), - "user : name : event 4" - ); - - calendar.destroy(); - delete fieldRegistry.map.deferred_widget; - }); - - QUnit.test('select events and discard create', async function (assert) { - assert.expect(4); - - var calendar = await createCalendarView({ - View: CalendarView, - model: 'event', - data: this.data, - arch: - '', - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }, { positionalClicks: true }); - - assert.equal(calendar.$('.fc-dayGridMonth-view').length, 12, "should display in year mode"); - - calendar.el.querySelector('.fc-day-top[data-date="2016-11-13"]').scrollIntoView(); - // scroll to event as the calendar could be too small - await testUtils.dom.triggerMouseEvent(calendar.$('.fc-day-top[data-date="2016-11-13"]'), "mousedown"); - await testUtils.dom.triggerMouseEvent(calendar.$('.fc-day-top[data-date="2016-11-19"]'), "mousemove"); - await testUtils.dom.triggerMouseEvent(calendar.$('.fc-day-top[data-date="2016-11-19"]'), "mouseup"); - - assert.ok($('.modal-body').length, "should open the form view in dialog when select multiple days"); - assert.hasAttrValue($('.fc-highlight'), 'colspan', "7", "should highlight 7 days"); - await testUtils.dom.click($('.modal-footer button.btn:contains(Cancel)')); - - assert.containsNone(calendar, '.fc-highlight', "should not highlight days"); - - calendar.destroy(); - }); - - QUnit.test("popover ignores readonly field modifier", async function (assert) { - assert.expect(1); - - var calendar = await createCalendarView({ - View: CalendarView, - model: "event", - data: this.data, - arch: ` - - - - `, - archs: archs, - viewOptions: { - initialDate: initialDate, - }, - }); - - await testUtils.dom.click(calendar.$(`[data-event-id="4"]`)); - // test would fail here if we don't ignore readonly modifier - assert.containsOnce(calendar, ".o_cw_popover"); - - calendar.destroy(); - }); -}); - -});