diff --git a/addons/lunch/__manifest__.py b/addons/lunch/__manifest__.py index 452d93ff0ca..1142e121292 100644 --- a/addons/lunch/__manifest__.py +++ b/addons/lunch/__manifest__.py @@ -42,36 +42,23 @@ If you want to save your employees' time and avoid them to always have coins in 'application': True, 'assets': { 'web.assets_backend': [ + 'lunch/static/src/components/*.js', + 'lunch/static/src/components/*.scss', + 'lunch/static/src/mixins/*.js', + 'lunch/static/src/views/*.js', 'lunch/static/src/scss/lunch_view.scss', 'lunch/static/src/scss/lunch_kanban.scss', 'lunch/static/src/scss/lunch_list.scss', - 'lunch/static/src/js/lunch_controller_common.js', - 'lunch/static/src/js/lunch_widget.js', - 'lunch/static/src/js/lunch_mobile.js', - 'lunch/static/src/js/lunch_payment_dialog.js', - 'lunch/static/src/js/lunch_kanban_view.js', - 'lunch/static/src/js/lunch_kanban_controller.js', - 'lunch/static/src/js/lunch_kanban_renderer.js', - 'lunch/static/src/js/lunch_kanban_record.js', - 'lunch/static/src/js/lunch_model_extension.js', - 'lunch/static/src/js/lunch_list_view.js', - 'lunch/static/src/js/lunch_list_controller.js', - 'lunch/static/src/js/lunch_list_renderer.js', ], 'web.assets_tests': [ 'lunch/static/tests/tours/*.js', ], 'web.qunit_suite_tests': [ - 'lunch/static/tests/lunch_test_utils.js', 'lunch/static/tests/lunch_kanban_tests.js', - 'lunch/static/tests/lunch_list_tests.js', - ], - 'web.qunit_mobile_suite_tests': [ - 'lunch/static/tests/lunch_test_utils.js', - 'lunch/static/tests/lunch_kanban_mobile_tests.js', ], 'web.assets_qweb': [ - 'lunch/static/src/xml/lunch_templates.xml', + 'lunch/static/src/components/*.xml', + 'lunch/static/src/views/*.xml', ], }, 'license': 'LGPL-3', diff --git a/addons/lunch/data/lunch_demo.xml b/addons/lunch/data/lunch_demo.xml index 4a37949894c..dbf82ad216e 100644 --- a/addons/lunch/data/lunch_demo.xml +++ b/addons/lunch/data/lunch_demo.xml @@ -50,6 +50,7 @@ Coca Cola 2.9 + @@ -214,6 +215,7 @@ 3.0 + diff --git a/addons/lunch/static/src/components/lunch_dashboard.js b/addons/lunch/static/src/components/lunch_dashboard.js new file mode 100644 index 00000000000..b748c7b0082 --- /dev/null +++ b/addons/lunch/static/src/components/lunch_dashboard.js @@ -0,0 +1,179 @@ +/** @odoo-module */ + +import { useBus, useService } from "@web/core/utils/hooks"; +import { Many2XAutocomplete } from "@web/views/fields/relational_utils"; + +const { Component, useState, onWillStart, markup, xml } = owl; + +export class LunchCurrency extends Component { + get amount() { + return parseFloat(this.props.amount).toFixed(2); + } +} +LunchCurrency.template = 'lunch.LunchCurrency'; +LunchCurrency.props = ["currency", "amount"]; + +export class LunchOrderLine extends Component { + setup() { + super.setup(); + this.orm = useService('orm'); + this.state = useState({ mobileOpen: false }); + } + + get line() { + return this.props.line; + } + + get canEdit() { + return !['sent', 'confirmed'].includes(this.line.raw_state); + } + + get badgeClass() { + const mapping = {'new': 'bg-warning', 'confirmed': 'bg-success', 'sent': 'bg-info', 'ordered': 'bg-danger'}; + return mapping[this.line.raw_state]; + } + + get hasToppings() { + return this.line.toppings.length !== 0; + } + + async updateQuantity(increment) { + await this.orm.call('lunch.order', 'update_quantity', [ + this.props.line.id, + increment + ]); + + await this.props.onUpdateQuantity(); + } +} +LunchOrderLine.template = 'lunch.LunchOrderLine'; +LunchOrderLine.props = ["line", "currency", "onUpdateQuantity", "openOrderLine"]; +LunchOrderLine.components = { + LunchCurrency, +}; + +export class LunchAlert extends Component { + get message() { + return markup(this.props.message); + } +} +LunchAlert.props = ["message"]; +LunchAlert.template = xml`` + +export class LunchAlerts extends Component {} +LunchAlerts.components = { + LunchAlert, +} +LunchAlerts.props = ["alerts"]; +LunchAlerts.template = 'lunch.LunchAlerts'; + +export class LunchUser extends Component { + getDomain() { + return [['share', '=', false]]; + } +} +LunchUser.components = { + Many2XAutocomplete, +} +LunchUser.props = ["username", "isManager", "onUpdateUser"]; +LunchUser.template = "lunch.LunchUser"; + +export class LunchLocation extends Component { + getDomain() { + return []; + } +} +LunchLocation.components = { + Many2XAutocomplete, +} +LunchLocation.props = ["location", "onUpdateLunchLocation"]; +LunchLocation.template = "lunch.LunchLocation"; + +export class LunchDashboard extends Component { + setup() { + super.setup(); + this.rpc = useService("rpc"); + this.user = useService("user"); + this.state = useState({ + infos: {}, + }); + + useBus(this.env.bus, 'lunch_update_dashboard', () => this._fetchLunchInfos()); + onWillStart(async () => { + await this._fetchLunchInfos() + this.env.searchModel.updateLocationId(this.state.infos.user_location[0]); + }); + } + + async lunchRpc(route, args = {}) { + return await this.rpc(route, { + ...args, + context: this.user.context, + user_id: this.env.searchModel.lunchState.userId, + }) + } + + async _fetchLunchInfos() { + this.state.infos = await this.lunchRpc('/lunch/infos'); + } + + async emptyCart() { + await this.lunchRpc('/lunch/trash'); + await this._fetchLunchInfos(); + } + + get hasLines() { + return this.state.infos.lines && this.state.infos.lines.length !== 0; + } + + get canOrder() { + return this.state.infos.raw_state === 'new'; + } + + get location() { + return this.state.infos.user_location && this.state.infos.user_location[1]; + } + + async orderNow() { + if (!this.canOrder) { + return; + } + + await this.lunchRpc('/lunch/pay'); + await this._fetchLunchInfos(); + } + + async onUpdateQuantity() { + await this._fetchLunchInfos(); + } + + async onUpdateUser(value) { + if (!value) { + return; + } + this.env.searchModel.updateUserId(value[0].id); + await this._fetchLunchInfos(); + } + + async onUpdateLunchLocation(value) { + if (!value) { + return; + } + + await this.lunchRpc('/lunch/user_location_set', { + location_id: value[0].id, + }); + await this._fetchLunchInfos(); + this.env.searchModel.updateLocationId(value[0].id); + } +} +LunchDashboard.components = { + LunchAlerts, + LunchCurrency, + LunchLocation, + LunchOrderLine, + LunchUser, + Many2XAutocomplete, +}; +LunchDashboard.props = ["openOrderLine"]; +LunchDashboard.template = 'lunch.LunchDashboard'; diff --git a/addons/lunch/static/src/components/lunch_dashboard.scss b/addons/lunch/static/src/components/lunch_dashboard.scss new file mode 100644 index 00000000000..d6bc1d508c6 --- /dev/null +++ b/addons/lunch/static/src/components/lunch_dashboard.scss @@ -0,0 +1,9 @@ +.lunch_topping:before { + content: '+ '; +} + +.o_lunch_content { + .o-autocomplete--input { + cursor: pointer; + } +} diff --git a/addons/lunch/static/src/components/lunch_dashboard.xml b/addons/lunch/static/src/components/lunch_dashboard.xml new file mode 100644 index 00000000000..7778036efc2 --- /dev/null +++ b/addons/lunch/static/src/components/lunch_dashboard.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + +
+
+
+ + + + + + + + +
+
    +
  • + + +
  • +
+
+ + + + + + + +
+ + +
+
+ + +
+ +
+
+ + + + +
+
+
+
+
+ +
+
+ + + + +
+ Your Account + +
+
+
+
+
+

+ Your Order +

+
    +
  • + +
  • +
+
+
+

+ + Total + + +

