diff --git a/pos_event_sale/__manifest__.py b/pos_event_sale/__manifest__.py index 55271d0641..2c14a6494c 100644 --- a/pos_event_sale/__manifest__.py +++ b/pos_event_sale/__manifest__.py @@ -18,7 +18,7 @@ "views/event_registration.xml", "views/event_event.xml", "views/pos_order.xml", - "views/pos_config.xml", + "views/res_config_settings.xml", ], "assets": { "point_of_sale.assets": [ @@ -29,8 +29,6 @@ "web/static/lib/fullcalendar/interaction/main.js", "pos_event_sale/static/src/js/**/*.js", "pos_event_sale/static/src/scss/**/*.scss", - ], - "web.assets_qweb": [ "pos_event_sale/static/src/xml/**/*.xml", ], "web.assets_tests": [ diff --git a/pos_event_sale/models/__init__.py b/pos_event_sale/models/__init__.py index 40cd74ae34..1212a0ef85 100644 --- a/pos_event_sale/models/__init__.py +++ b/pos_event_sale/models/__init__.py @@ -1,6 +1,9 @@ from . import event_event +from . import event_mail from . import event_registration from . import event_ticket from . import pos_order from . import pos_order_line from . import pos_config +from . import pos_session +from . import res_config_settings diff --git a/pos_event_sale/models/event_event.py b/pos_event_sale/models/event_event.py index 5a388e3b0d..5724de2781 100644 --- a/pos_event_sale/models/event_event.py +++ b/pos_event_sale/models/event_event.py @@ -42,42 +42,24 @@ def _compute_pos_price_subtotal(self): date_now = fields.Datetime.now() sale_price_by_event = {} if self.ids: - event_subtotals = self.env["pos.order.line"]._read_group( + line_ids = self.env["pos.order.line"].search( [ ("event_id", "in", self.ids), ("order_id.state", "!=", "cancel"), ("price_subtotal", "!=", 0), - ], - ["event_id", "currency_id"], - ["price_subtotal:sum"], + ] ) - currency_ids = [ - event_subtotal["currency_id"][0] for event_subtotal in event_subtotals - ] - company_by_event = { - event._origin.id or event.id: event.company_id for event in self - } - currency_by_event = { - event._origin.id or event.id: event.currency_id for event in self - } - currency_by_id = { - currency.id: currency - for currency in self.env["res.currency"].browse(currency_ids) - } - for event_subtotal in event_subtotals: - price_subtotal = event_subtotal["price_subtotal"] - event_id = event_subtotal["event_id"][0] - currency_id = event_subtotal["currency_id"][0] - sale_price = currency_by_event[event_id]._convert( - price_subtotal, - currency_by_id[currency_id], - company_by_event[event_id], + for line_id in line_ids: + sale_price = line_id.event_id.currency_id._convert( + line_id.price_subtotal, + line_id.currency_id, + line_id.event_id.company_id, date_now, ) - if event_id in sale_price_by_event: - sale_price_by_event[event_id] += sale_price + if line_id.event_id.id in sale_price_by_event: + sale_price_by_event[line_id.event_id.id] += sale_price else: - sale_price_by_event[event_id] = sale_price + sale_price_by_event[line_id.event_id.id] = sale_price for rec in self: rec.pos_price_subtotal = sale_price_by_event.get( diff --git a/pos_event_sale/models/event_mail.py b/pos_event_sale/models/event_mail.py new file mode 100644 index 0000000000..4b56d651b8 --- /dev/null +++ b/pos_event_sale/models/event_mail.py @@ -0,0 +1,22 @@ +############################################################################## +# Copyright (c) 2023 braintec AG (https://braintec.com) +# All Rights Reserved +# +# Licensed under the AGPL-3.0 (http://www.gnu.org/licenses/agpl.html) +# See LICENSE file for full licensing details. +############################################################################## + +from odoo import models + + +class EventMail(models.Model): + _inherit = "event.mail" + + def _create_missing_mail_registrations(self, registrations): + """Create mail registrations just for those partners with email. + + This way we also prevent long delays in the POS, at the time of the order validation. + """ + return super()._create_missing_mail_registrations( + registrations.filtered("email") + ) diff --git a/pos_event_sale/models/pos_order.py b/pos_event_sale/models/pos_order.py index eee9fdd32a..c9dbc82359 100644 --- a/pos_event_sale/models/pos_order.py +++ b/pos_event_sale/models/pos_order.py @@ -22,8 +22,8 @@ class PosOrder(models.Model): def _compute_event_registrations_count(self): count = self.env["event.registration"]._read_group( [("pos_order_id", "in", self.ids)], + fields=["pos_order_id"], groupby=["pos_order_id"], - aggregates=["__count"], ) count_map = {x["pos_order_id"][0]: x["pos_order_id_count"] for x in count} for rec in self: diff --git a/pos_event_sale/models/pos_session.py b/pos_event_sale/models/pos_session.py new file mode 100644 index 0000000000..cb084aa0f1 --- /dev/null +++ b/pos_event_sale/models/pos_session.py @@ -0,0 +1,148 @@ +############################################################################## +# Copyright (c) 2023 braintec AG (https://braintec.com) +# All Rights Reserved +# +# Licensed under the AGPL-3.0 (http://www.gnu.org/licenses/agpl.html). +# See LICENSE file for full licensing details. +############################################################################## + +from odoo import api, fields, models +from odoo.tools import date_utils + + +class PosSession(models.Model): + _inherit = "pos.session" + + @api.model + def _pos_ui_models_to_load(self): + models_to_load = super()._pos_ui_models_to_load() + models_to_load.extend( + [ + "event.event", + "event.event.ticket", + "event.tag.category", + "event.tag", + ] + ) + return models_to_load + + def _get_pos_ui_event_event(self, params): + if self.config_id.iface_event_sale: + return self.env["event.event"].search_read(**params["search_params"]) + return [] + + def _loader_params_event_event(self): + domain = [ + ("company_id", "in", (False, self.config_id.company_id[0].id)), + ("event_ticket_ids.product_id.active", "=", True), + ("event_ticket_ids.available_in_pos", "=", True), + ] + + if self.config_id.iface_available_event_stage_ids: + event_stage_ids = self.config_id.iface_available_event_stage_ids + domain.append(("stage_id", "in", event_stage_ids.ids)) + + if self.config_id.iface_available_event_type_ids: + event_type_ids = self.config_id.iface_available_event_type_ids + domain.append(("event_type_id", "in", event_type_ids)) + + if self.config_id.iface_available_event_tag_ids: + event_tag_ids = self.config_id.iface_available_event_tag_ids + domain.append(("tag_ids", "in", event_tag_ids)) + + if self.config_id.iface_event_load_days_before >= 0: + date_end = date_utils.subtract( + fields.Date.today(), self.config_id.iface_event_load_days_before, "days" + ) + domain.append(("date_end", ">=", date_end)) + + if self.config_id.iface_event_load_days_after >= 0: + date_start = date_utils.add( + fields.Date.today(), self.config_id.iface_event_load_days_after, "days" + ) + domain.append(("date_start", "<=", date_start)) + + fields_list = [ + "name", + "display_name", + "event_type_id", + "tag_ids", + "country_id", + "date_begin", + "date_end", + "date_tz", + "seats_limited", + "seats_available", + ] + + return { + "search_params": { + "domain": domain, + "fields": fields_list, + }, + } + + def _get_pos_ui_event_event_ticket(self, params): + if self.config_id.iface_event_sale: + return self.env["event.event.ticket"].search_read(**params["search_params"]) + return [] + + def _loader_params_event_event_ticket(self): + domain = [ + ("product_id.active", "=", True), + ("available_in_pos", "=", True), + ] + + fields_list = [ + "name", + "description", + "event_id", + "product_id", + "price", + "seats_limited", + "seats_available", + ] + + return { + "search_params": { + "domain": domain, + "fields": fields_list, + }, + } + + def _get_pos_ui_event_tag_category(self, params): + if self.config_id.iface_event_sale: + return self.env["event.tag.category"].search_read(**params["search_params"]) + return [] + + def _loader_params_event_tag_category(self): + return { + "search_params": { + "domain": [], + "fields": [ + "name", + ], + }, + } + + def _get_pos_ui_event_tag(self, params): + if self.config_id.iface_event_sale: + return self.env["event.tag"].search_read(**params["search_params"]) + return [] + + def _loader_params_event_tag(self): + return { + "search_params": { + "domain": [], + "fields": [ + "name", + "category_id", + "color", + ], + }, + } + + def _loader_params_product_product(self): + params = super()._loader_params_product_product() + params["search_params"]["fields"].append("detailed_type") + return params diff --git a/pos_event_sale/models/res_config_settings.py b/pos_event_sale/models/res_config_settings.py new file mode 100644 index 0000000000..051b2ab545 --- /dev/null +++ b/pos_event_sale/models/res_config_settings.py @@ -0,0 +1,31 @@ +# Copyright 2021 Camptocamp (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + pos_iface_event_sale = fields.Boolean( + related="pos_config_id.iface_event_sale", readonly=False + ) + pos_iface_available_event_stage_ids = fields.Many2many( + related="pos_config_id.iface_available_event_stage_ids", readonly=False + ) + pos_iface_available_event_type_ids = fields.Many2many( + related="pos_config_id.iface_available_event_type_ids", readonly=False + ) + pos_iface_available_event_tag_ids = fields.Many2many( + related="pos_config_id.iface_available_event_tag_ids", readonly=False + ) + pos_iface_event_seats_available_warning = fields.Integer( + related="pos_config_id.iface_event_seats_available_warning", readonly=False + ) + pos_iface_event_load_days_before = fields.Integer( + related="pos_config_id.iface_event_load_days_before", readonly=False + ) + pos_iface_event_load_days_after = fields.Integer( + related="pos_config_id.iface_event_load_days_after", readonly=False + ) diff --git a/pos_event_sale/static/src/js/ControlButtons/AddEventButton.js b/pos_event_sale/static/src/js/ControlButtons/AddEventButton.js index 189ac97fe9..ebb51abc23 100644 --- a/pos_event_sale/static/src/js/ControlButtons/AddEventButton.js +++ b/pos_event_sale/static/src/js/ControlButtons/AddEventButton.js @@ -8,12 +8,12 @@ odoo.define("pos_event_sale.AddEventButton", function (require) { const PosComponent = require("point_of_sale.PosComponent"); const ProductScreen = require("point_of_sale.ProductScreen"); - const {useListener} = require("web.custom_hooks"); + const {useListener} = require("@web/core/utils/hooks"); const Registries = require("point_of_sale.Registries"); class AddEventButton extends PosComponent { - constructor() { - super(...arguments); + setup() { + super.setup(); useListener("click", this.onClick); } async onClick() { diff --git a/pos_event_sale/static/src/js/Popups/EventAvailabilityBadge.js b/pos_event_sale/static/src/js/Popups/EventAvailabilityBadge.js index d7b466dedb..31177a0b67 100644 --- a/pos_event_sale/static/src/js/Popups/EventAvailabilityBadge.js +++ b/pos_event_sale/static/src/js/Popups/EventAvailabilityBadge.js @@ -19,7 +19,7 @@ odoo.define("pos_event_sale.EventAvailabilityBadge", function (require) { label: this.env._t("Oversold"), addedClasses: {"bg-danger": true}, }; - } else if (seatsAvailable == 0) { + } else if (seatsAvailable === 0) { return { label: this.env._t("Sold out"), addedClasses: {"bg-danger": true}, diff --git a/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventCalendar.js b/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventCalendar.js index 82c81f0cb7..0273ef4dc6 100644 --- a/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventCalendar.js +++ b/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventCalendar.js @@ -8,14 +8,18 @@ odoo.define("pos_event_sale.EventCalendar", function (require) { const PosComponent = require("point_of_sale.PosComponent"); const Registries = require("point_of_sale.Registries"); + const {onMounted, onWillUnmount, onWillUpdateProps} = owl; class EventCalendar extends PosComponent { /** * @param {Object} props.eventsByDate Mapping events to their dates. */ - constructor() { - super(...arguments); + setup() { + super.setup(); this.eventsByDate = this.props.eventsByDate; + onMounted(this.mounted); + onWillUnmount(this.willUnmount); + onWillUpdateProps(this.willUpdateProps); } /** * Documentation here: https://fullcalendar.io/docs/v4/ diff --git a/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventFilters.js b/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventFilters.js index 2e879197ad..f21e0fdac2 100644 --- a/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventFilters.js +++ b/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventFilters.js @@ -8,11 +8,11 @@ odoo.define("pos_event_sale.EventFilters", function (require) { const PosComponent = require("point_of_sale.PosComponent"); const Registries = require("point_of_sale.Registries"); - const {useState} = owl.hooks; + const {useState} = owl; class EventFilters extends PosComponent { - constructor() { - super(...arguments); + setup() { + super.setup(); this.state = useState({ filters: this.props.filters, }); diff --git a/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventItem.js b/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventItem.js index 1c67c04d71..9e8d5b119c 100644 --- a/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventItem.js +++ b/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventItem.js @@ -6,20 +6,25 @@ odoo.define("pos_event_sale.EventItem", function (require) { "use strict"; - const {useState} = owl.hooks; + const {useState} = owl; const PosComponent = require("point_of_sale.PosComponent"); const Registries = require("point_of_sale.Registries"); + const {onWillRender} = owl; class EventItem extends PosComponent { /** * @param {Object} props * @param {Object} props.event */ - constructor() { - super(...arguments); + setup() { + super.setup(); this.state = useState({ seatsAvailable: this.props.event.getSeatsAvailableReal(), }); + onWillRender(this.willRender); + } + willRender() { + this.state.seatsAvailable = this.props.event.getSeatsAvailableReal(); } get disabled() { return this.state.seatsAvailable <= 0; diff --git a/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventSelectorPopup.js b/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventSelectorPopup.js index 5b18f6879e..2aefa4b38b 100644 --- a/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventSelectorPopup.js +++ b/pos_event_sale/static/src/js/Popups/EventSelectorPopup/EventSelectorPopup.js @@ -6,11 +6,12 @@ odoo.define("pos_event_sale.EventSelectorPopup", function (require) { "use strict"; - const {useState} = owl.hooks; - const {useListener} = require("web.custom_hooks"); + const {useState} = owl; + const {useListener} = require("@web/core/utils/hooks"); const {getDatesInRange} = require("pos_event_sale.utils"); const AbstractAwaitablePopup = require("point_of_sale.AbstractAwaitablePopup"); const Registries = require("point_of_sale.Registries"); + const {onWillStart} = owl; class EventSelectorPopup extends AbstractAwaitablePopup { /** @@ -21,8 +22,8 @@ odoo.define("pos_event_sale.EventSelectorPopup", function (require) { * @confirmed {Boolean} * @payload {Object} Selected event. */ - constructor() { - super(...arguments); + setup() { + super.setup(); this.state = useState({ selectedStartDate: moment().startOf("day").toDate(), selectedEndDate: moment().endOf("day").toDate(), @@ -43,6 +44,7 @@ odoo.define("pos_event_sale.EventSelectorPopup", function (require) { // Cached properties this._events = null; this._eventsByDate = null; + onWillStart(this.willStart); } /** * @override @@ -119,9 +121,6 @@ odoo.define("pos_event_sale.EventSelectorPopup", function (require) { * @property {Array} events List of filtered events */ get events() { - if (this._events !== null) { - return this._events; - } this._events = this.env.pos.db.events.filter(this._compileFilters()); return this._events; } @@ -129,9 +128,6 @@ odoo.define("pos_event_sale.EventSelectorPopup", function (require) { * @property {Object} eventsByDate Mapping of dates and filtered events */ get eventsByDate() { - if (this._eventsByDate !== null) { - return this._eventsByDate; - } this._eventsByDate = {}; for (const event of this.events) { for (const eventDate of event.getEventDates()) { diff --git a/pos_event_sale/static/src/js/Popups/EventTicketsPopup/EventTicketItem.js b/pos_event_sale/static/src/js/Popups/EventTicketsPopup/EventTicketItem.js index 29ca902ea1..24cb8bef36 100644 --- a/pos_event_sale/static/src/js/Popups/EventTicketsPopup/EventTicketItem.js +++ b/pos_event_sale/static/src/js/Popups/EventTicketsPopup/EventTicketItem.js @@ -6,33 +6,26 @@ odoo.define("pos_event_sale.EventTicketItem", function (require) { "use strict"; - const {useState} = owl.hooks; + const {useState} = owl; const PosComponent = require("point_of_sale.PosComponent"); const Registries = require("point_of_sale.Registries"); + const {onWillRender} = owl; class EventTicketItem extends PosComponent { /** * @param {Object} props * @param {Object} props.eventTicket */ - constructor() { - super(...arguments); + setup() { + super.setup(); this.state = useState({ orderedQty: this.props.eventTicket.getOrderedQuantity(), seatsAvailable: this.props.eventTicket.getSeatsAvailableReal(), }); + onWillRender(this.willRendered); } - mounted() { - const order = this.env.pos.get_order(); - if (order) { - order.orderlines.on("change add remove", this._orderlinesUpdated, this); - } - } - willUnmount() { - const order = this.env.pos.get_order(); - if (order) { - order.orderlines.off("change add remove", null, this); - } + willRendered() { + this._updateQuantities(); } get imageUrl() { const product_id = this.props.eventTicket.product_id[0]; @@ -72,12 +65,6 @@ odoo.define("pos_event_sale.EventTicketItem", function (require) { this.state.seatsAvailable = this.props.eventTicket.getSeatsAvailableReal(); this.state.orderedQty = this.props.eventTicket.getOrderedQuantity(); } - _orderlinesUpdated(orderline) { - const event = this.props.eventTicket.getEvent(); - if (event === orderline.getEvent()) { - this._updateQuantities(); - } - } } EventTicketItem.template = "EventTicketItem"; diff --git a/pos_event_sale/static/src/js/Popups/EventTicketsPopup/EventTicketsPopup.js b/pos_event_sale/static/src/js/Popups/EventTicketsPopup/EventTicketsPopup.js index 94c9e983b1..5f6de7f641 100644 --- a/pos_event_sale/static/src/js/Popups/EventTicketsPopup/EventTicketsPopup.js +++ b/pos_event_sale/static/src/js/Popups/EventTicketsPopup/EventTicketsPopup.js @@ -6,7 +6,7 @@ odoo.define("pos_event_sale.EventTicketsPopup", function (require) { "use strict"; - const {useListener} = require("web.custom_hooks"); + const {useListener} = require("@web/core/utils/hooks"); const AbstractAwaitablePopup = require("point_of_sale.AbstractAwaitablePopup"); const Registries = require("point_of_sale.Registries"); @@ -15,8 +15,8 @@ odoo.define("pos_event_sale.EventTicketsPopup", function (require) { * @param {Object} props * @param {Object} props.event */ - constructor() { - super(...arguments); + setup() { + super.setup(); useListener("click-event-ticket", this._clickEventTicket); } get title() { @@ -28,6 +28,12 @@ odoo.define("pos_event_sale.EventTicketsPopup", function (require) { get currentOrder() { return this.env.pos.get_order(); } + backToOrder() { + this.env.posbus.trigger("close-popup", { + popupId: this.props.id, + response: {confirmed: false, payload: null}, + }); + } _getAddProductOptions(eventTicket) { return eventTicket._prepareOrderlineOptions(); } diff --git a/pos_event_sale/static/src/js/Screens/AbstractReceiptScreen.js b/pos_event_sale/static/src/js/Screens/AbstractReceiptScreen.js index e04fb8f413..027d35c1b5 100644 --- a/pos_event_sale/static/src/js/Screens/AbstractReceiptScreen.js +++ b/pos_event_sale/static/src/js/Screens/AbstractReceiptScreen.js @@ -18,7 +18,7 @@ odoo.define("pos_event_sale.AbstractReceiptScreen", function (require) { * @returns {Boolean} */ async _printEventRegistrations() { - if (this.env.pos.proxy.printer) { + if (this.env.pos.proxy && this.env.pos.proxy.printer) { const $receipts = this.el.getElementsByClassName( "event-registration-receipt" ); diff --git a/pos_event_sale/static/src/js/Screens/EventRegistrationReceipt.js b/pos_event_sale/static/src/js/Screens/EventRegistrationReceipt.js index 8225d5f432..39283baa96 100644 --- a/pos_event_sale/static/src/js/Screens/EventRegistrationReceipt.js +++ b/pos_event_sale/static/src/js/Screens/EventRegistrationReceipt.js @@ -8,11 +8,13 @@ odoo.define("pos_event_sale.EventRegistrationReceipt", function (require) { const PosComponent = require("point_of_sale.PosComponent"); const Registries = require("point_of_sale.Registries"); + const {onWillUpdateProps} = owl; class EventRegistrationReceipt extends PosComponent { - constructor() { - super(...arguments); + setup() { + super.setup(); this._receiptEnv = this.props.order.getOrderReceiptEnv(); + onWillUpdateProps(this.willUpdateProps); } willUpdateProps(nextProps) { this._receiptEnv = nextProps.order.getOrderReceiptEnv(); diff --git a/pos_event_sale/static/src/js/Screens/PaymentScreen.js b/pos_event_sale/static/src/js/Screens/PaymentScreen.js index 9ebb2283de..cb11c1eaa4 100644 --- a/pos_event_sale/static/src/js/Screens/PaymentScreen.js +++ b/pos_event_sale/static/src/js/Screens/PaymentScreen.js @@ -13,7 +13,6 @@ odoo.define("pos_event_sale.PaymentScreen", function (require) { const PosEventSalePaymentScreen = (PaymentScreen) => class extends PaymentScreen { async _postPushOrderResolve(order, server_ids) { - debugger; if (order.hasEvents()) { order.event_registrations = await this.rpc({ model: "event.registration", diff --git a/pos_event_sale/static/src/js/Screens/ProductInfoButton.js b/pos_event_sale/static/src/js/Screens/ProductInfoButton.js new file mode 100644 index 0000000000..a56889bcad --- /dev/null +++ b/pos_event_sale/static/src/js/Screens/ProductInfoButton.js @@ -0,0 +1,23 @@ +odoo.define("pos_event_sale.ProductInfoButton", function (require) { + "use strict"; + + const ProductInfoButton = require("point_of_sale.ProductInfoButton"); + const Registries = require("point_of_sale.Registries"); + + /* eslint-disable no-shadow */ + const PosEventSaleProductInfoButton = (ProductInfoButton) => + class extends ProductInfoButton { + async onClick() { + const orderline = this.env.pos.get_order().get_selected_orderline(); + if (orderline) { + if (orderline.get_product().detailed_type == "event") { + return; + } + } + return super.onClick(); + } + }; + + Registries.Component.extend(ProductInfoButton, PosEventSaleProductInfoButton); + return ProductInfoButton; +}); diff --git a/pos_event_sale/static/src/js/Screens/ReprintReceiptScreen.js b/pos_event_sale/static/src/js/Screens/ReprintReceiptScreen.js index ee473d7c73..374a2116d6 100644 --- a/pos_event_sale/static/src/js/Screens/ReprintReceiptScreen.js +++ b/pos_event_sale/static/src/js/Screens/ReprintReceiptScreen.js @@ -8,10 +8,16 @@ odoo.define("pos_event_sale.ReprintReceiptScreen", function (require) { const ReprintReceiptScreen = require("point_of_sale.ReprintReceiptScreen"); const Registries = require("point_of_sale.Registries"); const session = require("web.session"); + const {onWillStart} = owl; /* eslint-disable no-shadow */ const PosEventSaleReprintReceiptScreen = (ReprintReceiptScreen) => class extends ReprintReceiptScreen { + setup() { + super.setup(); + onWillStart(this.willStart); + } + /** * @override */ diff --git a/pos_event_sale/static/src/js/Widgets/MultiSelectButton.js b/pos_event_sale/static/src/js/Widgets/MultiSelectButton.js index b8b97fa748..b9722c0356 100644 --- a/pos_event_sale/static/src/js/Widgets/MultiSelectButton.js +++ b/pos_event_sale/static/src/js/Widgets/MultiSelectButton.js @@ -7,13 +7,13 @@ odoo.define("pos_event_sale.MultiSelectButton", function (require) { "use strict"; - const {useState, useExternalListener} = owl.hooks; + const {useState, useExternalListener} = owl; const PosComponent = require("point_of_sale.PosComponent"); const Registries = require("point_of_sale.Registries"); class MultiSelectButton extends PosComponent { - constructor() { - super(...arguments); + setup() { + super.setup(); useExternalListener(window, "click", this.onWindowClick, true); useExternalListener(window, "keydown", this.onWindowKeydown); this.state = useState({open: false}); diff --git a/pos_event_sale/static/src/js/models.js b/pos_event_sale/static/src/js/models.js deleted file mode 100644 index 775c5cd8aa..0000000000 --- a/pos_event_sale/static/src/js/models.js +++ /dev/null @@ -1,147 +0,0 @@ -/* - Copyright 2021 Camptocamp (https://www.camptocamp.com). - @author Iván Todorovich - License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -*/ -odoo.define("pos_event_sale.models", function (require) { - "use strict"; - - const models = require("point_of_sale.models"); - - models.load_fields("product.product", ["detailed_type"]); - - models.load_models([ - { - model: "event.event", - after: "product.product", - label: "Events", - fields: [ - "name", - "display_name", - "event_type_id", - "tag_ids", - "country_id", - "date_begin", - "date_end", - "date_tz", - "seats_limited", - "seats_available", - ], - condition: function (self) { - return self.config.iface_event_sale; - }, - domain: function (self) { - const domain = [ - ["company_id", "in", [false, self.config.company_id[0]]], - ["event_ticket_ids.product_id.active", "=", true], - ["event_ticket_ids.available_in_pos", "=", true], - ]; - if (self.config.iface_available_event_stage_ids.length) { - const event_stage_ids = self.config.iface_available_event_stage_ids; - domain.push(["stage_id", "in", event_stage_ids]); - } - if (self.config.iface_available_event_type_ids.length) { - const event_type_ids = self.config.iface_available_event_type_ids; - domain.push(["event_type_id", "in", event_type_ids]); - } - if (self.config.iface_available_event_tag_ids.length) { - const event_tag_ids = self.config.iface_available_event_tag_ids; - domain.push(["tag_ids", "in", event_tag_ids]); - } - if (self.config.iface_event_load_days_before >= 0) { - const date_end = moment() - .subtract(self.config.iface_event_load_days_before, "days") - .toDate(); - domain.push(["date_end", ">=", date_end]); - } - if (self.config.iface_event_load_days_after >= 0) { - const date_start = moment() - .add(self.config.iface_event_load_days_after, "days") - .toDate(); - domain.push(["date_start", "<=", date_start]); - } - return domain; - }, - loaded: function (self, records) { - self.db.addEvents( - records.map((record) => { - record.pos = self; - return new models.EventEvent({}, record); - }) - ); - }, - }, - { - model: "event.event.ticket", - after: "event.event", - label: "Event Tickets", - fields: [ - "name", - "description", - "event_id", - "product_id", - "price", - "seats_limited", - "seats_available", - ], - condition: function (self) { - return self.config.iface_event_sale; - }, - domain: function (self) { - const event_ids = Object.keys(self.db.event_by_id).map((id) => - Number(id) - ); - return [ - ["product_id.active", "=", true], - ["available_in_pos", "=", true], - ["event_id", "in", event_ids], - ]; - }, - loaded: function (self, records) { - self.db.addEventTickets( - records.map((record) => { - record.pos = self; - return new models.EventTicket({}, record); - }) - ); - }, - }, - { - model: "event.tag.category", - after: "event.event", - label: "Event Tag Categories", - fields: ["name"], - condition: function (self) { - return self.config.iface_event_sale; - }, - loaded: function (self, records) { - self.db.event_tag_category_by_id = {}; - self.db.event_tags = records; - for (const record of records) { - record.tag_ids = []; - self.db.event_tag_category_by_id[record.id] = record; - } - }, - }, - { - model: "event.tag", - after: "event.tag.category", - label: "Event Tags", - fields: ["name", "category_id", "color"], - condition: function (self) { - return self.config.iface_event_sale; - }, - loaded: function (self, records) { - for (const record of records) { - const category = - self.db.event_tag_category_by_id[record.category_id[0]]; - if (category) { - category.tag_ids.push(record); - } - } - }, - }, - ]); - - return models; -}); diff --git a/pos_event_sale/static/src/js/models/EventEvent.js b/pos_event_sale/static/src/js/models/EventEvent.js index da378ec59b..119fcecbc6 100644 --- a/pos_event_sale/static/src/js/models/EventEvent.js +++ b/pos_event_sale/static/src/js/models/EventEvent.js @@ -6,23 +6,22 @@ License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). odoo.define("pos_event_sale.EventEvent", function (require) { "use strict"; - const models = require("point_of_sale.models"); + const Registries = require("point_of_sale.Registries"); + const PosModel = require("pos_event_sale.PosModel"); const {getDatesInRange} = require("pos_event_sale.utils"); - models.EventEvent = window.Backbone.Model.extend({ - initialize: function (attr, options) { - _.extend(this, options); - }, - getEventTickets: function () { + class EventEvent extends PosModel { + getEventTickets() { return this.pos.db.getEventTicketsByEventID(this.id); - }, + } + /** * Computes the total ordered quantity for this event. * * @param {Order} options.order defaults to the current order * @returns {Number} ordered quantity */ - getOrderedQuantity: function ({order} = {}) { + getOrderedQuantity({order} = {}) { /* eslint-disable no-param-reassign */ order = order ? order : this.pos.get_order(); if (!order) { @@ -32,7 +31,8 @@ odoo.define("pos_event_sale.EventEvent", function (require) { .get_orderlines() .filter((line) => line.getEvent() === this) .reduce((sum, line) => sum + line.quantity, 0); - }, + } + /** * Computes the available places, considering all the ordered quantities in * the current order. @@ -43,11 +43,12 @@ odoo.define("pos_event_sale.EventEvent", function (require) { * @param {Object} options - Sent to getOrderedQuantity * @returns {Number} available seats */ - getSeatsAvailable: function (options) { + getSeatsAvailable(options) { return this.seats_limited ? this.seats_available - this.getOrderedQuantity(options) : this.seats_available; - }, + } + /** * Computes the total available places according to event ticket limits. * @@ -57,11 +58,12 @@ odoo.define("pos_event_sale.EventEvent", function (require) { * @param {Object} options - Sent to the ticket's getSeatsAvailable * @returns {Number} available seats */ - getTicketSeatsAvailable: function (options) { + getTicketSeatsAvailable(options) { return this.getEventTickets() .map((ticket) => ticket.getSeatsAvailable(options)) .reduce((sum, qty) => sum + qty, 0); - }, + } + /** * Similar to getSeatsAvailable, but also checks its ticket's availability. * It's useful to display the real availability in the UI, that accounts for @@ -70,18 +72,20 @@ odoo.define("pos_event_sale.EventEvent", function (require) { * @param {Object} options - Sent to getOrderedQuantity * @returns {Number} available seats */ - getSeatsAvailableReal: function (options) { + getSeatsAvailableReal(options) { const ticketSeatsAvailable = this.getTicketSeatsAvailable(options); const eventSeatsAvailable = this.getSeatsAvailable(options); return Math.min(ticketSeatsAvailable, eventSeatsAvailable); - }, + } + /** * @returns {[Date]} List of Dates for which this event is available */ - getEventDates: function () { + getEventDates() { return getDatesInRange(this.date_begin, this.date_end); - }, - }); + } + } - return models; + Registries.Model.add(EventEvent); + return EventEvent; }); diff --git a/pos_event_sale/static/src/js/models/EventTicket.js b/pos_event_sale/static/src/js/models/EventTicket.js index c3b8e8bc9e..ab1ecf90f1 100644 --- a/pos_event_sale/static/src/js/models/EventTicket.js +++ b/pos_event_sale/static/src/js/models/EventTicket.js @@ -6,21 +6,22 @@ License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). odoo.define("pos_event_sale.EventTicket", function (require) { "use strict"; - const models = require("point_of_sale.models"); + const Registries = require("point_of_sale.Registries"); + const PosModel = require("pos_event_sale.PosModel"); - models.EventTicket = window.Backbone.Model.extend({ - initialize: function (attr, options) { - _.extend(this, options); - }, - getEvent: function () { + class EventTicket extends PosModel { + getEvent() { return this.pos.db.getEventByID(this.event_id[0]); - }, - getProduct: function () { + } + + getProduct() { return this.pos.db.get_product_by_id(this.product_id[0]); - }, - getPriceExtra: function () { + } + + getPriceExtra() { return this.price - this.getProduct().lst_price; - }, + } + _prepareOrderlineOptions() { return { price_extra: this.getPriceExtra(), @@ -28,7 +29,8 @@ odoo.define("pos_event_sale.EventTicket", function (require) { event_ticket_id: this.id, }, }; - }, + } + /** * Computes the total ordered quantity for this event ticket. * @@ -36,7 +38,7 @@ odoo.define("pos_event_sale.EventTicket", function (require) { * @param {Order} options.order defaults to the current order * @returns {Number} ordered quantity */ - getOrderedQuantity: function ({order} = {}) { + getOrderedQuantity({order} = {}) { /* eslint-disable no-param-reassign */ order = order ? order : this.pos.get_order(); if (!order) { @@ -46,7 +48,8 @@ odoo.define("pos_event_sale.EventTicket", function (require) { .get_orderlines() .filter((line) => line.getEventTicket() === this) .reduce((sum, line) => sum + line.quantity, 0); - }, + } + /** * Computes the available places, considering all the ordered quantities in * the current order. @@ -57,24 +60,26 @@ odoo.define("pos_event_sale.EventTicket", function (require) { * @param {Object} options - Sent to getOrderedQuantity * @returns {Number} available seats */ - getSeatsAvailable: function (options) { + getSeatsAvailable(options) { return this.seats_limited ? this.seats_available - this.getOrderedQuantity(options) : this.seats_available; - }, + } + /** * Similar to getSeatsAvailable, but also checks the event's availability. * * @param {Object} options - Sent to getOrderedQuantity * @returns {Number} available seats */ - getSeatsAvailableReal: function (options) { + getSeatsAvailableReal(options) { const event = this.getEvent(); const ticketSeatsAvailable = this.getSeatsAvailable(options); const eventSeatsAvailable = event.getSeatsAvailable(options); return Math.min(ticketSeatsAvailable, eventSeatsAvailable); - }, - }); + } + } - return models; + Registries.Model.add(EventTicket); + return EventTicket; }); diff --git a/pos_event_sale/static/src/js/models/Order.js b/pos_event_sale/static/src/js/models/Order.js index a61868525a..59ec3376b3 100644 --- a/pos_event_sale/static/src/js/models/Order.js +++ b/pos_event_sale/static/src/js/models/Order.js @@ -6,99 +6,110 @@ odoo.define("pos_event_sale.Order", function (require) { "use strict"; - const models = require("point_of_sale.models"); const core = require("web.core"); const _t = core._t; + const Registries = require("point_of_sale.Registries"); + var {Order} = require("point_of_sale.models"); - const OrderSuper = models.Order.prototype; - models.Order = models.Order.extend({ - /** - * @returns {Orderlines} linked to event tickets - */ - getEventOrderlines: function () { - return this.get_orderlines().filter((line) => line.event_ticket_id); - }, - /** - * @returns Array of {event.ticket} included in this order - */ - getEventTickets: function () { - return _.unique( - this.getEventOrderlines().map((line) => line.getEventTicket()) - ); - }, - /** - * @returns Array of {event.event} included in this order - */ - getEvents: function () { - return _.unique(this.getEventOrderlines().map((line) => line.getEvent())); - }, - /** - * @returns {Boolean} - */ - hasEvents: function () { - return this.getEventTickets().length > 0; - }, - /** - * Please note it doesn't check the available seats against the backend. - * For a real availability check see updateAndCheckEventAvailability. - * - * @raise {Exception} if the order includes events without enough available seats. - */ - checkEventAvailability: function () { - const lines = this.getEventOrderlines(); - for (const line of lines) { - line.checkEventAvailability(); + // Extend the Pos global state to load events + const PosEventSaleOrder = (Order) => + class PosEventSaleOrder extends Order { + /** + * @returns {Orderlines} linked to event tickets + */ + getEventOrderlines() { + return this.get_orderlines().filter((line) => line.event_ticket_id); } - }, - /** - * Updates and check the ordered events availability - * Requires an active internet connection. - * - * @returns Promise that resolves if all is ok. - */ - updateAndCheckEventAvailability: async function () { - const tickets = this.getEventTickets(); - const limitedTickets = tickets.filter( - (ticket) => ticket.seats_limited || ticket.getEvent().seats_limited - ); - const limitedEventIds = _.unique( - limitedTickets.map((ticket) => ticket.getEvent().id) - ); - // Nothing to check! - if (!limitedEventIds.length) { - return true; + + /** + * @returns Array of {event.ticket} included in this order + */ + getEventTickets() { + return _.unique( + this.getEventOrderlines().map((line) => line.getEventTicket()) + ); } - // Update event's available seats from backend - try { - await this.pos.db.updateEventSeatsAvailable({ - event_ids: limitedEventIds, - }); - } catch (error) { - throw new Error( - _t( - "Unable to check event tickets availability. Check the internet connection then try again." - ) + + /** + * @returns Array of {event.event} included in this order + */ + getEvents() { + return _.unique( + this.getEventOrderlines().map((line) => line.getEvent()) ); } - // Check ordered event's availability - this.checkEventAvailability(); - }, - /** - * @override - */ - wait_for_push_order: function () { - const res = OrderSuper.wait_for_push_order.apply(this, arguments); - return Boolean(res || this.hasEvents()); - }, - /** - * @override - */ - export_for_printing: function () { - const res = OrderSuper.export_for_printing.apply(this, arguments); - res.event_registrations = this.event_registrations; - return res; - }, - }); - return models; + /** + * @returns {Boolean} + */ + hasEvents() { + return this.getEventTickets().length > 0; + } + + /** + * Please note it doesn't check the available seats against the backend. + * For a real availability check see updateAndCheckEventAvailability. + * + * @raise {Exception} if the order includes events without enough available seats. + */ + checkEventAvailability() { + const lines = this.getEventOrderlines(); + for (const line of lines) { + line.checkEventAvailability(); + } + } + + /** + * Updates and check the ordered events availability + * Requires an active internet connection. + * + * @returns Promise that resolves if all is ok. + */ + async updateAndCheckEventAvailability() { + const tickets = this.getEventTickets(); + const limitedTickets = tickets.filter( + (ticket) => ticket.seats_limited || ticket.getEvent().seats_limited + ); + const limitedEventIds = _.unique( + limitedTickets.map((ticket) => ticket.getEvent().id) + ); + // Nothing to check! + if (!limitedEventIds.length) { + return true; + } + // Update event's available seats from backend + try { + await this.pos.db.updateEventSeatsAvailable({ + event_ids: limitedEventIds, + }); + } catch (error) { + throw new Error( + _t( + "Unable to check event tickets availability. Check the internet connection then try again." + ) + ); + } + // Check ordered event's availability + this.checkEventAvailability(); + } + + /** + * @override + */ + wait_for_push_order() { + const res = super.wait_for_push_order.apply(this, arguments); + return Boolean(res || this.hasEvents()); + } + + /** + * @override + */ + export_for_printing() { + const res = super.export_for_printing.apply(this, arguments); + res.event_registrations = this.event_registrations; + return res; + } + }; + + Registries.Model.extend(Order, PosEventSaleOrder); }); diff --git a/pos_event_sale/static/src/js/models/Orderline.js b/pos_event_sale/static/src/js/models/Orderline.js index 8d0bf761e6..97e71de1f4 100644 --- a/pos_event_sale/static/src/js/models/Orderline.js +++ b/pos_event_sale/static/src/js/models/Orderline.js @@ -6,150 +6,165 @@ odoo.define("pos_event_sale.Orderline", function (require) { "use strict"; - const models = require("point_of_sale.models"); const utils = require("web.utils"); const core = require("web.core"); + const Registries = require("point_of_sale.Registries"); + const {Orderline} = require("point_of_sale.models"); const _t = core._t; const round_di = utils.round_decimals; - const OrderlineSuper = models.Orderline.prototype; - models.Orderline = models.Orderline.extend({ - /** - * @returns the event.ticket object - */ - getEventTicket: function () { - if (this.event_ticket_id) { - return this.pos.db.getEventTicketByID(this.event_ticket_id); + const PosEventSaleOrderLine = (Orderline) => + class extends Orderline { + /** + * @returns the event.ticket object + */ + getEventTicket() { + if (this.event_ticket_id) { + return this.pos.db.getEventTicketByID(this.event_ticket_id); + } } - }, - /** - * @returns the event object related to this line event.ticket - */ - getEvent: function () { - if (this.event_ticket_id) { - return this.getEventTicket().getEvent(); + + /** + * @returns the event object related to this line event.ticket + */ + getEvent() { + if (this.event_ticket_id) { + return this.getEventTicket().getEvent(); + } } - }, - /** - * @returns {String} The full event description for this order line - */ - getEventSaleDescription: function () { - const event = this.getEvent(); - const ticket = this.getEventTicket(); - if (ticket && event) { - return `${event.display_name} (${ticket.name})`; + + /** + * @returns {String} The full event description for this order line + */ + getEventSaleDescription() { + const event = this.getEvent(); + const ticket = this.getEventTicket(); + if (ticket && event) { + return `${event.display_name} (${ticket.name})`; + } + return ""; } - return ""; - }, - /** - * Please note it doesn't check the available seats against the backend. - * For a real availability check see updateAndCheckEventAvailability. - * - * @returns {Boolean} - */ - _checkEventAvailability: function () { - const ticket = this.getEventTicket(); - if (!ticket) { - return; + + /** + * Please note it doesn't check the available seats against the backend. + * For a real availability check see updateAndCheckEventAvailability. + * + * @returns {Boolean} + */ + _checkEventAvailability() { + const ticket = this.getEventTicket(); + if (!ticket) { + return; + } + // If it's negative, we've oversold + return ticket.getSeatsAvailableReal({order: this.order}) >= 0; } - // If it's negative, we've oversold - return ticket.getSeatsAvailableReal({order: this.order}) >= 0; - }, - /** - * Please note it doesn't check the available seats against the backend. - * For a real availability check see updateAndCheckEventAvailability. - * - * @throws {Error} - */ - checkEventAvailability: function () { - if (!this._checkEventAvailability()) { - throw new Error( - _.str.sprintf( - _t("Not enough available seats for %s"), - this.getEventSaleDescription() - ) - ); + + /** + * Please note it doesn't check the available seats against the backend. + * For a real availability check see updateAndCheckEventAvailability. + * + * @throws {Error} + */ + checkEventAvailability() { + if (!this._checkEventAvailability()) { + throw new Error( + _.str.sprintf( + _t("Not enough available seats for %s"), + this.getEventSaleDescription() + ) + ); + } } - }, - /** - * @override - */ - get_lst_price: function () { - if (this.event_ticket_id) { - return this.getEventTicket().price; + + /** + * @override + */ + get_lst_price() { + if (this.event_ticket_id) { + return this.getEventTicket().price; + } + return super.get_lst_price.apply(this, arguments); } - return OrderlineSuper.get_lst_price.apply(this, arguments); - }, - /** - * @override - */ - set_lst_price: function (price) { - if (this.event_ticket_id) { - this.order.assert_editable(); - this.getEventTicket().price = round_di( - parseFloat(price) || 0, - this.pos.dp["Product Price"] - ); - this.trigger("change", this); + + /** + * @override + */ + set_lst_price(price) { + if (this.event_ticket_id) { + this.order.assert_editable(); + this.getEventTicket().price = round_di( + parseFloat(price) || 0, + this.pos.dp["Product Price"] + ); + this.trigger("change", this); + } + return super.set_lst_price.apply(this, arguments); } - return OrderlineSuper.set_lst_price.apply(this, arguments); - }, - /** - * @override - */ - can_be_merged_with: function (orderline) { - if (this.event_ticket_id !== orderline.event_ticket_id) { - return false; + + /** + * @override + */ + can_be_merged_with(orderline) { + if (this.event_ticket_id !== orderline.event_ticket_id) { + return false; + } + return super.can_be_merged_with.apply(this, arguments); } - return OrderlineSuper.can_be_merged_with.apply(this, arguments); - }, - /** - * @override - */ - get_full_product_name: function () { - if (this.full_product_name) { - return this.full_product_name; + + /** + * @override + */ + get_full_product_name() { + if (this.full_product_name) { + return this.full_product_name; + } + if (this.event_ticket_id) { + return this.getEventSaleDescription(); + } + return super.get_full_product_name.apply(this, arguments); } - if (this.event_ticket_id) { - return this.getEventSaleDescription(); + + /** + * @override + */ + clone() { + const res = super.clone.apply(this, arguments); + res.event_ticket_id = this.event_ticket_id; + return res; + } + + /** + * @override + */ + init_from_JSON(json) { + super.init_from_JSON.apply(this, arguments); + this.event_ticket_id = json.event_ticket_id; } - return OrderlineSuper.get_full_product_name.apply(this, arguments); - }, - /** - * @override - */ - clone: function () { - const res = OrderlineSuper.clone.apply(this, arguments); - res.event_ticket_id = this.event_ticket_id; - return res; - }, - /** - * @override - */ - init_from_JSON: function (json) { - OrderlineSuper.init_from_JSON.apply(this, arguments); - this.event_ticket_id = json.event_ticket_id; - }, - /** - * @override - */ - export_as_JSON: function () { - const res = OrderlineSuper.export_as_JSON.apply(this, arguments); - res.event_ticket_id = this.event_ticket_id; - return res; - }, - /** - * @override - */ - export_for_printing: function () { - const res = OrderlineSuper.export_for_printing.apply(this, arguments); - if (this.event_ticket_id) { - res.event = this.getEvent(); - res.event_ticket = this.getEventTicket(); + + /** + * @override + */ + export_as_JSON() { + const res = super.export_as_JSON.apply(this, arguments); + res.event_ticket_id = this.event_ticket_id; + return res; } - return res; - }, - }); - return models; + /** + * @override + */ + export_for_printing() { + const res = super.export_for_printing.apply(this, arguments); + if (this.event_ticket_id) { + res.event = this.getEvent(); + res.event_ticket = this.getEventTicket(); + } + return res; + } + }; + + Registries.Model.extend(Orderline, PosEventSaleOrderLine); + + return Orderline; }); diff --git a/pos_event_sale/static/src/js/models/PosGlobalState.js b/pos_event_sale/static/src/js/models/PosGlobalState.js new file mode 100644 index 0000000000..3136574a8a --- /dev/null +++ b/pos_event_sale/static/src/js/models/PosGlobalState.js @@ -0,0 +1,151 @@ +/* + Copyright 2022 Camptocamp (https://www.camptocamp.com). + @author Iván Todorovich + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +*/ +odoo.define("pos_event_sale.models", function (require) { + "use strict"; + + const Registries = require("point_of_sale.Registries"); + const {PosGlobalState} = require("point_of_sale.models"); + const EventEvent = require("pos_event_sale.EventEvent"); + const EventTicket = require("pos_event_sale.EventTicket"); + + // Extend the Pos global state to load events + const PosEventSalePosGlobalState = (PosGlobalState) => + class extends PosGlobalState { + async _processData(loadedData) { + await super._processData(loadedData); + this._loadEventEvent(loadedData["event.event"]); + this._loadEventTicket(loadedData["event.event.ticket"]); + this._loadEventTagCategory(loadedData["event.tag.category"]); + this._loadEventTag(loadedData["event.tag"]); + } + + _loadEventEvent(events) { + const modelEvents = events.map((event) => { + event.pos = this; + return EventEvent.create(event); + }); + this.db.addEvents(modelEvents); + } + + _loadEventTicket(tickets) { + const modelTickets = tickets.map((ticket) => { + ticket.pos = this; + return EventTicket.create(ticket); + }); + this.db.addEventTickets(modelTickets); + } + + _loadEventTagCategory(eventTagCategories) { + this.db.event_tag_category_by_id = {}; + this.db.event_tags = eventTagCategories; + for (const eventTagCategory of eventTagCategories) { + eventTagCategory.tag_ids = []; + this.db.event_tag_category_by_id[eventTagCategory.id] = + eventTagCategory; + } + } + + _loadEventTag(eventTags) { + for (const eventTag of eventTags) { + const category = + this.db.event_tag_category_by_id[eventTag.category_id[0]]; + if (category) { + category.tag_ids.push(eventTag); + } + } + } + + /** + * @override + */ + async _loadMissingProducts(orders) { + return Promise.all([ + super._loadMissingProducts.apply(this, arguments), + this._loadMissingEvents(orders), + this._loadMissingEventTickets(orders), + ]); + } + + /** + * Load missing event data from orders that may be loaded from + * localStorage or from export_for_ui. + */ + async _loadMissingEvents(orders) { + const missingEventIds = []; + for (const order of orders) { + for (const line of order.lines) { + const eventId = line[2].event_id; + if (eventId && !missingEventIds.includes(eventId)) { + if (!this.db.getEventByID(eventId, false)) { + missingEventIds.push(eventId); + } + } + } + } + if (!missingEventIds.length) { + return; + } + const eventModel = this.models.find( + (model) => model.model === "event.event" + ); + const events = await this.rpc({ + model: eventModel.model, + method: "read", + args: [missingEventIds, eventModel.fields], + context: this.session.user_context, + }); + eventModel.loaded(this, events); + } + + /** + * Load missing event.ticket data from orders that may be loaded from + * localStorage or from export_for_ui. + */ + async _loadMissingEventTickets(orders) { + const missingEventTicketIds = []; + for (const order of orders) { + for (const line of order.lines) { + const eventTicketId = line[2].event_ticket_id; + if ( + eventTicketId && + !missingEventTicketIds.includes(eventTicketId) + ) { + if (!this.db.getEventTicketByID(eventTicketId, false)) { + missingEventTicketIds.push(eventTicketId); + } + } + } + } + if (!missingEventTicketIds.length) { + return; + } + const eventTicketModel = this.models.find( + (model) => model.model === "event.event.ticket" + ); + const eventTickets = await this.rpc({ + model: eventTicketModel.model, + method: "read", + args: [missingEventTicketIds, eventTicketModel.fields], + context: this.session.user_context, + }); + eventTicketModel.loaded(this, eventTickets); + } + + /** + * Prevent race condition on clicking twice the payment screen validate button + */ + _flush_orders(orders) { + if (!orders || !orders.length || orders[0] === undefined) { + return Promise.resolve([]); + } + return super._flush_orders(...arguments); + } + }; + + Registries.Model.extend(PosGlobalState, PosEventSalePosGlobalState); + + return PosGlobalState; +}); diff --git a/pos_event_sale/static/src/js/models/PosModel.js b/pos_event_sale/static/src/js/models/PosModel.js index c517860109..1771bbea68 100644 --- a/pos_event_sale/static/src/js/models/PosModel.js +++ b/pos_event_sale/static/src/js/models/PosModel.js @@ -1,89 +1,16 @@ /* - Copyright 2022 Camptocamp (https://www.camptocamp.com). - @author Iván Todorovich + Copyright 2023 Braintec (https://www.braintec.com). + @author David Moreno License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ odoo.define("pos_event_sale.PosModel", function (require) { "use strict"; - const models = require("point_of_sale.models"); + // Since PosModel is not exported, and because we want our new models to have the + // same structure as the native ones, we're forced to find a solution to get the + // original PosModel class accessible + const {Packlotline} = require("point_of_sale.models"); - const PosModelSuper = models.PosModel.prototype; - models.PosModel = models.PosModel.extend({ - /** - * @override - */ - async _loadMissingProducts(orders) { - return Promise.all([ - PosModelSuper._loadMissingProducts.apply(this, arguments), - this._loadMissingEvents(orders), - this._loadMissingEventTickets(orders), - ]); - }, - /** - * Load missing event data from orders that may be loaded from - * localStorage or from export_for_ui. - */ - async _loadMissingEvents(orders) { - const missingEventIds = []; - for (const order of orders) { - for (const line of order.lines) { - const eventId = line[2].event_id; - if (eventId && !missingEventIds.includes(eventId)) { - if (!this.db.getEventByID(eventId, false)) { - missingEventIds.push(eventId); - } - } - } - } - if (!missingEventIds.length) { - return; - } - const eventModel = this.models.find( - (model) => model.model === "event.event" - ); - const events = await this.rpc({ - model: eventModel.model, - method: "read", - args: [missingEventIds, eventModel.fields], - context: this.session.user_context, - }); - eventModel.loaded(this, events); - }, - /** - * Load missing event.ticket data from orders that may be loaded from - * localStorage or from export_for_ui. - */ - async _loadMissingEventTickets(orders) { - const missingEventTicketIds = []; - for (const order of orders) { - for (const line of order.lines) { - const eventTicketId = line[2].event_ticket_id; - if ( - eventTicketId && - !missingEventTicketIds.includes(eventTicketId) - ) { - if (!this.db.getEventTicketByID(eventTicketId, false)) { - missingEventTicketIds.push(eventTicketId); - } - } - } - } - if (!missingEventTicketIds.length) { - return; - } - const eventTicketModel = this.models.find( - (model) => model.model === "event.event.ticket" - ); - const eventTickets = await this.rpc({ - model: eventTicketModel.model, - method: "read", - args: [missingEventTicketIds, eventTicketModel.fields], - context: this.session.user_context, - }); - eventTicketModel.loaded(this, eventTickets); - }, - }); - - return models; + // This will return the PosModel class + return Object.getPrototypeOf(Packlotline); }); diff --git a/pos_event_sale/static/src/scss/pos_event_sale.scss b/pos_event_sale/static/src/scss/pos_event_sale.scss index 9ccc521b30..7f6076b2bf 100644 --- a/pos_event_sale/static/src/scss/pos_event_sale.scss +++ b/pos_event_sale/static/src/scss/pos_event_sale.scss @@ -481,4 +481,8 @@ $pos-event-sale-primary-color: #6ec89b !default; } } } + + div.button.next.validation { + cursor: pointer; + } } diff --git a/pos_event_sale/static/src/xml/EventSelectorPopup/EventFilters.xml b/pos_event_sale/static/src/xml/EventSelectorPopup/EventFilters.xml index f4dc2b6511..509d900e23 100644 --- a/pos_event_sale/static/src/xml/EventSelectorPopup/EventFilters.xml +++ b/pos_event_sale/static/src/xml/EventSelectorPopup/EventFilters.xml @@ -13,7 +13,6 @@ config="searchBarConfig" placeholder="'Search events..'" t-on-search.stop="onSearch" - t-on-change.stop="" />
@@ -33,7 +32,7 @@
diff --git a/pos_event_sale/static/src/xml/EventSelectorPopup/EventSelectorPopup.xml b/pos_event_sale/static/src/xml/EventSelectorPopup/EventSelectorPopup.xml index fb6f5e932d..28d31a6e6b 100644 --- a/pos_event_sale/static/src/xml/EventSelectorPopup/EventSelectorPopup.xml +++ b/pos_event_sale/static/src/xml/EventSelectorPopup/EventSelectorPopup.xml @@ -26,7 +26,7 @@
- Cancel + Close
diff --git a/pos_event_sale/static/src/xml/EventTicketsPopup/EventTicketsPopup.xml b/pos_event_sale/static/src/xml/EventTicketsPopup/EventTicketsPopup.xml index 7d1dcfddf0..aa6b128925 100644 --- a/pos_event_sale/static/src/xml/EventTicketsPopup/EventTicketsPopup.xml +++ b/pos_event_sale/static/src/xml/EventTicketsPopup/EventTicketsPopup.xml @@ -14,6 +14,9 @@