diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/lunch/static/src | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/lunch/static/src')
18 files changed, 1203 insertions, 0 deletions
diff --git a/addons/lunch/static/src/js/lunch_controller_common.js b/addons/lunch/static/src/js/lunch_controller_common.js new file mode 100644 index 00000000..8cd09316 --- /dev/null +++ b/addons/lunch/static/src/js/lunch_controller_common.js @@ -0,0 +1,218 @@ +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'); +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($('<div>').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, + }, + }); + }, + _fetchWidgetData: async function () { + this.widgetData = await this._rpc({ + route: '/lunch/infos', + params: { + user_id: this.searchModel.get('userId'), + context: this.context, + }, + }); + }, + /** + * Renders and appends the lunch banner widget. + * + * @private + */ + _renderLunchWidget: function () { + var self = this; + var oldWidget = this.widget; + this.widgetData.wallet = parseFloat(this.widgetData.wallet).toFixed(2); + this.widget = new LunchWidget(this, _.extend(this.widgetData, {edit: this.editMode})); + return this.widget.appendTo(document.createDocumentFragment()).then(function () { + self.$('.o_lunch_content').prepend(self.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(); + }, + }; + + // YTI TODO Maybe don't always pass the default_product_id + 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 new file mode 100644 index 00000000..dc370a46 --- /dev/null +++ b/addons/lunch/static/src/js/lunch_kanban_controller.js @@ -0,0 +1,18 @@ +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 new file mode 100644 index 00000000..9031192a --- /dev/null +++ b/addons/lunch/static/src/js/lunch_kanban_record.js @@ -0,0 +1,36 @@ +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}); + } + }, + }); + + return LunchKanbanRecord; + + }); diff --git a/addons/lunch/static/src/js/lunch_kanban_renderer.js b/addons/lunch/static/src/js/lunch_kanban_renderer.js new file mode 100644 index 00000000..bea87992 --- /dev/null +++ b/addons/lunch/static/src/js/lunch_kanban_renderer.js @@ -0,0 +1,29 @@ +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 new file mode 100644 index 00000000..50d347de --- /dev/null +++ b/addons/lunch/static/src/js/lunch_kanban_view.js @@ -0,0 +1,33 @@ +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 new file mode 100644 index 00000000..bdfbab6b --- /dev/null +++ b/addons/lunch/static/src/js/lunch_list_controller.js @@ -0,0 +1,18 @@ +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 new file mode 100644 index 00000000..0ab0b5c9 --- /dev/null +++ b/addons/lunch/static/src/js/lunch_list_renderer.js @@ -0,0 +1,56 @@ +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 new file mode 100644 index 00000000..00e8efa5 --- /dev/null +++ b/addons/lunch/static/src/js/lunch_list_view.js @@ -0,0 +1,38 @@ +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 new file mode 100644 index 00000000..f6ad1e92 --- /dev/null +++ b/addons/lunch/static/src/js/lunch_mobile.js @@ -0,0 +1,78 @@ +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 new file mode 100644 index 00000000..720b4356 --- /dev/null +++ b/addons/lunch/static/src/js/lunch_model.js @@ -0,0 +1,130 @@ +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 new file mode 100644 index 00000000..aec64b97 --- /dev/null +++ b/addons/lunch/static/src/js/lunch_model_extension.js @@ -0,0 +1,100 @@ +odoo.define("lunch/static/src/js/lunch_model_extension.js", function (require) { + "use strict"; + + const ActionModel = require("web/static/src/js/views/action_model.js"); + + 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 new file mode 100644 index 00000000..ae624403 --- /dev/null +++ b/addons/lunch/static/src/js/lunch_payment_dialog.js @@ -0,0 +1,20 @@ +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 new file mode 100644 index 00000000..ca0c497c --- /dev/null +++ b/addons/lunch/static/src/js/lunch_widget.js @@ -0,0 +1,168 @@ +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.userimage = params.userimage || ''; + this.username = params.username || ''; + + this.lunchUserField = null; + + this.group_portal_id = undefined; + + this.locations = params.locations || []; + this.userLocation = params.user_location[1] || ''; + + this.lunchLocationField = this._createMany2One('locations', 'lunch.location', this.userLocation); + + 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 self = this; + var superDef = this._super.apply(this, arguments); + + var def = this._rpc({ + model: 'ir.model.data', + method: 'xmlid_to_res_id', + kwargs: {xmlid: 'base.group_portal'}, + }).then(function (id) { + self.group_portal_id = id; + + if (self.is_manager) { + self.lunchUserField = self._createMany2One('users', 'res.users', self.username, function () { + return [['groups_id', 'not in', [self.group_portal_id]]]; + }); + } + }); + return Promise.all([superDef, def]); + }, + 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); + } + 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/scss/lunch_kanban.scss b/addons/lunch/static/src/scss/lunch_kanban.scss new file mode 100644 index 00000000..b4ffa97c --- /dev/null +++ b/addons/lunch/static/src/scss/lunch_kanban.scss @@ -0,0 +1,8 @@ +.o_lunch_content { + .o_kanban_view { + flex: 1 1 100%; + &.o_kanban_grouped { + min-height: auto; // override min-height: 100% + } + } +} diff --git a/addons/lunch/static/src/scss/lunch_list.scss b/addons/lunch/static/src/scss/lunch_list.scss new file mode 100644 index 00000000..b0afa58a --- /dev/null +++ b/addons/lunch/static/src/scss/lunch_list.scss @@ -0,0 +1,11 @@ +.o_lunch_content { + .o_list_button { + width: 1px; + } + + .o_lunch_list_view { + td:last-child { + padding-right: 16px; + } + } +}
\ No newline at end of file diff --git a/addons/lunch/static/src/scss/lunch_view.scss b/addons/lunch/static/src/scss/lunch_view.scss new file mode 100644 index 00000000..6b7056f9 --- /dev/null +++ b/addons/lunch/static/src/scss/lunch_view.scss @@ -0,0 +1,81 @@ +.o_lunch_content { + display: flex; + flex-direction: column; // display lunch widget above kanban renderer + flex: 1 1 100%; // displayed to the right of the searchPanel + min-width: 0; // prevent grouped kanban from horizontally overflowing + max-width: 100%; + height: 100%; + .o_lunch_banner { + flex: 0 0 auto; + border-bottom: 1px solid #CED4DA; + background-color: white; + } + + .o_lunch_purple { + color: $o-brand-odoo; + } + + .o_flex_basis_0 { + flex-basis: 0; + } + + .o_lunch_widget { + min-height: 90px; + max-height: 33vh; + overflow-y: auto; + + .o_lunch_widget_info.card { + &, .card-title, .card-body { + color: $o-main-text-color; + background-color: inherit !important; + } + + .card-title { + font-weight: bold; + margin-bottom: 0; + } + + .card-body { + padding: 0.5rem 1rem; + } + + .btn-link { + padding: 0; + &.o_lunch_open_wizard { + color: $o-main-text-color; + font-weight: normal; + } + } + } + } +} + +.o_lunch_image { + img { + max-width: 128px; + max-height: 128px !important; + } +} + +@include media-breakpoint-down(sm) { + .o_lunch_content { + details summary { + // Hide the caret. For details see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary + list-style-type: none; + &::-webkit-details-marker { + display: none + } + } + .o_lunch_widget { + max-height: 100% + } + } +} + +.o_lunch_wizard { + .col-10 { + .o_form_label { + font-weight: normal !important; + } + } +} diff --git a/addons/lunch/static/src/xml/lunch.xml b/addons/lunch/static/src/xml/lunch.xml new file mode 100644 index 00000000..d0415545 --- /dev/null +++ b/addons/lunch/static/src/xml/lunch.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates> + <div t-name="LunchPreviousOrdersWidgetNoOrder" class="col-lg-12"> + <h3>This is the first time you order a meal</h3> + <p class="text-muted">Select a product and put your order comments on the note.</p> + <p class="text-muted">Your favorite meals will be created based on your last orders.</p> + <p class="text-muted">Don't forget the alerts displayed in the reddish area</p> + </div> + <div t-name="LunchPreviousOrdersWidgetList" class="row"> + <div t-foreach="categories" t-as="supplier" class="col-lg-4"> + <h3><t t-esc="supplier"/></h3> + <div t-foreach='categories[supplier]' t-as='order' class="o_lunch_vignette"> + <button type="button" class="float-right o_add_button oe_edit_only oe_link" t-att-data-id="order.line_id"> + <span class="fa fa-plus-square"></span> + <span>Add</span> + </button> + <div> + <t t-esc="order.product_name"/> + <span class="badge badge-pill float-right"> + <span class="o_lunch_price" t-raw="formatValue(order)"/> + </span> + </div> + <div class="text-muted"> + <t t-if="order.note != false" t-esc="order.note"/> + </div> + </div> + </div> + </div> +</templates> diff --git a/addons/lunch/static/src/xml/lunch_templates.xml b/addons/lunch/static/src/xml/lunch_templates.xml new file mode 100644 index 00000000..6cc78721 --- /dev/null +++ b/addons/lunch/static/src/xml/lunch_templates.xml @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates> + <span t-name="LunchWidget"> + <t t-foreach="widget.alerts" t-as="alert"> + <div class="alert alert-warning mb-0" role="alert"> + <t t-raw="alert.message"/> <!-- alert.message is coming from a fields.Html so it should be safe --> + </div> + </t> + <div class="o_lunch_banner container-fluid"> + <div class="o_lunch_widget row py-3 py-md-0"> + <div class="o_lunch_widget_info col-12 col-md-4 card border-0"> + <div class="card-body row no-gutters align-items-center"> + <div class="col-3 col-md-6 col-lg-3"> + <img class="o_image_64_cover rounded-circle" t-attf-src="{{ widget.userimage }}"/> + </div> + <div class="col-9 col-md-6 col-lg-9"> + <div class="pl-3"> + <div class="o_lunch_user_field py-1"/> + <div class="o_lunch_location_field py-1"/> + <div class="d-flex flex-row py-1"> + <span class="flex-grow-1">Your Account</span> + <t t-call="currency_field"> + <t t-set="value" t-value="widget.wallet"/> + <t t-set="currency" t-value="widget.currency"/> + </t> + </div> + </div> + </div> + </div> + </div> + <div class="o_lunch_widget_info col-12 col-md-4 card border-0"> + <t t-if="!_.isEmpty(widget.lines)"> + <t t-if="widget.raw_state == 'ordered'"> + <t t-set="state_class" t-value="'badge-warning o_lunch_ordered'"/> + </t> + <t t-else="widget.raw_state == 'confirmed'"> + <t t-set="state_class" t-value="'badge-success o_lunch_confirmed'"/> + </t> + <div class="card-body"> + <h4 class="card-title"> + Your order + <button t-if="widget.raw_state != 'confirmed'" class="btn btn-sm btn-icon btn-link fa fa-trash o_lunch_widget_unlink"/> + <span t-if="widget.raw_state != 'new'" t-esc="widget.state" t-attf-class="badge badge-pill {{ state_class }}"/> + </h4> + <ul class="list-unstyled o_lunch_widget_lines"> + <li t-foreach="widget.lines" t-as="line"> + <div class="d-flex align-items-center"> + <div class="flex-grow-0 flex-shrink-0 o_lunch_product_quantity"> + <button class="btn btn-sm btn-icon btn-link fa fa-minus-circle o_remove_product" t-if="widget.raw_state != 'confirmed'" t-attf-data-id="{{ line.id }}"/> + <span t-esc="line.quantity"/> + <button class="btn btn-sm btn-icon btn-link fa fa-plus-circle o_add_product" t-if="widget.raw_state != 'confirmed'" t-attf-data-id="{{ line.id }}"/> + </div> + <div class="flex-grow-1 pl-2"> + <button t-esc="line.product[1]" class="btn btn-link o_lunch_open_wizard" t-attf-data-product-id="{{ line.product[0] }}" t-attf-data-id="{{ line.id }}"/> + </div> + <div class="flex-grow-0"> + <t t-call="currency_field"> + <t t-set="value" t-value="line.product[2]"/> + <t t-set="currency" t-value="widget.currency"/> + </t> + </div> + </div> + <div t-foreach="line.toppings" t-as="topping" class="d-flex flex-row"> + <div class="flex-grow-1 pl-5"> + <span>+ <t t-esc="topping[0]"/></span> + </div> + <div class="flex-grow-0"> + <t t-call="currency_field"> + <t t-set="value" t-value="topping[1]"/> + <t t-set="currency" t-value="widget.currency"/> + </t> + </div> + </div> + <span t-if="line.note" t-esc="line.note" class="text-muted pl-5"/> + </li> + </ul> + </div> + </t> + </div> + <div class="o_lunch_widget_info col-12 col-md-4 card border-0"> + <t t-if="!_.isEmpty(widget.lines) && widget.raw_state == 'new'"> + <div class="card-body d-flex flex-column justify-content-between"> + <h4 class="card-title d-flex py-1"> + <span class="flex-grow-1">Total</span> + <t t-call="currency_field"> + <t t-set="value" t-value="widget.total"/> + <t t-set="currency" t-value="widget.currency"/> + </t> + </h4> + <button t-if="widget.raw_state == 'new'" class="btn btn-primary w-100 o_lunch_widget_order_button">Order now</button> + </div> + </t> + </div> + </div> + </div> + </span> + + <span t-name="currency_field" class="o_field_monetary o_field_number o_field_widget"> + <t t-js="ctx"> + ctx.value = _.str.sprintf('%.2f', parseFloat(ctx.value)); + </t> + <t t-if="currency"> + <t t-if="currency.position == 'after'"> + <t t-esc="value"/><t t-esc="currency.symbol"/> + </t> + <t t-else=""> + <t t-esc="currency.symbol"/><t t-esc="value"/> + </t> + </t> + <t t-else=""> + <t t-esc="value"/> + </t> + </span> + + <div t-name="lunch.LunchPaymentDialog"> + <span t-esc="widget.message"/> + </div> + + <t t-name="LunchWidgetMobile"> + <details class="fixed-bottom" t-attf-open="#{widget.keepOpen}"> + <summary class="o_lunch_toggle_cart btn btn-primary w-100"> + <i class="fa fa-fw fa-shopping-cart"/> + Your cart + (<t t-call="currency_field"> + <t t-set="value" t-value="widget.total"/> + <t t-set="currency" t-value="widget.currency"/> + </t>) + </summary> + <t t-call="LunchWidget"/> + </details> + </t> +</templates> |