+ +
+
+
+
+ + + + + + + +
+ + + Your Cart () + + + +
+
+
+ diff --git a/addons/lunch/static/src/js/lunch_controller_common.js b/addons/lunch/static/src/js/lunch_controller_common.js deleted file mode 100644 index 95f3b2bfd6c..00000000000 --- a/addons/lunch/static/src/js/lunch_controller_common.js +++ /dev/null @@ -1,219 +0,0 @@ -odoo.define('lunch.LunchControllerCommon', function (require) { -"use strict"; - -/** - * This file defines the common events and functions used by Controllers for the Lunch view. - */ - -var session = require('web.session'); -var core = require('web.core'); -const {Markup} = require('web.utils'); -var LunchWidget = require('lunch.LunchWidget'); -var LunchPaymentDialog = require('lunch.LunchPaymentDialog'); - -var _t = core._t; - -var LunchControllerCommon = { - custom_events: { - add_product: '_onAddProduct', - change_location: '_onLocationChanged', - change_user: '_onUserChanged', - open_wizard: '_onOpenWizard', - order_now: '_onOrderNow', - remove_product: '_onRemoveProduct', - unlink_order: '_onUnlinkOrder', - }, - /** - * @override - */ - init: function () { - this._super.apply(this, arguments); - this.editMode = false; - this.updated = false; - this.widgetData = null; - this.context = session.user_context; - this.archiveEnabled = false; - }, - /** - * @override - */ - start: function () { - // create a div inside o_content that will be used to wrap the lunch - // banner and renderer (this is required to get the desired - // layout with the searchPanel to the left) - var self = this; - this.$('.o_content').append($('
').addClass('o_lunch_content')); - return this._super.apply(this, arguments).then(function () { - self.$('.o_lunch_content').append(self.$('.o_lunch_view')); - }); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - _fetchPaymentInfo: function () { - return this._rpc({ - route: '/lunch/payment_message', - params: { - context: this.context, - }, - }); - }, - async _fetchWidgetData() { - const widgetData = await this._rpc({ - route: '/lunch/infos', - params: { - user_id: this.searchModel.get('userId'), - context: this.context, - }, - }); - widgetData.wallet = parseFloat(widgetData.wallet).toFixed(2); - (widgetData.alerts || []).forEach(alert => { alert.message = Markup(alert.message); }); - this.widgetData = widgetData; - }, - /** - * Renders and appends the lunch banner widget. - * - * @private - */ - _renderLunchWidget: function () { - var oldWidget = this.widget; - this.widget = new LunchWidget(this, Object.assign(this.widgetData, {edit: this.editMode})); - return this.widget.appendTo(document.createDocumentFragment()).then(() => { - this.$('.o_lunch_content').prepend(this.widget.$el); - if (oldWidget) { - oldWidget.destroy(); - } - }); - }, - _showPaymentDialog: function (title) { - var self = this; - - title = title || ''; - - this._fetchPaymentInfo().then(function (data) { - var paymentDialog = new LunchPaymentDialog(self, _.extend(data, {title: title})); - paymentDialog.open(); - }); - }, - /** - * Override to fetch and display the lunch data. Because of the presence of - * the searchPanel, also wrap the lunch widget and the renderer into - * a div, to get the desired layout. - * - * @override - * @private - */ - _update: function () { - var def = this._fetchWidgetData().then(this._renderLunchWidget.bind(this)); - return Promise.all([def, this._super.apply(this, arguments)]); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - _onAddProduct: function (ev) { - var self = this; - ev.stopPropagation(); - - this._rpc({ - model: 'lunch.order', - method: 'update_quantity', - args: [[ev.data.lineId], 1], - }).then(function () { - self.reload(); - }); - }, - _onLocationChanged: function (ev) { - ev.stopPropagation(); - this.searchModel.dispatch('setLocationId', ev.data.locationId); - }, - _onOpenWizard: function (ev) { - var self = this; - ev.stopPropagation(); - - var ctx = this.searchModel.get('userId') ? {default_user_id: this.searchModel.get('userId')} : {}; - - var options = { - on_close: function () { - self.reload(); - }, - }; - - var action = { - res_model: 'lunch.order', - name: _t('Configure Your Order'), - type: 'ir.actions.act_window', - views: [[false, 'form']], - target: 'new', - context: _.extend(ctx, {default_product_id: ev.data.productId}), - }; - - if (ev.data.lineId) { - action = _.extend(action, { - res_id: ev.data.lineId, - context: _.extend(action.context, { - active_id: ev.data.lineId, - }), - }); - } - - this.do_action(action, options); - }, - _onOrderNow: function (ev) { - var self = this; - ev.stopPropagation(); - - this._rpc({ - route: '/lunch/pay', - params: { - user_id: this.searchModel.get('userId'), - context: this.context, - }, - }).then(function (isPaid) { - if (isPaid) { - // TODO: feedback? - self.reload(); - } else { - self._showPaymentDialog(_t("Not enough money in your wallet")); - self.reload(); - } - }); - }, - _onRemoveProduct: function (ev) { - var self = this; - ev.stopPropagation(); - - this._rpc({ - model: 'lunch.order', - method: 'update_quantity', - args: [[ev.data.lineId], -1], - }).then(function () { - self.reload(); - }); - }, - _onUserChanged: function (ev) { - ev.stopPropagation(); - this.searchModel.dispatch('updateUserId', ev.data.userId); - }, - _onUnlinkOrder: function (ev) { - var self = this; - ev.stopPropagation(); - - this._rpc({ - route: '/lunch/trash', - params: { - user_id: this.searchModel.get('userId'), - context: this.context, - }, - }).then(function () { - self.reload(); - }); - }, -}; - -return LunchControllerCommon; - -}); diff --git a/addons/lunch/static/src/js/lunch_kanban_controller.js b/addons/lunch/static/src/js/lunch_kanban_controller.js deleted file mode 100644 index dc370a46277..00000000000 --- a/addons/lunch/static/src/js/lunch_kanban_controller.js +++ /dev/null @@ -1,18 +0,0 @@ -odoo.define('lunch.LunchKanbanController', function (require) { -"use strict"; - -/** - * This file defines the Controller for the Lunch Kanban view, which is an - * override of the KanbanController. - */ - -var KanbanController = require('web.KanbanController'); -var LunchControllerCommon = require('lunch.LunchControllerCommon'); - -var LunchKanbanController = KanbanController.extend(LunchControllerCommon , { - custom_events: _.extend({}, KanbanController.prototype.custom_events, LunchControllerCommon.custom_events), -}); - -return LunchKanbanController; - -}); diff --git a/addons/lunch/static/src/js/lunch_kanban_record.js b/addons/lunch/static/src/js/lunch_kanban_record.js deleted file mode 100644 index f58463b26ae..00000000000 --- a/addons/lunch/static/src/js/lunch_kanban_record.js +++ /dev/null @@ -1,38 +0,0 @@ -odoo.define('lunch.LunchKanbanRecord', function (require) { - "use strict"; - - /** - * This file defines the KanbanRecord for the Lunch Kanban view. - */ - - var KanbanRecord = require('web.KanbanRecord'); - - var LunchKanbanRecord = KanbanRecord.extend({ - events: _.extend({}, KanbanRecord.prototype.events, { - 'click': '_onSelectRecord', - }), - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Open the add product wizard - * - * @private - * @param {MouseEvent} ev Click event - */ - _onSelectRecord: function (ev) { - ev.preventDefault(); - // ignore clicks on oe_kanban_action elements - if (!$(ev.target).hasClass('oe_kanban_action')) { - this.trigger_up('open_wizard', {productId: this.recordData.product_id ? this.recordData.product_id.res_id: this.recordData.id}); - } - }, - - _openRecord() {}, - }); - - return LunchKanbanRecord; - - }); diff --git a/addons/lunch/static/src/js/lunch_kanban_renderer.js b/addons/lunch/static/src/js/lunch_kanban_renderer.js deleted file mode 100644 index bea87992928..00000000000 --- a/addons/lunch/static/src/js/lunch_kanban_renderer.js +++ /dev/null @@ -1,29 +0,0 @@ -odoo.define('lunch.LunchKanbanRenderer', function (require) { -"use strict"; - -/** - * This file defines the Renderer for the Lunch Kanban view, which is an - * override of the KanbanRenderer. - */ - -var LunchKanbanRecord = require('lunch.LunchKanbanRecord'); - -var KanbanRenderer = require('web.KanbanRenderer'); - -var LunchKanbanRenderer = KanbanRenderer.extend({ - config: _.extend({}, KanbanRenderer.prototype.config, { - KanbanRecord: LunchKanbanRecord, - }), - - /** - * @override - */ - start: function () { - this.$el.addClass('o_lunch_view o_lunch_kanban_view position-relative align-content-start flex-grow-1 flex-shrink-1'); - return this._super.apply(this, arguments); - }, -}); - -return LunchKanbanRenderer; - -}); diff --git a/addons/lunch/static/src/js/lunch_kanban_view.js b/addons/lunch/static/src/js/lunch_kanban_view.js deleted file mode 100644 index 50d347de309..00000000000 --- a/addons/lunch/static/src/js/lunch_kanban_view.js +++ /dev/null @@ -1,33 +0,0 @@ -odoo.define('lunch.LunchKanbanView', function (require) { -"use strict"; - -var LunchKanbanController = require('lunch.LunchKanbanController'); -var LunchKanbanRenderer = require('lunch.LunchKanbanRenderer'); - -var core = require('web.core'); -var KanbanView = require('web.KanbanView'); -var view_registry = require('web.view_registry'); - -var _lt = core._lt; - -var LunchKanbanView = KanbanView.extend({ - config: _.extend({}, KanbanView.prototype.config, { - Controller: LunchKanbanController, - Renderer: LunchKanbanRenderer, - }), - display_name: _lt('Lunch Kanban'), - - /** - * @override - */ - _createSearchModel(params, extraExtensions = {}) { - Object.assign(extraExtensions, { Lunch: {} }); - return this._super(params, extraExtensions); - }, -}); - -view_registry.add('lunch_kanban', LunchKanbanView); - -return LunchKanbanView; - -}); diff --git a/addons/lunch/static/src/js/lunch_list_controller.js b/addons/lunch/static/src/js/lunch_list_controller.js deleted file mode 100644 index bdfbab6b107..00000000000 --- a/addons/lunch/static/src/js/lunch_list_controller.js +++ /dev/null @@ -1,18 +0,0 @@ -odoo.define('lunch.LunchListController', function (require) { -"use strict"; - -/** - * This file defines the Controller for the Lunch List view, which is an - * override of the ListController. - */ - -var ListController = require('web.ListController'); -var LunchControllerCommon = require('lunch.LunchControllerCommon'); - -var LunchListController = ListController.extend(LunchControllerCommon, { - custom_events: _.extend({}, ListController.prototype.custom_events, LunchControllerCommon.custom_events), -}); - -return LunchListController; - -}); diff --git a/addons/lunch/static/src/js/lunch_list_renderer.js b/addons/lunch/static/src/js/lunch_list_renderer.js deleted file mode 100644 index 0ab0b5c9c13..00000000000 --- a/addons/lunch/static/src/js/lunch_list_renderer.js +++ /dev/null @@ -1,56 +0,0 @@ -odoo.define('lunch.LunchListRenderer', function (require) { -"use strict"; - -/** - * This file defines the Renderer for the Lunch List view, which is an - * override of the ListRenderer. - */ - -var ListRenderer = require('web.ListRenderer'); - -var LunchListRenderer = ListRenderer.extend({ - events: _.extend({}, ListRenderer.prototype.events, { - 'click .o_data_row': '_onClickListRow', - }), - - /** - * @override - */ - start: function () { - this.$el.addClass('o_lunch_view o_lunch_list_view'); - return this._super.apply(this, arguments); - }, - /** - * Override to add id of product_id in dataset. - * - * @override - */ - _renderRow: function (record) { - var tr = this._super.apply(this, arguments); - tr.attr('data-product-id', record.data.id); - return tr; - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Open the add product wizard - * - * @private - * @param {MouseEvent} ev Click event - */ - _onClickListRow: function (ev) { - ev.preventDefault(); - var productId = ev.currentTarget.dataset && ev.currentTarget.dataset.productId ? parseInt(ev.currentTarget.dataset.productId) : null; - - if (productId) { - this.trigger_up('open_wizard', {productId: productId}); - } - }, -}); - -return LunchListRenderer; - -}); diff --git a/addons/lunch/static/src/js/lunch_list_view.js b/addons/lunch/static/src/js/lunch_list_view.js deleted file mode 100644 index 00e8efa5aba..00000000000 --- a/addons/lunch/static/src/js/lunch_list_view.js +++ /dev/null @@ -1,38 +0,0 @@ -odoo.define('lunch.LunchListView', function (require) { -"use strict"; - -var LunchListController = require('lunch.LunchListController'); -var LunchListRenderer = require('lunch.LunchListRenderer'); - -var core = require('web.core'); -var ListView = require('web.ListView'); -var view_registry = require('web.view_registry'); - -var _lt = core._lt; - -var LunchListView = ListView.extend({ - config: _.extend({}, ListView.prototype.config, { - Controller: LunchListController, - Renderer: LunchListRenderer, - }), - display_name: _lt('Lunch List'), - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @override - */ - _createSearchModel(params, extraExtensions = {}) { - Object.assign(extraExtensions, { Lunch: {} }); - return this._super(params, extraExtensions); - }, - -}); - -view_registry.add('lunch_list', LunchListView); - -return LunchListView; - -}); diff --git a/addons/lunch/static/src/js/lunch_mobile.js b/addons/lunch/static/src/js/lunch_mobile.js deleted file mode 100644 index f6ad1e92081..00000000000 --- a/addons/lunch/static/src/js/lunch_mobile.js +++ /dev/null @@ -1,78 +0,0 @@ -odoo.define('lunch.LunchMobile', function (require) { -"use strict"; - -var config = require('web.config'); -var LunchWidget = require('lunch.LunchWidget'); -var LunchKanbanController = require('lunch.LunchKanbanController'); -var LunchListController = require('lunch.LunchListController'); - -if (!config.device.isMobile) { - return; -} - -LunchWidget.include({ - template: "LunchWidgetMobile", - - /** - * Override to set the toggle state allowing initially open it. - * - * @override - */ - init: function (parent, params) { - this._super.apply(this, arguments); - this.keepOpen = params.keepOpen || undefined; - }, -}); - -var mobileFunctions = { - /** - * @override - */ - init: function () { - this._super.apply(this, arguments); - this.openWidget = false; - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Override to add the widget's toggle state to its data. - * - * @override - * @private - */ - _renderLunchWidget: function () { - this.widgetData.keepOpen = this.openWidget; - this.openWidget = false; - return this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @override - * @private - */ - _onAddProduct: function () { - this.openWidget = true; - this._super.apply(this, arguments); - }, - - /** - * @override - * @private - */ - _onRemoveProduct: function () { - this.openWidget = true; - this._super.apply(this, arguments); - }, -}; - -LunchKanbanController.include(mobileFunctions); -LunchListController.include(mobileFunctions); - -}); diff --git a/addons/lunch/static/src/js/lunch_model.js b/addons/lunch/static/src/js/lunch_model.js deleted file mode 100644 index 720b43565ec..00000000000 --- a/addons/lunch/static/src/js/lunch_model.js +++ /dev/null @@ -1,130 +0,0 @@ -odoo.define('lunch.LunchModel', function (require) { -"use strict"; - -/** - * This file defines the Model for the Lunch Kanban view, which is an - * override of the KanbanModel. - */ - -var session = require('web.session'); -var BasicModel = require('web.BasicModel'); - -var LunchModel = BasicModel.extend({ - init: function () { - this.locationId = false; - this.userId = false; - this._promInitLocation = null; - - this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @return {Promise} resolved with the location domain - */ - getLocationDomain: function () { - var self = this; - return this._initUserLocation().then(function () { - return self._buildLocationDomainLeaf() ? [self._buildLocationDomainLeaf()]: []; - }); - }, - __load: function () { - var self = this; - var args = arguments; - var _super = this._super; - - return this._initUserLocation().then(function () { - var params = args[0]; - self._addOrUpdate(params.domain, self._buildLocationDomainLeaf()); - - return _super.apply(self, args); - }); - }, - __reload: function (id, options) { - var domain = options && options.domain || this.localData[id].domain; - - this._addOrUpdate(domain, this._buildLocationDomainLeaf()); - options = _.extend(options, {domain: domain}); - - return this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - _addOrUpdate: function (domain, subDomain) { - if (subDomain && subDomain.length) { - var key = subDomain[0]; - var index = _.findIndex(domain, function (val) { - return val[0] === key; - }); - - if (index < 0) { - domain.push(subDomain); - } else { - domain[index] = subDomain; - } - - return domain; - } - - return domain; - }, - /** - * Builds the domain leaf corresponding to the current user's location - * - * @private - * @return {(Array[])|undefined} - */ - _buildLocationDomainLeaf: function () { - if (this.locationId) { - return ['is_available_at', 'in', [this.locationId]]; - } - }, - _getUserLocation: function () { - return this._rpc({ - route: '/lunch/user_location_get', - params: { - context: session.user_context, - user_id: this.userId, - }, - }); - }, - /** - * Gets the user location once. - * Can be triggered from anywhere - * Useful to inject the location domain in the search panel - * - * @private - * @return {Promise} - */ - _initUserLocation: function () { - var self = this; - if (!this._promInitLocation) { - this._promInitLocation = new Promise(function (resolve) { - self._getUserLocation().then(function (locationId) { - self.locationId = locationId; - resolve(); - }); - }); - } - return this._promInitLocation; - }, - _updateLocation: function (locationId) { - this.locationId = locationId; - return Promise.resolve(); - }, - _updateUser: function (userId) { - this.userId = userId; - this._promInitLocation = null; - return this._initUserLocation(); - } -}); - -return LunchModel; - -}); diff --git a/addons/lunch/static/src/js/lunch_model_extension.js b/addons/lunch/static/src/js/lunch_model_extension.js deleted file mode 100644 index 15b03d31e56..00000000000 --- a/addons/lunch/static/src/js/lunch_model_extension.js +++ /dev/null @@ -1,100 +0,0 @@ -odoo.define("lunch/static/src/js/lunch_model_extension.js", function (require) { - "use strict"; - - const ActionModel = require("web.ActionModel"); - - class LunchModelExtension extends ActionModel.Extension { - - //--------------------------------------------------------------------- - // Public - //--------------------------------------------------------------------- - - /** - * @override - * @returns {any} - */ - get(property) { - switch (property) { - case "domain": return this.getDomain(); - case "userId": return this.state.userId; - } - } - - /** - * @override - */ - async load() { - await this._updateLocationId(); - } - - /** - * @override - */ - prepareState() { - Object.assign(this.state, { - locationId: null, - userId: null, - }); - } - - //--------------------------------------------------------------------- - // Actions / Getters - //--------------------------------------------------------------------- - - /** - * @returns {Array[] | null} - */ - getDomain() { - if (this.state.locationId) { - return [["is_available_at", "in", [this.state.locationId]]]; - } - return null; - } - - /** - * @param {number} locationId - * @returns {Promise} - */ - setLocationId(locationId) { - this.state.locationId = locationId; - this.env.services.rpc({ - route: "/lunch/user_location_set", - params: { - context: this.env.session.user_context, - location_id: this.state.locationId, - user_id: this.state.userId, - }, - }); - } - - /** - * @param {number} userId - * @returns {Promise} - */ - updateUserId(userId) { - this.state.userId = userId; - this.shouldLoad = true; - } - - //--------------------------------------------------------------------- - // Private - //--------------------------------------------------------------------- - - /** - * @returns {Promise} - */ - async _updateLocationId() { - this.state.locationId = await this.env.services.rpc({ - route: "/lunch/user_location_get", - params: { - context: this.env.session.user_context, - user_id: this.state.userId, - }, - }); - } - } - - ActionModel.registry.add("Lunch", LunchModelExtension, 20); - - return LunchModelExtension; -}); diff --git a/addons/lunch/static/src/js/lunch_payment_dialog.js b/addons/lunch/static/src/js/lunch_payment_dialog.js deleted file mode 100644 index ae624403f12..00000000000 --- a/addons/lunch/static/src/js/lunch_payment_dialog.js +++ /dev/null @@ -1,20 +0,0 @@ -odoo.define('lunch.LunchPaymentDialog', function (require) { -"use strict"; - -var Dialog = require('web.Dialog'); - -var LunchPaymentDialog = Dialog.extend({ - template: 'lunch.LunchPaymentDialog', - - init: function (parent, options) { - this._super.apply(this, arguments); - - options = options || {}; - - this.message = options.message || ''; - }, -}); - -return LunchPaymentDialog; - -}); diff --git a/addons/lunch/static/src/js/lunch_widget.js b/addons/lunch/static/src/js/lunch_widget.js deleted file mode 100644 index c58cdb6df3f..00000000000 --- a/addons/lunch/static/src/js/lunch_widget.js +++ /dev/null @@ -1,164 +0,0 @@ -odoo.define('lunch.LunchWidget', function (require) { -"use strict"; - -var core = require('web.core'); -var relationalFields = require('web.relational_fields'); -var session = require('web.session'); -var Widget = require('web.Widget'); - -var _t = core._t; -var FieldMany2One = relationalFields.FieldMany2One; - - -var LunchMany2One = FieldMany2One.extend({ - start: function () { - this.$el.addClass('w-100'); - return this._super.apply(this, arguments); - } -}); - -var LunchWidget = Widget.extend({ - template: 'LunchWidget', - custom_events: { - field_changed: '_onFieldChanged', - }, - events: { - 'click .o_add_product': '_onAddProduct', - 'click .o_lunch_widget_order_button': '_onOrderNow', - 'click .o_remove_product': '_onRemoveProduct', - 'click .o_lunch_widget_unlink': '_onUnlinkOrder', - 'click .o_lunch_open_wizard': '_onLunchOpenWizard', - }, - - init: function (parent, params) { - this._super.apply(this, arguments); - - this.is_manager = params.is_manager || false; - this.group_portal_id = params.group_portal_id || false; - this.userimage = params.userimage || ''; - this.username = params.username || ''; - - this.lunchUserField = null; - - this.locations = params.locations || []; - this.userLocation = params.user_location[1] || ''; - - const company_ids = [false].concat(session.user_context.allowed_company_ids || []); - this.lunchLocationField = this._createMany2One('locations', 'lunch.location', this.userLocation, () => [ - ['company_id', 'in', company_ids] - ]); - - this.wallet = params.wallet || 0; - this.raw_state = params.raw_state || 'new'; - this.state = params.state || _t('To Order'); - this.lines = params.lines || []; - this.total = params.total || 0; - - this.alerts = params.alerts || []; - - this.currency = params.currency || session.get_currency(session.company_currency_id); - }, - willStart: function () { - var superDef = this._super.apply(this, arguments); - - if (this.is_manager) { - this.lunchUserField = this._createMany2One('users', 'res.users', this.username, - () => [['groups_id', 'not in', [this.group_portal_id]]] - ); - } - return superDef; - }, - renderElement: function () { - this._super.apply(this, arguments); - if (this.lunchUserField) { - this.lunchUserField.appendTo(this.$('.o_lunch_user_field')); - } else { - this.$('.o_lunch_user_field').text(this.username); - } - - if (this.userLocation) { - this.lunchLocationField.appendTo(this.$('.o_lunch_location_field')); - } - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - _createMany2One: function (name, model, value, domain, context) { - var fields = {}; - fields[name] = {type: 'many2one', relation: model, string: name}; - var data = {}; - data[name] = {data: {display_name: value}}; - - var record = { - id: name, - res_id: 1, - model: 'dummy', - fields: fields, - fieldsInfo: { - default: fields, - }, - data: data, - getDomain: domain || function () { return []; }, - getContext: context || function () { return {}; }, - }; - var options = { - mode: 'edit', - noOpen: true, - attrs: { - can_create: false, - can_write: false, - } - }; - return new LunchMany2One(this, name, record, options); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - _onAddProduct: function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - this.trigger_up('add_product', {lineId: $(ev.currentTarget).data('id')}); - }, - _onOrderNow: function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - - this.trigger_up('order_now', {}); - }, - _onLunchOpenWizard: function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - - var target = $(ev.currentTarget); - this.trigger_up('open_wizard', {productId: target.data('product-id'), lineId: target.data('id')}); - }, - _onFieldChanged: function (ev) { - ev.stopPropagation(); - - if (ev.data.dataPointID === 'users') { - this.trigger_up('change_user', {userId: ev.data.changes.users.id}); - } else if (ev.data.dataPointID === 'locations') { - this.trigger_up('change_location', {locationId: ev.data.changes.locations.id}); - } - }, - _onRemoveProduct: function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - - this.trigger_up('remove_product', {lineId: $(ev.currentTarget).data('id')}); - }, - _onUnlinkOrder: function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - - this.trigger_up('unlink_order', {}); - }, -}); - -return LunchWidget; - -}); diff --git a/addons/lunch/static/src/mixins/lunch_renderer_mixin.js b/addons/lunch/static/src/mixins/lunch_renderer_mixin.js new file mode 100644 index 00000000000..b27b8fd57c4 --- /dev/null +++ b/addons/lunch/static/src/mixins/lunch_renderer_mixin.js @@ -0,0 +1,40 @@ +/** @odoo-module */ + +import { useBus, useService } from "@web/core/utils/hooks"; + +export const LunchRendererMixin = { + setup() { + this._super(...arguments); + + this.action = useService("action"); + useBus(this.env.bus, 'lunch_open_order', (ev) => this.openOrderLine(ev.detail.productId)); + }, + + openOrderLine(productId, orderId) { + let context = {}; + + if (this.env.searchModel.lunchState.userId) { + context['default_user_id'] = this.env.searchModel.lunchState.userId; + } + + let action = { + res_model: 'lunch.order', + name: this.env._t('Configure Your Order'), + type: 'ir.actions.act_window', + views: [[false, 'form']], + target: 'new', + context: { + ...context, + default_product_id: productId, + }, + }; + + if (orderId) { + action['res_id'] = orderId; + } + + this.action.doAction(action, { + onClose: () => this.env.bus.trigger('lunch_update_dashboard') + }); + }, +} diff --git a/addons/lunch/static/src/scss/lunch_kanban.scss b/addons/lunch/static/src/scss/lunch_kanban.scss index 6cbf1da507d..ca1aa4eb3cc 100644 --- a/addons/lunch/static/src/scss/lunch_kanban.scss +++ b/addons/lunch/static/src/scss/lunch_kanban.scss @@ -1,5 +1,5 @@ .o_lunch_content { - .o_legacy_kanban_view { + .o_kanban_renderer { flex: 1 1 100%; &.o_kanban_grouped { min-height: auto; // override min-height: 100% diff --git a/addons/lunch/static/src/views/kanban.js b/addons/lunch/static/src/views/kanban.js new file mode 100644 index 00000000000..2f49d27fef3 --- /dev/null +++ b/addons/lunch/static/src/views/kanban.js @@ -0,0 +1,36 @@ +/** @odoo-module */ + +import { patch } from '@web/core/utils/patch'; +import { registry } from '@web/core/registry'; + +import { kanbanView } from '@web/views/kanban/kanban_view'; +import { KanbanRecord } from '@web/views/kanban/kanban_record'; +import { KanbanRenderer } from '@web/views/kanban/kanban_renderer'; + +import { LunchDashboard } from '../components/lunch_dashboard'; +import { LunchRendererMixin } from '../mixins/lunch_renderer_mixin'; + +import { LunchSearchModel } from './search_model'; + + +export class LunchKanbanRecord extends KanbanRecord { + onGlobalClick(ev) { + this.env.bus.trigger('lunch_open_order', {productId: this.props.record.resId}); + } +} + +export class LunchKanbanRenderer extends KanbanRenderer {} +patch(LunchKanbanRenderer.prototype, 'lunch_kanban_renderer_mixin', LunchRendererMixin); + +LunchKanbanRenderer.template = 'lunch.KanbanRenderer'; +LunchKanbanRenderer.components = { + ...LunchKanbanRenderer.components, + LunchDashboard, + KanbanRecord: LunchKanbanRecord, +} + +registry.category('views').add('lunch_kanban', { + ...kanbanView, + Renderer: LunchKanbanRenderer, + SearchModel: LunchSearchModel, +}); diff --git a/addons/lunch/static/src/views/kanban.xml b/addons/lunch/static/src/views/kanban.xml new file mode 100644 index 00000000000..d4b7f837f5a --- /dev/null +++ b/addons/lunch/static/src/views/kanban.xml @@ -0,0 +1,12 @@ + + + +
+ + +
+ +
+
+
+
diff --git a/addons/lunch/static/src/views/list.js b/addons/lunch/static/src/views/list.js new file mode 100644 index 00000000000..09661d84592 --- /dev/null +++ b/addons/lunch/static/src/views/list.js @@ -0,0 +1,32 @@ +/** @odoo-module */ + +import { patch } from '@web/core/utils/patch'; +import { registry } from '@web/core/registry'; + +import { listView } from '@web/views/list/list_view'; +import { ListRenderer } from '@web/views/list/list_renderer'; + +import { LunchDashboard } from '../components/lunch_dashboard'; +import { LunchRendererMixin } from '../mixins/lunch_renderer_mixin'; + +import { LunchSearchModel } from './search_model'; + + +export class LunchListRenderer extends ListRenderer { + onCellClicked(record, column) { + this.openOrderLine(record.resId); + } +} +patch(LunchListRenderer.prototype, 'lunch_list_renderer_mixin', LunchRendererMixin); + +LunchListRenderer.template = 'lunch.ListRenderer'; +LunchListRenderer.components = { + ...LunchListRenderer.components, + LunchDashboard, +} + +registry.category('views').add('lunch_list', { + ...listView, + Renderer: LunchListRenderer, + SearchModel: LunchSearchModel, +}); diff --git a/addons/lunch/static/src/views/list.xml b/addons/lunch/static/src/views/list.xml new file mode 100644 index 00000000000..b002cf8e7fd --- /dev/null +++ b/addons/lunch/static/src/views/list.xml @@ -0,0 +1,12 @@ + + + +
+ + +
+ +
+
+
+
diff --git a/addons/lunch/static/src/views/search_model.js b/addons/lunch/static/src/views/search_model.js new file mode 100644 index 00000000000..9c4fb53ab51 --- /dev/null +++ b/addons/lunch/static/src/views/search_model.js @@ -0,0 +1,67 @@ +/** @odoo-module */ + +import { useService } from "@web/core/utils/hooks"; + +import { Domain } from '@web/core/domain'; +import { SearchModel } from '@web/search/search_model'; + +const { useState, onWillStart } = owl; + + +export class LunchSearchModel extends SearchModel { + setup() { + super.setup(...arguments); + + this.rpc = useService('rpc'); + this.lunchState = useState({ + locationId: false, + userId: false, + }); + + onWillStart(async () => { + const locationId = await this.rpc('/lunch/user_location_get', {}); + this.updateLocationId(locationId); + }); + } + + exportState() { + const state = super.exportState(); + state.locationId = this.lunchState.locationId; + state.userId = this.lunchState.userId; + return state; + } + + _importState(state) { + super._importState(...arguments); + + if (state.locationId) { + this.lunchState.locationId = state.locationId; + } + if (state.userId) { + this.lunchState.userId = state.userId; + } + } + + updateUserId(userId) { + this.lunchState.userId = userId; + this._notify(); + } + + updateLocationId(locationId) { + this.lunchState.locationId = locationId; + this._notify(); + } + + _getDomain(params = {}) { + const domain = super._getDomain(params); + + if (!this.lunchState.locationId) { + return domain; + } + + return Domain.and([ + domain, + [['is_available_at', '=', this.lunchState.locationId]] + ]).toList(); + } +} diff --git a/addons/lunch/static/src/xml/lunch_templates.xml b/addons/lunch/static/src/xml/lunch_templates.xml deleted file mode 100644 index 9140a03d3ae..00000000000 --- a/addons/lunch/static/src/xml/lunch_templates.xml +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - -
-
-
-
-
- -
-
-
-
-
-
- Your Account - - - - -
-
-
-
-
-
- -
-

- Your order -

-
    - -
  • - -
    -
    -
    -
    -
    -
    - - - - -
    -
    -
    -
    - + -
    -
    - - - - -
    -
    - -
  • -
-
-
-
-
- -
-

- Total - - - - -

- -
-
-
-
-
- - - - - ctx.value = _.str.sprintf('%.2f', parseFloat(ctx.value)); - - - - - - - - - - - - - - -
- -
- - -
- - - Your cart - ( - - - ) - - -
-
- diff --git a/addons/lunch/static/tests/lunch_kanban_tests.js b/addons/lunch/static/tests/lunch_kanban_tests.js index 13a53618eb0..f7c63b49d09 100644 --- a/addons/lunch/static/tests/lunch_kanban_tests.js +++ b/addons/lunch/static/tests/lunch_kanban_tests.js @@ -1,980 +1,415 @@ -odoo.define('lunch.lunchKanbanTests', function (require) { -"use strict"; +/** @odoo-module */ -const LunchKanbanView = require('lunch.LunchKanbanView'); +import { click, getFixture, nextTick, patchWithCleanup } from '@web/../tests/helpers/utils'; +import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; -const testUtils = require('web.test_utils'); -const {createLunchView, mockLunchRPC} = require('lunch.test_utils'); +import { LunchKanbanRenderer } from '@lunch/views/kanban'; -QUnit.module('Views'); +let target; +let serverData; +let lunchInfos; -QUnit.module('LunchKanbanView', { - beforeEach() { - const PORTAL_GROUP_ID = 1234; +async function makeLunchView(extraArgs = {}) { + return await makeView( + Object.assign({ + serverData, + type: "kanban", + resModel: "lunch.product", + arch: ` + + + +
+ + +
+
+
+
`, + mockRPC: (route, args) => { + if (route == '/lunch/user_location_get') { + return Promise.resolve(serverData.models['lunch.location'].records[0].id); + } else if (route == '/lunch/infos') { + return Promise.resolve(lunchInfos); + } + } + }, extraArgs + )); +} - this.data = { - 'product': { - fields: { - is_available_at: {string: 'Product Availability', type: 'many2one', relation: 'lunch.location'}, - category_id: {string: 'Product Category', type: 'many2one', relation: 'lunch.product.category'}, - supplier_id: {string: 'Vendor', type: 'many2one', relation: 'lunch.supplier'}, +QUnit.module('Lunch', {}, function() { +QUnit.module('LunchKanban', (hooks) => { + hooks.beforeEach(() => { + target = getFixture(); + serverData = { + models: { + 'lunch.product': { + fields: { + id: { string: "ID", type: "integer" }, + name: { string: 'Name', type: 'char' }, + is_available_at: { string: 'Available', type: 'integer' }, + price: { string: 'Price', type: 'float', }, + }, + records: [ + { id: 1, name: "Big Plate", is_available_at: 1, price: 4.95, }, + { id: 2, name: "Small Plate", is_available_at: 2, price: 6.99, }, + { id: 3, name: "Just One Plate", is_available_at: 2, price: 5.87, }, + ] }, - records: [ - {id: 1, name: 'Tuna sandwich', is_available_at: 1}, - ], - }, - 'lunch.order': { - fields: {}, - update_quantity() { - return Promise.resolve(); + 'lunch.location': { + fields: { + name: { string: 'Name', type: 'char' }, + }, + records: [ + { id: 1, name: "Old Office" }, + { id: 2, name: "New Office" }, + ] }, - }, - 'lunch.product.category': { - fields: {}, - records: [], - }, - 'lunch.supplier': { - fields: {}, - records: [], - }, - 'lunch.location': { - fields: { - name: {string: 'Name', type: 'char'}, - company_id: {string: 'Company', type: 'many2one', relation: 'res.company'}, + 'lunch.order': { + fields: { + product_id: { string: 'Product', type: 'many2one', relation: 'lunch.product', }, + } }, - records: [ - {id: 1, name: "Office 1", company_id: false}, - {id: 2, name: "Office 2", company_id: false}, - ], + 'res.users': { + fields: { + share: { type: 'boolean', }, + }, + records: [ + { id: 1, name: 'Johnny Hache', share: false, }, + { id: 2, name: 'David Elora', share: false, } + ] + } }, - 'res.users': { - fields: { - name: {string: 'Name', type: 'char'}, - groups_id: {string: 'Groups', type: 'many2many'}, - }, - records: [ - {id: 1, name: "Mitchell Admin", groups_id: []}, - {id: 2, name: "Marc Demo", groups_id: []}, - {id: 3, name: "Jean-Luc Portal", groups_id: [PORTAL_GROUP_ID]}, - ], - }, - 'res.company': { - fields: { - name: {string: 'Name', type: 'char'}, - }, records: [ - {id: 1, name: "Dunder Trade Company"}, - ] + views: { + 'lunch.order,false,form': `
+ + + +
+
+
` } }; - this.regularInfos = { - username: "Marc Demo", - wallet: 36.5, + lunchInfos = { + username: "Johnny Hache", + wallet: 12.05, is_manager: false, - group_portal_id: PORTAL_GROUP_ID, currency: { - symbol: "\u20ac", - position: "after" + symbol: "€", + position: "after", }, - user_location: [2, "Office 2"], + user_location: [1, "Old Office"], + alerts: [], + lines: [], }; - this.managerInfos = { - username: "Mitchell Admin", - wallet: 47.6, - is_manager: true, - group_portal_id: PORTAL_GROUP_ID, - currency: { - symbol: "\u20ac", - position: "after" - }, - user_location: [2, "Office 2"], - }; - }, -}, function () { - QUnit.test('basic rendering', async function (assert) { - assert.expect(7); - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: mockLunchRPC({ - infos: this.regularInfos, - userLocation: this.data['lunch.location'].records[0].id, - }), - }); - - assert.containsOnce(kanban, '.o_legacy_kanban_view .o_kanban_record:not(.o_kanban_ghost)', - "should have 1 records in the renderer"); - - // check view layout - assert.containsN(kanban, '.o_content > div', 2, - "should have 2 columns"); - assert.containsOnce(kanban, '.o_content > div.o_search_panel', - "should have a 'lunch filters' column"); - assert.containsOnce(kanban, '.o_content > .o_lunch_content', - "should have a 'lunch wrapper' column"); - assert.containsOnce(kanban, '.o_lunch_content > .o_legacy_kanban_view', - "should have a 'classical kanban view' column"); - assert.hasClass(kanban.$('.o_legacy_kanban_view'), 'o_lunch_kanban_view', - "should have classname 'o_lunch_kanban_view'"); - assert.containsOnce(kanban, '.o_lunch_content > span > .o_lunch_banner', - "should have a 'lunch' banner"); - - kanban.destroy(); + setupViewRegistries(); }); - QUnit.test('no flickering at reload', async function (assert) { + QUnit.test("Basic rendering", async function (assert) { + assert.expect(4); + + await makeLunchView(); + + assert.containsOnce(target, '.o_lunch_banner'); + assert.containsNone(target, '.o_lunch_content .alert'); + assert.containsOnce(target, '.o_kanban_record:not(.o_kanban_ghost)', 1); + + const lunchDashboard = target.querySelector('.o_lunch_banner'); + const lunchUser = lunchDashboard.querySelector('.lunch_user span'); + assert.equal(lunchUser.innerText, 'Johnny Hache'); + }); + + QUnit.test("Open product", async function (assert) { assert.expect(2); - const self = this; - let infosProm = Promise.resolve(); - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: function (route, args) { - if (route === '/lunch/user_location_get') { - return Promise.resolve(self.data['lunch.location'].records[0].id); - } - if (route === '/lunch/infos') { - return Promise.resolve(self.regularInfos); - } - var result = this._super.apply(this, arguments); - if (args.method === 'xmlid_to_res_id') { - // delay the rendering of the lunch widget - return infosProm.then(_.constant(result)); - } - return result; - }, + await makeLunchView(); + + patchWithCleanup(LunchKanbanRenderer.prototype, { + openOrderLine(productId, orderId) { + assert.equal(productId, 1); + } }); - infosProm = testUtils.makeTestPromise(); - kanban.reload(); + assert.containsOnce(target, '.o_kanban_record:not(.o_kanban_ghost)'); - assert.strictEqual(kanban.$('.o_lunch_widget').length, 1, - "old widget should still be present"); - - await infosProm.resolve(); - - assert.strictEqual(kanban.$('.o_lunch_widget').length, 1); - - kanban.destroy(); + click(target, '.o_kanban_record:not(.o_kanban_ghost)'); }); - QUnit.module('LunchWidget', function () { + QUnit.test("Basic rendering with alerts", async function (assert) { + assert.expect(2); - QUnit.test('empty cart', async function (assert) { - assert.expect(3); - - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: mockLunchRPC({ - infos: this.regularInfos, - userLocation: this.data['lunch.location'].records[0].id, - }), - }); - - const $kanbanWidget = kanban.$('.o_lunch_widget'); - - assert.containsN($kanbanWidget, '> .o_lunch_widget_info', 3, - "should have 3 columns"); - assert.isVisible($kanbanWidget.find('> .o_lunch_widget_info:first'), - "should have the first column visible"); - assert.strictEqual($kanbanWidget.find('> .o_lunch_widget_info:not(:first)').html().trim(), "", - "all columns but the first should be empty"); - - kanban.destroy(); - }); - - QUnit.test('search panel domain location', async function (assert) { - assert.expect(18); - let expectedLocation = 1; - let locationId = this.data['lunch.location'].records[0].id; - const regularInfos = _.extend({}, this.regularInfos); - - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: function (route, args) { - assert.step(route); - - if (route.startsWith('/lunch')) { - if (route === '/lunch/user_location_set') { - locationId = args.location_id; - return Promise.resolve(true); - } - return mockLunchRPC({ - infos: regularInfos, - userLocation: locationId, - }).apply(this, arguments); - } - if (args.method === 'search_panel_select_multi_range') { - assert.deepEqual(args.kwargs.search_domain, [["is_available_at", "in", [expectedLocation]]], - 'The initial domain of the search panel must contain the user location'); - } - if (route === '/web/dataset/search_read') { - assert.deepEqual(args.domain, [["is_available_at", "in", [expectedLocation]]], - 'The domain for fetching actual data should be correct'); - } - return this._super.apply(this, arguments); - }, - }); - - expectedLocation = 2; - await testUtils.fields.many2one.clickOpenDropdown('locations'); - await testUtils.fields.many2one.clickItem('locations', "Office 2"); - - assert.verifySteps([ - // Initial state - '/lunch/user_location_get', - '/web/dataset/call_kw/product/search_panel_select_multi_range', - '/web/dataset/call_kw/product/search_panel_select_multi_range', - '/web/dataset/search_read', - '/lunch/infos', - // Click m2o - '/web/dataset/call_kw/lunch.location/name_search', - // Click new location - '/lunch/user_location_set', - '/web/dataset/call_kw/product/search_panel_select_multi_range', - '/web/dataset/call_kw/product/search_panel_select_multi_range', - '/web/dataset/search_read', - '/lunch/infos', - ]); - - kanban.destroy(); - }); - - QUnit.test('search panel domain location false: fetch products in all locations', async function (assert) { - assert.expect(9); - const regularInfos = _.extend({}, this.regularInfos); - - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: function (route, args) { - assert.step(route); - - if (route.startsWith('/lunch')) { - return mockLunchRPC({ - infos: regularInfos, - userLocation: false, - }).apply(this, arguments); - } - if (args.method === 'search_panel_select_multi_range') { - assert.deepEqual(args.kwargs.search_domain, [], - 'The domain should not exist since the location is false.'); - } - if (route === '/web/dataset/search_read') { - assert.deepEqual(args.domain, [], - 'The domain for fetching actual data should be correct'); - } - return this._super.apply(this, arguments); + let userInfos = { + ...lunchInfos, + alerts: [ + { + id: 1, + message: 'free boudin compote for everyone', } - }); - assert.verifySteps([ - '/lunch/user_location_get', - '/web/dataset/call_kw/product/search_panel_select_multi_range', - '/web/dataset/call_kw/product/search_panel_select_multi_range', - '/web/dataset/search_read', - '/lunch/infos', - ]) - - kanban.destroy(); + ] + }; + await makeLunchView({ + mockRPC: (route, args) => { + if (route == '/lunch/user_location_get') { + return Promise.resolve(userInfos.user_location[0]); + } else if (route == '/lunch/infos') { + return Promise.resolve(userInfos); + } + } }); - QUnit.test('non-empty cart', async function (assert) { - assert.expect(17); + assert.containsOnce(target, '.o_lunch_content .alert'); + assert.equal(target.querySelector('.o_lunch_content .alert').innerText, 'free boudin compote for everyone'); + }); - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: mockLunchRPC({ - infos: Object.assign({}, this.regularInfos, { - total: "3.00", + QUnit.test("Open product", async function (assert) { + assert.expect(2); + + await makeLunchView(); + + patchWithCleanup(LunchKanbanRenderer.prototype, { + openOrderLine(productId, orderId) { + assert.equal(productId, 1); + } + }); + + assert.containsOnce(target, '.o_kanban_record:not(.o_kanban_ghost)'); + + click(target, '.o_kanban_record:not(.o_kanban_ghost)'); + }); + + QUnit.test("Location change", async function (assert) { + assert.expect(3); + + let userInfos = { ...lunchInfos }; + await makeLunchView({ + mockRPC: (route, args) => { + if (route == '/lunch/user_location_get') { + return Promise.resolve(userInfos.user_location[0]); + } else if (route == '/lunch/infos') { + return Promise.resolve(userInfos); + } else if (route == '/lunch/user_location_set') { + assert.equal(args.location_id, 2); + userInfos.user_location = [2, "New Office"]; + return Promise.resolve(true); + } + } + }); + + click(target, '.lunch_location input'); + + await nextTick(); + assert.containsOnce(target, '.lunch_location .dropdown-item:contains(New Office)'); + + click(target, '.lunch_location .dropdown-item:not(.ui-state-active)'); + + await nextTick(); + assert.containsN(target, 'div[role=article].o_kanban_record', 2); + }); + + QUnit.test("Manager: user change", async function (assert) { + assert.expect(8); + + let userInfos = { ...lunchInfos, is_manager: true }; + let expectedUserId = false; // false as we are requesting for the current user + await makeLunchView({ + mockRPC: (route, args) => { + if (route == '/lunch/user_location_get') { + return Promise.resolve(userInfos.user_location[0]); + } else if (route == '/lunch/infos') { + assert.equal(expectedUserId, args.user_id); + + if (expectedUserId === 2) { + userInfos = { + ...userInfos, + username: 'David Elora', + wallet: -10000, + }; + } + + return Promise.resolve(userInfos); + } else if (route == '/lunch/user_location_set') { + assert.equal(args.location_id, 2); + userInfos.user_location = [2, "New Office"]; + return Promise.resolve(true); + } + } + }); + + assert.containsOnce(target, '.lunch_user input'); + click(target, '.lunch_user input'); + + await nextTick(); + assert.containsOnce(target, '.lunch_user .dropdown-item:contains(David Elora)'); + + expectedUserId = 2; + click(target, '.lunch_user .dropdown-item:not(.ui-state-active)'); + + await nextTick(); + const wallet = target.querySelector('.o_lunch_banner .col-9 > .d-flex > span:nth-child(2)'); + assert.equal(wallet.innerText, '-10000.00€', 'David Elora is poor') + + click(target, '.lunch_location input'); + await nextTick(); + click(target, '.lunch_location .dropdown-item:not(.ui-state-active)'); + + await nextTick(); + const user = target.querySelector('.lunch_user input'); + assert.equal(user.value, 'David Elora', 'changing location should not reset user'); + }); + + QUnit.test("Trash existing order", async function (assert) { + assert.expect(5); + + let userInfos = { + ...lunchInfos, + lines: [ + { + id: 1, + product: [1, "Big Plate", "4.95"], + toppings: [], + quantity: 1, + price: 4.95, + raw_state: "new", + state: "To Order", + note: false + } + ], + raw_state: "new", + total: "4.95", + }; + await makeLunchView({ + mockRPC: (route, args) => { + if (route == '/lunch/user_location_get') { + return Promise.resolve(userInfos.user_location[0]); + } else if (route == '/lunch/infos') { + return Promise.resolve(userInfos); + } else if (route == '/lunch/trash') { + userInfos = { + ...userInfos, + lines: [], + raw_state: false, + total: 0, + }; + return Promise.resolve(true); + } + } + }); + + assert.containsN(target, 'div.o_lunch_banner > .row > div', 3); + assert.containsOnce(target, 'div.o_lunch_banner > .row > div:nth-child(2) button.fa-trash', 'should have trash icon'); + assert.containsOnce(target, 'div.o_lunch_banner > .row > div:nth-child(2) ul > li', 'should have one order line'); + + assert.containsOnce(target, 'div.o_lunch_banner > .row > div:nth-child(3) button:contains(Order Now)'); + + click(target, 'div.o_lunch_banner > .row > div:nth-child(2) button.fa-trash'); + await nextTick(); + assert.containsN(target, 'div.o_lunch_banner > .row > div', 1); + }); + + QUnit.test("Change existing order", async function (assert) { + assert.expect(1); + + let userInfos = { + ...lunchInfos, + lines: [ + { + id: 1, + product: [1, "Big Plate", "4.95"], + toppings: [], + quantity: 1, + price: 4.95, + raw_state: "new", + state: "To Order", + note: false + } + ], + raw_state: "new", + total: "4.95", + }; + await makeLunchView({ + mockRPC: (route, args) => { + if (route == '/lunch/user_location_get') { + return Promise.resolve(userInfos.user_location[0]); + } else if (route == '/lunch/infos') { + return Promise.resolve(userInfos); + } else if (route == '/web/dataset/call_kw/lunch.order/update_quantity') { + assert.equal(args.args[1], 1, 'should increment order quantity by 1'); + userInfos = { + ...userInfos, lines: [ { - product: [1, "Tuna sandwich", "3.00"], - toppings: [], - quantity: 1.0, - }, + ...userInfos.lines[0], + product: [1, "Big Plate", "9.9"], + quantity: 2, + price: 4.95 * 2, + } ], - }), - userLocation: this.data['lunch.location'].records[0].id, - }), - }); + total: 4.95 * 2, + }; - const $kanbanWidget = kanban.$('.o_lunch_widget'); - - assert.containsN($kanbanWidget, '> .o_lunch_widget_info', 3, - "should have 3 columns"); - - assert.containsOnce($kanbanWidget, '.o_lunch_widget_info:eq(1)', - "should have a second column"); - - const $widgetSecondColumn = $kanbanWidget.find('.o_lunch_widget_info:eq(1)'); - - assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_unlink', - "should have a button to clear the order"); - - assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_lines > li', - "should have 1 order line"); - - const $firstLine = $widgetSecondColumn.find('.o_lunch_widget_lines > li:first'); - assert.containsOnce($firstLine, 'button.o_remove_product', - "should have a button to remove a product quantity on each line"); - assert.containsOnce($firstLine, 'button.o_add_product', - "should have a button to add a product quantity on each line"); - assert.containsOnce($firstLine, '.o_lunch_product_quantity > :eq(1)', - "should have the line's quantity"); - assert.strictEqual($firstLine.find('.o_lunch_product_quantity > :eq(1)').text().trim(), "1", - "should have 1 as the line's quantity"); - assert.containsOnce($firstLine, '.o_lunch_open_wizard', - "should have the line's product name to open the wizard"); - assert.strictEqual($firstLine.find('.o_lunch_open_wizard').text().trim(), "Tuna sandwich", - "should have 'Tuna sandwich' as the line's product name"); - assert.containsOnce($firstLine, '.o_field_monetary', - "should have the line's amount"); - assert.strictEqual($firstLine.find('.o_field_monetary').text().trim(), "3.00€", - "should have '3.00€' as the line's amount"); - - assert.containsOnce($kanbanWidget, '.o_lunch_widget_info:eq(2)', - "should have a third column"); - - const $widgetThirdColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(2)'); - - assert.containsOnce($widgetThirdColumn, '.o_field_monetary', - "should have an account balance"); - assert.strictEqual($widgetThirdColumn.find('.o_field_monetary').text().trim(), "3.00€", - "should have '3.00€' in the account balance"); - assert.containsOnce($widgetThirdColumn, '.o_lunch_widget_order_button', - "should have a button to validate the order"); - assert.strictEqual($widgetThirdColumn.find('.o_lunch_widget_order_button').text().trim(), "Order now", - "should have 'Order now' as the validate order button text"); - - kanban.destroy(); + return Promise.resolve(true); + } + } }); - QUnit.test('ordered cart', async function (assert) { - assert.expect(13); + click(target, 'div.o_lunch_banner > .row > div:nth-child(2) button.fa-plus-circle'); + }); - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: mockLunchRPC({ - infos: Object.assign({}, this.regularInfos, { - raw_state: "ordered", - state: "Ordered", + QUnit.test("Confirm existing order", async function (assert) { + assert.expect(3); + + let userInfos = { + ...lunchInfos, + lines: [ + { + id: 1, + product: [1, "Big Plate", "4.95"], + toppings: [], + quantity: 1, + price: 4.95, + raw_state: "new", + state: "To Order", + note: false + } + ], + raw_state: "new", + total: "4.95", + }; + await makeLunchView({ + mockRPC: (route, args) => { + if (route == '/lunch/user_location_get') { + return Promise.resolve(userInfos.user_location[0]); + } else if (route == '/lunch/infos') { + return Promise.resolve(userInfos); + } else if (route == '/lunch/pay') { + assert.equal(args.user_id, false); // Should confirm order of current user + userInfos = { + ...userInfos, lines: [ { - product: [1, "Tuna sandwich", "3.00"], - toppings: [], - quantity: 1.0, + ...userInfos.lines[0], raw_state: 'ordered', - }, + state: 'Ordered,' + } ], - }), - userLocation: this.data['lunch.location'].records[0].id, - }), - }); - - const $kanbanWidget = kanban.$('.o_lunch_widget'); - - assert.containsN($kanbanWidget, '> .o_lunch_widget_info', 3, - "should have 3 columns"); - - assert.containsOnce($kanbanWidget, '.o_lunch_widget_info:eq(1)', - "should have a second column"); - - const $widgetSecondColumn = $kanbanWidget.find('.o_lunch_widget_info:eq(1)'); - - assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_unlink', - "should have a button to clear the order"); - - assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_lines > li', - "should have 1 order line"); - - const $firstLine = $widgetSecondColumn.find('.o_lunch_widget_lines > li:first'); - assert.containsOnce($firstLine, 'button.o_remove_product', - "should have a button to remove a product quantity on each line"); - assert.containsOnce($firstLine, 'button.o_add_product', - "should have a button to add a product quantity on each line"); - assert.containsOnce($firstLine, '.o_lunch_product_quantity > :eq(1)', - "should have the line's quantity"); - assert.strictEqual($firstLine.find('.o_lunch_product_quantity > :eq(1)').text().trim(), "1", - "should have 1 as the line's quantity"); - assert.containsOnce($firstLine, '.o_lunch_open_wizard', - "should have the line's product name to open the wizard"); - assert.strictEqual($firstLine.find('.o_lunch_open_wizard').text().trim(), "Tuna sandwich", - "should have 'Tuna sandwich' as the line's product name"); - assert.containsOnce($firstLine, '.o_field_monetary', - "should have the line's amount"); - assert.strictEqual($firstLine.find('.o_field_monetary').text().trim(), "3.00€", - "should have '3.00€' as the line's amount"); - - assert.strictEqual($kanbanWidget.find('> .o_lunch_widget_info:eq(2)').html().trim(), "", - "third column should be empty"); - - kanban.destroy(); + raw_state: 'ordered', + wallet: userInfos.wallet - 4.95, + }; + return Promise.resolve(true); + } + } }); - QUnit.test('confirmed cart', async function (assert) { - assert.expect(12); + const wallet = target.querySelector('.o_lunch_banner .col-9 > .d-flex > span:nth-child(2)'); + assert.equal(wallet.innerText, '12.05€'); - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: mockLunchRPC({ - infos: Object.assign({}, this.regularInfos, { - raw_state: "confirmed", - state: "Received", - lines: [ - { - product: [1, "Tuna sandwich", "3.00"], - toppings: [], - quantity: 1.0, - raw_state: 'confirmed', - state: 'Confirmed', - }, - ], - }), - userLocation: this.data['lunch.location'].records[0].id, - }), - }); + click(target, 'div.o_lunch_banner > .row > div:nth-child(3) button'); - const $kanbanWidget = kanban.$('.o_lunch_widget'); - - assert.containsN($kanbanWidget, '> .o_lunch_widget_info', 3, - "should have 3 columns"); - - assert.containsOnce($kanbanWidget, '.o_lunch_widget_info:eq(1)', - "should have a second column"); - - const $widgetSecondColumn = $kanbanWidget.find('.o_lunch_widget_info:eq(1)'); - - assert.containsNone($widgetSecondColumn, '.o_lunch_widget_unlink', - "shouldn't have a button to clear the order"); - - assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_lines > li', - "should have 1 order line"); - - const $firstLine = $widgetSecondColumn.find('.o_lunch_widget_lines > li:first'); - assert.containsNone($firstLine, 'button.o_remove_product', - "shouldn't have a button to remove a product quantity on each line"); - assert.containsNone($firstLine, 'button.o_add_product', - "shouldn't have a button to add a product quantity on each line"); - assert.containsOnce($firstLine, '.o_lunch_product_quantity', - "should have the line's quantity"); - assert.strictEqual($firstLine.find('.o_lunch_product_quantity').text().trim(), "1", - "should have 1 as the line's quantity"); - assert.containsNone($firstLine, '.o_lunch_open_wizard', - "shouldn't have the line's product name to open the wizard"); - assert.containsOnce($firstLine, '.o_field_monetary', - "should have the line's amount"); - assert.strictEqual($firstLine.find('.o_field_monetary').text().trim(), "3.00€", - "should have '3.00€' as the line's amount"); - - assert.strictEqual($kanbanWidget.find('> .o_lunch_widget_info:eq(2)').html().trim(), "", - "third column should be empty"); - - kanban.destroy(); - }); - - QUnit.test('regular user', async function (assert) { - assert.expect(11); - - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: mockLunchRPC({ - infos: this.regularInfos, - userLocation: this.data['lunch.location'].records[0].id, - }), - }); - - const $kanbanWidget = kanban.$('.o_lunch_widget'); - - assert.containsOnce($kanbanWidget, '.o_lunch_widget_info:first', - "should have a first column"); - - const $widgetFirstColumn = $kanbanWidget.find('.o_lunch_widget_info:first'); - - assert.containsOnce($widgetFirstColumn, 'img.rounded-circle', - "should have a rounded avatar image"); - - assert.containsOnce($widgetFirstColumn, '.o_lunch_user_field', - "should have a user field"); - assert.containsNone($widgetFirstColumn, '.o_lunch_user_field > .o_field_widget', - "shouldn't have a field widget in the user field"); - assert.strictEqual($widgetFirstColumn.find('.o_lunch_user_field').text().trim(), "Marc Demo", - "should have 'Marc Demo' in the user field"); - - assert.containsOnce($widgetFirstColumn, '.o_lunch_location_field', - "should have a location field"); - assert.containsOnce($widgetFirstColumn, '.o_lunch_location_field > .o_field_many2one[name="locations"]', - "should have a many2one in the location field"); - - await testUtils.fields.many2one.clickOpenDropdown('locations'); - const $input = $widgetFirstColumn.find('.o_field_many2one[name="locations"] input'); - assert.containsN($input.autocomplete('widget'), 'li', 2, - "autocomplete dropdown should have 2 entries"); - assert.strictEqual($input.val(), "Office 2", - "locations input should have 'Office 2' as value"); - - assert.containsOnce($widgetFirstColumn, '.o_lunch_location_field + div', - "should have an account balance"); - assert.strictEqual($widgetFirstColumn.find('.o_lunch_location_field + div .o_field_monetary').text().trim(), "36.50€", - "should have '36.50€' in the account balance"); - - kanban.destroy(); - }); - - QUnit.test('manager user', async function (assert) { - assert.expect(12); - - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: mockLunchRPC({ - infos: this.managerInfos, - userLocation: this.data['lunch.location'].records[0].id, - }), - }); - - const $kanbanWidget = kanban.$('.o_lunch_widget'); - - assert.containsOnce($kanbanWidget, '.o_lunch_widget_info:first', - "should have a first column"); - - const $widgetFirstColumn = $kanbanWidget.find('.o_lunch_widget_info:first'); - - assert.containsOnce($widgetFirstColumn, 'img.rounded-circle', - "should have a rounded avatar image"); - - assert.containsOnce($widgetFirstColumn, '.o_lunch_user_field', - "should have a user field"); - assert.containsOnce($widgetFirstColumn, '.o_lunch_user_field > .o_field_many2one[name="users"]', - "shouldn't have a field widget in the user field"); - - await testUtils.fields.many2one.clickOpenDropdown('users'); - const $userInput = $widgetFirstColumn.find('.o_field_many2one[name="users"] input'); - assert.containsN($userInput.autocomplete('widget'), 'li', 2, - "users autocomplete dropdown should have 2 entries"); - assert.strictEqual($userInput.val(), "Mitchell Admin", - "should have 'Mitchell Admin' as value in user field"); - - assert.containsOnce($widgetFirstColumn, '.o_lunch_location_field', - "should have a location field"); - assert.containsOnce($widgetFirstColumn, '.o_lunch_location_field > .o_field_many2one[name="locations"]', - "should have a many2one in the location field"); - - await testUtils.fields.many2one.clickOpenDropdown('locations'); - const $locationInput = $widgetFirstColumn.find('.o_field_many2one[name="locations"] input'); - assert.containsN($locationInput.autocomplete('widget'), 'li', 2, - "locations autocomplete dropdown should have 2 entries"); - assert.strictEqual($locationInput.val(), "Office 2", - "should have 'Office 2' as value"); - - assert.containsOnce($widgetFirstColumn, '.o_lunch_location_field + div', - "should have an account balance"); - assert.strictEqual($widgetFirstColumn.find('.o_lunch_location_field + div .o_field_monetary').text().trim(), "47.60€", - "should have '47.60€' in the account balance"); - - kanban.destroy(); - }); - - QUnit.test('add a product', async function (assert) { - assert.expect(1); - - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: mockLunchRPC({ - infos: this.regularInfos, - userLocation: this.data['lunch.location'].records[0].id, - }), - intercepts: { - do_action: function (ev) { - assert.deepEqual(ev.data.action, { - name: "Configure Your Order", - res_model: 'lunch.order', - type: 'ir.actions.act_window', - views: [[false, 'form']], - target: 'new', - context: { - default_product_id: 1, - }, - }, - "should open the wizard"); - }, - }, - }); - - await testUtils.dom.click(kanban.$('.o_kanban_record:first')); - - kanban.destroy(); - }); - - QUnit.test('add product quantity', async function (assert) { - assert.expect(3); - - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: Object.assign({}, this.data, { - 'lunch.order': { - fields: {}, - update_quantity([lineIds, increment]) { - assert.deepEqual(lineIds, [6], "should have [6] as lineId to update quantity"); - assert.strictEqual(increment, 1, "should have +1 as increment to update quantity"); - return Promise.resolve(); - }, - }, - }), - arch: ` - - - -
-
-
-
- `, - mockRPC: mockLunchRPC({ - infos: Object.assign({}, this.regularInfos, { - lines: [ - { - id: 6, - product: [1, "Tuna sandwich", "3.00"], - toppings: [], - quantity: 1.0, - }, - ], - }), - userLocation: this.data['lunch.location'].records[0].id, - }), - }); - - const $widgetSecondColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(1)'); - - assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_lines > li', - "should have 1 order line"); - - const $firstLine = $widgetSecondColumn.find('.o_lunch_widget_lines > li:first'); - - await testUtils.dom.click($firstLine.find('button.o_add_product')); - - kanban.destroy(); - }); - - QUnit.test('remove product quantity', async function (assert) { - assert.expect(3); - - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: Object.assign({}, this.data, { - 'lunch.order': { - fields: {}, - update_quantity([lineIds, increment]) { - assert.deepEqual(lineIds, [6], "should have [6] as lineId to update quantity"); - assert.strictEqual(increment, -1, "should have -1 as increment to update quantity"); - return Promise.resolve(); - }, - }, - }), - arch: ` - - - -
-
-
-
- `, - mockRPC: mockLunchRPC({ - infos: Object.assign({}, this.regularInfos, { - lines: [ - { - id: 6, - product: [1, "Tuna sandwich", "3.00"], - toppings: [], - quantity: 1.0, - }, - ], - }), - userLocation: this.data['lunch.location'].records[0].id, - }), - }); - - const $widgetSecondColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(1)'); - - assert.containsOnce($widgetSecondColumn, '.o_lunch_widget_lines > li', - "should have 1 order line"); - - const $firstLine = $widgetSecondColumn.find('.o_lunch_widget_lines > li:first'); - - await testUtils.dom.click($firstLine.find('button.o_remove_product')); - - kanban.destroy(); - }); - - QUnit.test('clear order', async function (assert) { - assert.expect(1); - - const self = this; - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: function (route) { - if (route.startsWith('/lunch')) { - if (route === '/lunch/trash') { - assert.ok('should perform clear order RPC call'); - return Promise.resolve(); - } - return mockLunchRPC({ - infos: Object.assign({}, self.regularInfos, { - lines: [ - { - product: [1, "Tuna sandwich", "3.00"], - toppings: [], - }, - ], - }), - userLocation: self.data['lunch.location'].records[0].id, - }).apply(this, arguments); - } - return this._super.apply(this, arguments); - }, - }); - - const $widgetSecondColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(1)'); - - await testUtils.dom.click($widgetSecondColumn.find('button.o_lunch_widget_unlink')); - - kanban.destroy(); - }); - - QUnit.test('validate order: success', async function (assert) { - assert.expect(1); - - const self = this; - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: function (route) { - if (route.startsWith('/lunch')) { - if (route === '/lunch/pay') { - assert.ok("should perform pay order RPC call"); - return Promise.resolve(true); - } - return mockLunchRPC({ - infos: Object.assign({}, self.regularInfos, { - lines: [ - { - product: [1, "Tuna sandwich", "3.00"], - toppings: [], - }, - ], - }), - userLocation: self.data['lunch.location'].records[0].id, - }).apply(this, arguments); - } - return this._super.apply(this, arguments); - }, - }); - - const $widgetThirdColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(2)'); - - await testUtils.dom.click($widgetThirdColumn.find('button.o_lunch_widget_order_button')); - - kanban.destroy(); - }); - - QUnit.test('validate order: failure', async function (assert) { - assert.expect(5); - - const self = this; - const kanban = await createLunchView({ - View: LunchKanbanView, - model: 'product', - data: this.data, - arch: ` - - - -
-
-
-
- `, - mockRPC: function (route) { - if (route.startsWith('/lunch')) { - if (route === '/lunch/pay') { - assert.ok('should perform pay order RPC call'); - return Promise.resolve(false); - } - if (route === '/lunch/payment_message') { - assert.ok('should perform payment message RPC call'); - return Promise.resolve({ message: 'This is a payment message.'}); - } - return mockLunchRPC({ - infos: Object.assign({}, self.regularInfos, { - lines: [ - { - product: [1, "Tuna sandwich", "3.00"], - toppings: [], - }, - ], - }), - userLocation: self.data['lunch.location'].records[0].id, - }).apply(this, arguments); - } - return this._super.apply(this, arguments); - }, - }); - - const $widgetThirdColumn = kanban.$('.o_lunch_widget .o_lunch_widget_info:eq(2)'); - - await testUtils.dom.click($widgetThirdColumn.find('button.o_lunch_widget_order_button')); - - assert.containsOnce(document.body, '.modal', "should open a Dialog box"); - assert.strictEqual($('.modal-title').text().trim(), - "Not enough money in your wallet", "should have a Dialog's title"); - assert.strictEqual($('.modal-body').text().trim(), - "This is a payment message.", "should have a Dialog's message"); - - kanban.destroy(); - }); + await nextTick(); + assert.equal(wallet.innerText, '7.10€', 'Wallet should update'); }); }); - }); diff --git a/addons/lunch/static/tests/lunch_test_utils.js b/addons/lunch/static/tests/lunch_test_utils.js deleted file mode 100644 index baa7f18904c..00000000000 --- a/addons/lunch/static/tests/lunch_test_utils.js +++ /dev/null @@ -1,59 +0,0 @@ -odoo.define('lunch.test_utils', function (require) { -"use strict"; - -const AbstractStorageService = require('web.AbstractStorageService'); -const RamStorage = require('web.RamStorage'); -const {createView} = require('web.test_utils'); - -/** - * Helper to create a lunch view with searchpanel - * - * @param {object} params - */ -async function createLunchView(params) { - params.archs = params.archs || {}; - var searchArch = params.archs[`${params.model},false,search`] || ''; - var searchPanelArch = ` - - - - - `; - searchArch = searchArch.split('')[0] + searchPanelArch + ''; - params.archs[`${params.model},false,search`] = searchArch; - if (!params.services || !params.services.local_storage) { - // the searchPanel uses the localStorage to store/retrieve default - // active category value - params.services = params.services || {}; - const RamStorageService = AbstractStorageService.extend({ - storage: new RamStorage(), - }); - params.services.local_storage = RamStorageService; - } - return createView(params); -} - -/** - * Helper to generate a mockRPC function for the mandatory lunch routes (prefixed by '/lunch') - * - * @param {object} infos - * @param {integer} userLocation - */ -function mockLunchRPC({infos, userLocation}) { - return async function (route) { - if (route === '/lunch/infos') { - return Promise.resolve(infos); - } - if (route === '/lunch/user_location_get') { - return Promise.resolve(userLocation); - } - return this._super.apply(this, arguments); - }; -} - -return { - createLunchView, - mockLunchRPC, -}; - -}); diff --git a/addons/lunch/views/lunch_orders_views.xml b/addons/lunch/views/lunch_orders_views.xml index 2241bf80433..a6b05af3ebd 100644 --- a/addons/lunch/views/lunch_orders_views.xml +++ b/addons/lunch/views/lunch_orders_views.xml @@ -200,7 +200,7 @@ lunch.order.view.form lunch.order -
+ diff --git a/addons/lunch/views/lunch_product_views.xml b/addons/lunch/views/lunch_product_views.xml index 3f789f8af58..478257d0705 100644 --- a/addons/lunch/views/lunch_product_views.xml +++ b/addons/lunch/views/lunch_product_views.xml @@ -113,8 +113,8 @@ -
- +
+
@@ -160,7 +160,7 @@ -
+