diff --git a/event_sale_reservation/README.rst b/event_sale_reservation/README.rst new file mode 100644 index 000000000..adfe743d1 --- /dev/null +++ b/event_sale_reservation/README.rst @@ -0,0 +1,171 @@ +======================= +Sell event reservations +======================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fevent-lightgray.png?logo=github + :target: https://github.com/OCA/event/tree/15.0/event_sale_reservation + :alt: OCA/event +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/event-15-0/event-15-0-event_sale_reservation + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/199/15.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of event_sale to support selling +reservations of events that still don't exist and to allow you to schedule the +creation of events based on how many reservations already exist. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +To install this module, you need to: + +#. Install ``web_ir_actions_act_multi`` from https://github.com/OCA/web +#. Install ``web_ir_actions_act_view_reload`` from https://github.com/OCA/web + +Configuration +============= + + +To make use of this module, a user needs these minimal permissions: + +- Sales / User: Own Documents Only +- Events / User + +Usage +===== + +To know how many reservations exist for a given event type: + +#. Go to *Events > Configuration > Event Templates* and pick or create one. +#. There's a new smart button called *Reserved seats* with that count. +#. Click on it to get to the sales orders where the seats got reserved. + +But that counter will be probably zero when you install, so let's see how to +increase it. + +To create an event reservation product: + +#. Go to *Sales > Products > Products*. +#. Create one and set *Product Type* to *Event Reservation*. +#. Select one *Event type for reservations*. +#. Save. + +From now on, you can sell event reservations for that event type. To do it: + +#. Go to *Sales > Orders > Quotations*. +#. Create one. +#. Set its basic info (customer, date...) and go to *Order lines* tab. +#. Click *Add a product*. +#. Select the event reservation product you created above. +#. Set its info (quantity, price...). +#. Save that line and the quotation. + +At this point, the reservation is not yet confirmed, so if you go to the event +type, the smart button will still count zero. + +To confirm those reservations: + +#. Go to the quotation you just created (if you are not there yet). +#. Click on *Confirm*. + +Now, if you go to the event type form, the smart button will indicate how many +reserved seats exist. + +If you want to convert those reservations into real event registrations: + +#. Go to the quotation you just created (if you are not there yet). +#. Click on *Register in event*. +#. In the wizard you see, set the *Event* and *Event Ticket* for all the order + lines you want to convert into registrations. +#. If there is any line you still don't want to convert, remove it from the + wizard. +#. Click on *Next*. +#. A new wizard will appear, where you will be able to specify the name, email + and phone of each one of the attendees. If you don't do it, they will get + that info from the sales order customer. +#. After that's done, click on *Apply*. + +At this point, the sales order lines will be modified to include the ticket +product instead of the reservation product, and the event reservations have +been created, linked to those lines. + +If the event is set to autoconfirmation, the registrations are confirmed., otherwise, they are +kept as draft until an invoice is created for the sales order, and paid. But +that is just upstream ``event_sale`` module in action. + +Known issues / Roadmap +====================== + +Some addons (event_registration_multi_qty + event_sale_registration_multi_qty) +makes totals wrong because they depend currently on count and not sum of qtys; +integrating with them would require a glue module. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* `Tecnativa `_: + + * Jairo Llopis + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-Yajo| image:: https://github.com/Yajo.png?size=40px + :target: https://github.com/Yajo + :alt: Yajo + +Current `maintainer `__: + +|maintainer-Yajo| + +This module is part of the `OCA/event `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/event_sale_reservation/__init__.py b/event_sale_reservation/__init__.py new file mode 100644 index 000000000..8d855c85d --- /dev/null +++ b/event_sale_reservation/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import reports +from . import wizards diff --git a/event_sale_reservation/__manifest__.py b/event_sale_reservation/__manifest__.py new file mode 100644 index 000000000..d9ca97f89 --- /dev/null +++ b/event_sale_reservation/__manifest__.py @@ -0,0 +1,28 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Sell event reservations", + "summary": "Allow selling event registrations before the event exists", + "version": "15.0.1.0.0", + "development_status": "Beta", + "category": "Marketing", + "website": "https://github.com/OCA/event", + "author": "Tecnativa, Odoo Community Association (OCA)", + "maintainers": ["Yajo"], + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "event_sale", + "web_ir_actions_act_multi", + "web_ir_actions_act_view_reload", + ], + "data": [ + "reports/sale_report_view.xml", + "wizards/registration_editor_view.xml", + "views/event_type_view.xml", + "views/product_template_view.xml", + "views/sale_order_view.xml", + ], +} diff --git a/event_sale_reservation/exceptions.py b/event_sale_reservation/exceptions.py new file mode 100644 index 000000000..582465cd2 --- /dev/null +++ b/event_sale_reservation/exceptions.py @@ -0,0 +1,12 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import exceptions + + +class TicketAndReservationError(exceptions.ValidationError): + pass + + +class ReservationWithoutEventTypeError(exceptions.ValidationError): + pass diff --git a/event_sale_reservation/i18n/es.po b/event_sale_reservation/i18n/es.po new file mode 100644 index 000000000..21d887263 --- /dev/null +++ b/event_sale_reservation/i18n/es.po @@ -0,0 +1,210 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * event_sale_reservation +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-02-13 10:08+0000\n" +"PO-Revision-Date: 2023-02-13 11:09+0100\n" +"Last-Translator: Jairo Llopis \n" +"Language-Team: \n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.0.1\n" + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_product_product__detailed_type +#: model:ir.model.fields,help:event_sale_reservation.field_product_template__detailed_type +msgid "" +"A storable product is a product for which you manage stock. The Inventory " +"app has to be installed.\n" +"A consumable product is a product for which stock is not managed.\n" +"A service is a non-material product you provide." +msgstr "" + +#. module: event_sale_reservation +#: code:addons/event_sale_reservation/models/sale_order.py:0 +#, python-format +msgid "Attendees" +msgstr "Asistentes" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "Cancel" +msgstr "Cancelar" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "Configure registrations" +msgstr "Configurar registros" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "Convert pending event reservations into registrations for" +msgstr "Convertir las reservas a eventos que estén pendientes de" + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_sale_order_line__event_registration_count +msgid "Count of event registrations related to this sale order line" +msgstr "" +"Cuenta de registros a eventos relacionados con esta línea de pedido de venta" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_registration_editor +msgid "Edit Attendee Details on Sales Confirmation" +msgstr "Editar detalles del asistente al confirmar la venta" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_registration_editor_line +msgid "Edit Attendee Line on Sales Confirmation" +msgstr "Editar la línea del asistente al confirmar la venta" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_sale_order_line__event_registration_count +msgid "Event Registration Count" +msgstr "Cuenta de registros a eventos" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.view_order_product_search +msgid "Event Reservation Type" +msgstr "Categoría de evento de la reserva" + +#. module: event_sale_reservation +#: model:ir.model.fields.selection,name:event_sale_reservation.selection__product_template__detailed_type__event_reservation +msgid "Event Resevation" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_event_type +msgid "Event Template" +msgstr "Plantilla de producto" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_sale_order__event_registration_count +#: model:ir.model.fields,field_description:event_sale_reservation.field_sale_order_line__event_registration_ids +msgid "Event registrations" +msgstr "Registros a eventos" + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_sale_order_line__event_registration_ids +msgid "Event registrations related to this sale order line" +msgstr "Registros a eventos relacionados con esta línea de pedido de venta" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_sale_report__event_reservation_type_id +msgid "Event reservation type" +msgstr "Categoría de evento de reserva" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_product_product__event_reservation_type_id +#: model:ir.model.fields,field_description:event_sale_reservation.field_product_template__event_reservation_type_id +#: model:ir.model.fields,field_description:event_sale_reservation.field_registration_editor_line__event_reservation_type_id +#: model:ir.model.fields,field_description:event_sale_reservation.field_sale_order_line__event_reservation_type_id +msgid "Event type for reservations" +msgstr "Categoría de evento para las reservas" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "" +"If there is any line from that order that you still do\n" +" not want to convert into real event registrations, " +"you\n" +" can remove it from the list below. You will be able " +"to\n" +" repeat this process later for those lines." +msgstr "" +"Si hay alguna línea de ese pedido que todavía\n" +" no desee convertir en registros de evento reales,\n" +" puede borrarla de la lista abajo. Podrá\n" +" repetir este proceso más adelante para esas líneas." + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_sale_order__event_registration_count +msgid "Indicates how many event registrations are linked to this order." +msgstr "" +"Indica cuántas reservas a eventos están vinculadas a este pedido de venta." + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_sale_order__event_reservations_pending +msgid "" +"Indicates how many event reservations are still not linked to any " +"registration." +msgstr "" +"Indica cuántas reservas a eventos todavía no están vinculadas a ningún " +"registro." + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "Next" +msgstr "Siguiente" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_sale_order__event_reservations_pending +msgid "Pending event reservations" +msgstr "Reservas pendientes a eventos" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_product_template +msgid "Product Template" +msgstr "Plantilla de producto" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_product_product__detailed_type +#: model:ir.model.fields,field_description:event_sale_reservation.field_product_template__detailed_type +msgid "Product Type" +msgstr "Tipo de producto" + +#. module: event_sale_reservation +#: model:ir.actions.act_window,name:event_sale_reservation.action_registration_editor_reservations +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.view_sale_order_form_inherit_event +msgid "Register in event" +msgstr "Registrar en evento" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_event_type__reserved_sale_order_line_ids +msgid "Reserved sale order lines" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_event_type__seats_reservation_total +msgid "Reserved seats" +msgstr "Plazas reservadas" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_sale_report +msgid "Sales Analysis Report" +msgstr "Informe de análisis de ventas" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_sale_order +msgid "Sales Order" +msgstr "Pedido de venta" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línea de pedido de venta" + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_event_type__seats_reservation_total +msgid "Seats reserved for events of this type." +msgstr "Plazas reservadas para eventos de esta categoría." + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_product_product__event_reservation_type_id +#: model:ir.model.fields,help:event_sale_reservation.field_product_template__event_reservation_type_id +#: model:ir.model.fields,help:event_sale_reservation.field_registration_editor_line__event_reservation_type_id +#: model:ir.model.fields,help:event_sale_reservation.field_sale_order_line__event_reservation_type_id +msgid "Type of events that can be reserved by buying this product" +msgstr "Categoría de eventos que se pueden reservar al comprar este producto" + +#. module: event_sale_reservation +#: code:addons/event_sale_reservation/models/product_template.py:0 +#, python-format +msgid "You must indicate event type for %(name)s." +msgstr "Debe indicar la categoría de evento para %(name)s." diff --git a/event_sale_reservation/i18n/event_sale_reservation.pot b/event_sale_reservation/i18n/event_sale_reservation.pot new file mode 100644 index 000000000..249dec909 --- /dev/null +++ b/event_sale_reservation/i18n/event_sale_reservation.pot @@ -0,0 +1,197 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * event_sale_reservation +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-02-13 10:08+0000\n" +"PO-Revision-Date: 2023-02-13 10:08+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_product_product__detailed_type +#: model:ir.model.fields,help:event_sale_reservation.field_product_template__detailed_type +msgid "" +"A storable product is a product for which you manage stock. The Inventory app has to be installed.\n" +"A consumable product is a product for which stock is not managed.\n" +"A service is a non-material product you provide." +msgstr "" + +#. module: event_sale_reservation +#: code:addons/event_sale_reservation/models/sale_order.py:0 +#, python-format +msgid "Attendees" +msgstr "" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "Cancel" +msgstr "" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "Configure registrations" +msgstr "" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "Convert pending event reservations into registrations for" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_sale_order_line__event_registration_count +msgid "Count of event registrations related to this sale order line" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_registration_editor +msgid "Edit Attendee Details on Sales Confirmation" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_registration_editor_line +msgid "Edit Attendee Line on Sales Confirmation" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_sale_order_line__event_registration_count +msgid "Event Registration Count" +msgstr "" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.view_order_product_search +msgid "Event Reservation Type" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields.selection,name:event_sale_reservation.selection__product_template__detailed_type__event_reservation +msgid "Event Resevation" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_event_type +msgid "Event Template" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_sale_order__event_registration_count +#: model:ir.model.fields,field_description:event_sale_reservation.field_sale_order_line__event_registration_ids +msgid "Event registrations" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_sale_order_line__event_registration_ids +msgid "Event registrations related to this sale order line" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_sale_report__event_reservation_type_id +msgid "Event reservation type" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_product_product__event_reservation_type_id +#: model:ir.model.fields,field_description:event_sale_reservation.field_product_template__event_reservation_type_id +#: model:ir.model.fields,field_description:event_sale_reservation.field_registration_editor_line__event_reservation_type_id +#: model:ir.model.fields,field_description:event_sale_reservation.field_sale_order_line__event_reservation_type_id +msgid "Event type for reservations" +msgstr "" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "" +"If there is any line from that order that you still do\n" +" not want to convert into real event registrations, you\n" +" can remove it from the list below. You will be able to\n" +" repeat this process later for those lines." +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_sale_order__event_registration_count +msgid "Indicates how many event registrations are linked to this order." +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_sale_order__event_reservations_pending +msgid "" +"Indicates how many event reservations are still not linked to any " +"registration." +msgstr "" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "Next" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_sale_order__event_reservations_pending +msgid "Pending event reservations" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_product_template +msgid "Product Template" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_product_product__detailed_type +#: model:ir.model.fields,field_description:event_sale_reservation.field_product_template__detailed_type +msgid "Product Type" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.actions.act_window,name:event_sale_reservation.action_registration_editor_reservations +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.view_sale_order_form_inherit_event +msgid "Register in event" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_event_type__reserved_sale_order_line_ids +msgid "Reserved sale order lines" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,field_description:event_sale_reservation.field_event_type__seats_reservation_total +msgid "Reserved seats" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_sale_report +msgid "Sales Analysis Report" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model,name:event_sale_reservation.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_event_type__seats_reservation_total +msgid "Seats reserved for events of this type." +msgstr "" + +#. module: event_sale_reservation +#: model:ir.model.fields,help:event_sale_reservation.field_product_product__event_reservation_type_id +#: model:ir.model.fields,help:event_sale_reservation.field_product_template__event_reservation_type_id +#: model:ir.model.fields,help:event_sale_reservation.field_registration_editor_line__event_reservation_type_id +#: model:ir.model.fields,help:event_sale_reservation.field_sale_order_line__event_reservation_type_id +msgid "Type of events that can be reserved by buying this product" +msgstr "" + +#. module: event_sale_reservation +#: code:addons/event_sale_reservation/models/product_template.py:0 +#, python-format +msgid "You must indicate event type for %(name)s." +msgstr "" diff --git a/event_sale_reservation/migrations/15.0.1.0.0/post-migration.py b/event_sale_reservation/migrations/15.0.1.0.0/post-migration.py new file mode 100644 index 000000000..50074b310 --- /dev/null +++ b/event_sale_reservation/migrations/15.0.1.0.0/post-migration.py @@ -0,0 +1,12 @@ +# Copyright 2023 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + openupgrade.logged_query( + env.cr, + """UPDATE product_product SET detailed_type = 'event_reservation' + WHERE event_reservation_ok IS TRUE""", + ) diff --git a/event_sale_reservation/models/__init__.py b/event_sale_reservation/models/__init__.py new file mode 100644 index 000000000..f3fcd22cf --- /dev/null +++ b/event_sale_reservation/models/__init__.py @@ -0,0 +1,4 @@ +from . import event_type +from . import product_template +from . import sale_order +from . import sale_order_line diff --git a/event_sale_reservation/models/event_type.py b/event_sale_reservation/models/event_type.py new file mode 100644 index 000000000..75908bd5d --- /dev/null +++ b/event_sale_reservation/models/event_type.py @@ -0,0 +1,58 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class EventType(models.Model): + _inherit = "event.type" + + reserved_sale_order_line_ids = fields.One2many( + string="Reserved sale order lines", + comodel_name="sale.order.line", + inverse_name="event_reservation_type_id", + ) + seats_reservation_total = fields.Integer( + string="Reserved seats", + compute="_compute_reservations_total", + store=True, + help="Seats reserved for events of this type.", + ) + + def _seats_reservation_domain(self): + """Domain to select sale.order.line with pending reservations.""" + return [ + ("event_reservation_type_id", "in", self.ids), + ("order_id.state", "in", ("sale", "done")), + ("product_id.detailed_type", "=", "event_reservation"), + ] + + @api.depends( + "reserved_sale_order_line_ids.event_registration_count", + "reserved_sale_order_line_ids.event_reservation_type_id", + "reserved_sale_order_line_ids.order_id.state", + "reserved_sale_order_line_ids.product_id.detailed_type", + "reserved_sale_order_line_ids.product_uom_qty", + ) + def _compute_reservations_total(self): + """Get how many reserved seats exist.""" + results = self.env["sale.order.line"].read_group( + domain=self._seats_reservation_domain(), + fields=["event_registration_count", "product_uom_qty"], + groupby="event_reservation_type_id", + ) + totals = {group["event_reservation_type_id"][0]: group for group in results} + for one in self: + totals_item = totals.get(one.id, {}) + one.seats_reservation_total = totals_item.get( + "product_uom_qty", 0 + ) - totals_item.get("event_registration_count", 0) + + def action_open_sale_orders(self): + """Display SO that include reservations.""" + sol = self.env["sale.order.line"].search( + self._seats_reservation_domain(), + ) + result = self.env["ir.actions.act_window"]._for_xml_id("sale.action_orders") + result["domain"] = [("order_line", "in", sol.ids)] + return result diff --git a/event_sale_reservation/models/product_template.py b/event_sale_reservation/models/product_template.py new file mode 100644 index 000000000..11abede4e --- /dev/null +++ b/event_sale_reservation/models/product_template.py @@ -0,0 +1,44 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# Copyright 2023 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models + +from ..exceptions import ReservationWithoutEventTypeError + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + detailed_type = fields.Selection( + selection_add=[ + ("event_reservation", "Event Resevation"), + ], + ondelete={"event_reservation": "set service"}, + ) + event_reservation_type_id = fields.Many2one( + comodel_name="event.type", + index=True, + string="Event type for reservations", + help="Type of events that can be reserved by buying this product", + ) + + def _detailed_type_mapping(self): + type_mapping = super()._detailed_type_mapping() + type_mapping["event_reservation"] = "service" + return type_mapping + + @api.constrains("detailed_type") + def _check_event_reservation(self): + """Event reservation products checks. + + - A product cannot be both an event ticket and an event reservation. + - An event reservation must have an event type attached. + """ + for one in self: + if one.detailed_type != "event_reservation": + continue + if not one.event_reservation_type_id: + raise ReservationWithoutEventTypeError( + _("You must indicate event type for %(name)s.") + ) diff --git a/event_sale_reservation/models/sale_order.py b/event_sale_reservation/models/sale_order.py new file mode 100644 index 000000000..2dd4263c6 --- /dev/null +++ b/event_sale_reservation/models/sale_order.py @@ -0,0 +1,51 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + event_reservations_pending = fields.Integer( + compute="_compute_event_reservations_pending", + string="Pending event reservations", + help=( + "Indicates how many event reservations are still not linked to " + "any registration." + ), + ) + event_registration_count = fields.Integer( + compute="_compute_event_registration_count", + string="Event registrations", + help=("Indicates how many event registrations are linked to this order."), + ) + + @api.depends("order_line.product_uom_qty", "order_line.event_registration_count") + def _compute_event_reservations_pending(self): + """Know how many pending event reservations are linked to this SO.""" + for one in self: + reservation_lines = one.order_line.filtered( + lambda x: x.product_id.detailed_type == "event_reservation" + ) + reserved = sum(reservation_lines.mapped("product_uom_qty")) + registered = sum(reservation_lines.mapped("event_registration_count")) + one.event_reservations_pending = reserved - registered + + @api.depends("order_line.event_registration_ids") + def _compute_event_registration_count(self): + """Get registrations per SO.""" + for one in self: + one.event_registration_count = len( + one.mapped("order_line.event_registration_ids") + ) + + def action_open_event_registrations(self): + """Redirect user to event registrations related to this SO.""" + return { + "domain": [("sale_order_id", "in", self.ids)], + "name": _("Attendees"), + "res_model": "event.registration", + "type": "ir.actions.act_window", + "view_mode": "tree,form,calendar,graph", + } diff --git a/event_sale_reservation/models/sale_order_line.py b/event_sale_reservation/models/sale_order_line.py new file mode 100644 index 000000000..62900e892 --- /dev/null +++ b/event_sale_reservation/models/sale_order_line.py @@ -0,0 +1,32 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + event_registration_ids = fields.One2many( + comodel_name="event.registration", + inverse_name="sale_order_line_id", + string="Event registrations", + help="Event registrations related to this sale order line", + ) + event_registration_count = fields.Integer( + compute="_compute_event_registration_count", + store=True, + help="Count of event registrations related to this sale order line", + ) + event_reservation_type_id = fields.Many2one( + index=True, + readonly=True, + related="product_id.event_reservation_type_id", + store=True, + ) + + @api.depends("event_registration_ids") + def _compute_event_registration_count(self): + """Get count of related event registrations.""" + for one in self: + one.event_registration_count = len(one.event_registration_ids) diff --git a/event_sale_reservation/readme/CONFIGURE.rst b/event_sale_reservation/readme/CONFIGURE.rst new file mode 100644 index 000000000..e6f8effa4 --- /dev/null +++ b/event_sale_reservation/readme/CONFIGURE.rst @@ -0,0 +1,5 @@ + +To make use of this module, a user needs these minimal permissions: + +- Sales / User: Own Documents Only +- Events / User diff --git a/event_sale_reservation/readme/CONTRIBUTORS.rst b/event_sale_reservation/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..8c4d96846 --- /dev/null +++ b/event_sale_reservation/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Tecnativa `_: + + * Jairo Llopis diff --git a/event_sale_reservation/readme/DESCRIPTION.rst b/event_sale_reservation/readme/DESCRIPTION.rst new file mode 100644 index 000000000..95aced0ec --- /dev/null +++ b/event_sale_reservation/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module extends the functionality of event_sale to support selling +reservations of events that still don't exist and to allow you to schedule the +creation of events based on how many reservations already exist. diff --git a/event_sale_reservation/readme/INSTALL.rst b/event_sale_reservation/readme/INSTALL.rst new file mode 100644 index 000000000..8870fa25c --- /dev/null +++ b/event_sale_reservation/readme/INSTALL.rst @@ -0,0 +1,4 @@ +To install this module, you need to: + +#. Install ``web_ir_actions_act_multi`` from https://github.com/OCA/web +#. Install ``web_ir_actions_act_view_reload`` from https://github.com/OCA/web diff --git a/event_sale_reservation/readme/ROADMAP.rst b/event_sale_reservation/readme/ROADMAP.rst new file mode 100644 index 000000000..5199b37a1 --- /dev/null +++ b/event_sale_reservation/readme/ROADMAP.rst @@ -0,0 +1,3 @@ +Some addons (event_registration_multi_qty + event_sale_registration_multi_qty) +makes totals wrong because they depend currently on count and not sum of qtys; +integrating with them would require a glue module. diff --git a/event_sale_reservation/readme/USAGE.rst b/event_sale_reservation/readme/USAGE.rst new file mode 100644 index 000000000..2185c15d5 --- /dev/null +++ b/event_sale_reservation/readme/USAGE.rst @@ -0,0 +1,58 @@ +To know how many reservations exist for a given event type: + +#. Go to *Events > Configuration > Event Templates* and pick or create one. +#. There's a new smart button called *Reserved seats* with that count. +#. Click on it to get to the sales orders where the seats got reserved. + +But that counter will be probably zero when you install, so let's see how to +increase it. + +To create an event reservation product: + +#. Go to *Sales > Products > Products*. +#. Create one and set *Product Type* to *Event Reservation*. +#. Select one *Event type for reservations*. +#. Save. + +From now on, you can sell event reservations for that event type. To do it: + +#. Go to *Sales > Orders > Quotations*. +#. Create one. +#. Set its basic info (customer, date...) and go to *Order lines* tab. +#. Click *Add a product*. +#. Select the event reservation product you created above. +#. Set its info (quantity, price...). +#. Save that line and the quotation. + +At this point, the reservation is not yet confirmed, so if you go to the event +type, the smart button will still count zero. + +To confirm those reservations: + +#. Go to the quotation you just created (if you are not there yet). +#. Click on *Confirm*. + +Now, if you go to the event type form, the smart button will indicate how many +reserved seats exist. + +If you want to convert those reservations into real event registrations: + +#. Go to the quotation you just created (if you are not there yet). +#. Click on *Register in event*. +#. In the wizard you see, set the *Event* and *Event Ticket* for all the order + lines you want to convert into registrations. +#. If there is any line you still don't want to convert, remove it from the + wizard. +#. Click on *Next*. +#. A new wizard will appear, where you will be able to specify the name, email + and phone of each one of the attendees. If you don't do it, they will get + that info from the sales order customer. +#. After that's done, click on *Apply*. + +At this point, the sales order lines will be modified to include the ticket +product instead of the reservation product, and the event reservations have +been created, linked to those lines. + +If the event is set to autoconfirmation, the registrations are confirmed., otherwise, they are +kept as draft until an invoice is created for the sales order, and paid. But +that is just upstream ``event_sale`` module in action. diff --git a/event_sale_reservation/reports/__init__.py b/event_sale_reservation/reports/__init__.py new file mode 100644 index 000000000..cd23411b8 --- /dev/null +++ b/event_sale_reservation/reports/__init__.py @@ -0,0 +1 @@ +from . import sale_report diff --git a/event_sale_reservation/reports/sale_report.py b/event_sale_reservation/reports/sale_report.py new file mode 100644 index 000000000..13815fb47 --- /dev/null +++ b/event_sale_reservation/reports/sale_report.py @@ -0,0 +1,29 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class SaleReport(models.Model): + _inherit = "sale.report" + + event_reservation_type_id = fields.Many2one( + comodel_name="event.type", + readonly=True, + string="Event reservation type", + ) + + def _query(self, with_clause="", fields=None, groupby="", from_clause=""): + if fields is None: + fields = {} + select_str = """ , + t.event_reservation_type_id as event_reservation_type_id + """ + fields.update({"event_reservation_type_id": select_str}) + groupby += ", t.event_reservation_type_id" + return super()._query( + with_clause=with_clause, + fields=fields, + groupby=groupby, + from_clause=from_clause, + ) diff --git a/event_sale_reservation/reports/sale_report_view.xml b/event_sale_reservation/reports/sale_report_view.xml new file mode 100644 index 000000000..474ab40dd --- /dev/null +++ b/event_sale_reservation/reports/sale_report_view.xml @@ -0,0 +1,19 @@ + + + + + Event type search + sale.report + + + + + + + + diff --git a/event_sale_reservation/static/description/icon.png b/event_sale_reservation/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/event_sale_reservation/static/description/icon.png differ diff --git a/event_sale_reservation/static/description/index.html b/event_sale_reservation/static/description/index.html new file mode 100644 index 000000000..ff39258b4 --- /dev/null +++ b/event_sale_reservation/static/description/index.html @@ -0,0 +1,509 @@ + + + + + + +Sell event reservations + + + +
+

Sell event reservations

+ + +

Beta License: AGPL-3 OCA/event Translate me on Weblate Try me on Runbot

+

This module extends the functionality of event_sale to support selling +reservations of events that still don’t exist and to allow you to schedule the +creation of events based on how many reservations already exist.

+

Table of contents

+ +
+

Installation

+

To install this module, you need to:

+
    +
  1. Install web_ir_actions_act_multi from https://github.com/OCA/web
  2. +
  3. Install web_ir_actions_act_view_reload from https://github.com/OCA/web
  4. +
+
+
+

Configuration

+

To make use of this module, a user needs these minimal permissions:

+
    +
  • Sales / User: Own Documents Only
  • +
  • Events / User
  • +
+
+
+

Usage

+

To know how many reservations exist for a given event type:

+
    +
  1. Go to Events > Configuration > Event Templates and pick or create one.
  2. +
  3. There’s a new smart button called Reserved seats with that count.
  4. +
  5. Click on it to get to the sales orders where the seats got reserved.
  6. +
+

But that counter will be probably zero when you install, so let’s see how to +increase it.

+

To create an event reservation product:

+
    +
  1. Go to Sales > Products > Products.
  2. +
  3. Create one and set Product Type to Event Reservation.
  4. +
  5. Select one Event type for reservations.
  6. +
  7. Save.
  8. +
+

From now on, you can sell event reservations for that event type. To do it:

+
    +
  1. Go to Sales > Orders > Quotations.
  2. +
  3. Create one.
  4. +
  5. Set its basic info (customer, date…) and go to Order lines tab.
  6. +
  7. Click Add a product.
  8. +
  9. Select the event reservation product you created above.
  10. +
  11. Set its info (quantity, price…).
  12. +
  13. Save that line and the quotation.
  14. +
+

At this point, the reservation is not yet confirmed, so if you go to the event +type, the smart button will still count zero.

+

To confirm those reservations:

+
    +
  1. Go to the quotation you just created (if you are not there yet).
  2. +
  3. Click on Confirm.
  4. +
+

Now, if you go to the event type form, the smart button will indicate how many +reserved seats exist.

+

If you want to convert those reservations into real event registrations:

+
    +
  1. Go to the quotation you just created (if you are not there yet).
  2. +
  3. Click on Register in event.
  4. +
  5. In the wizard you see, set the Event and Event Ticket for all the order +lines you want to convert into registrations.
  6. +
  7. If there is any line you still don’t want to convert, remove it from the +wizard.
  8. +
  9. Click on Next.
  10. +
  11. A new wizard will appear, where you will be able to specify the name, email +and phone of each one of the attendees. If you don’t do it, they will get +that info from the sales order customer.
  12. +
  13. After that’s done, click on Apply.
  14. +
+

At this point, the sales order lines will be modified to include the ticket +product instead of the reservation product, and the event reservations have +been created, linked to those lines.

+

If the event is set to autoconfirmation, the registrations are confirmed., otherwise, they are +kept as draft until an invoice is created for the sales order, and paid. But +that is just upstream event_sale module in action.

+
+
+

Known issues / Roadmap

+

Some addons (event_registration_multi_qty + event_sale_registration_multi_qty) +makes totals wrong because they depend currently on count and not sum of qtys; +integrating with them would require a glue module.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

Yajo

+

This module is part of the OCA/event project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/event_sale_reservation/tests/__init__.py b/event_sale_reservation/tests/__init__.py new file mode 100644 index 000000000..c755c1872 --- /dev/null +++ b/event_sale_reservation/tests/__init__.py @@ -0,0 +1 @@ +from . import test_event_sale diff --git a/event_sale_reservation/tests/test_event_sale.py b/event_sale_reservation/tests/test_event_sale.py new file mode 100644 index 000000000..72da43949 --- /dev/null +++ b/event_sale_reservation/tests/test_event_sale.py @@ -0,0 +1,162 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# Copyright 2023 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from datetime import datetime, timedelta + +from odoo.tests.common import Form, TransactionCase + +from ..exceptions import ReservationWithoutEventTypeError + + +class EventSaleCase(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + qtys = (1, 10, 100) + cls.event_types = cls.env["event.type"].create( + [{"name": "event type %d" % num} for num in range(3)] + ) + cls.products = cls.env["product.product"].create( + [ + { + "detailed_type": "event_reservation", + "event_reservation_type_id": cls.event_types[num].id, + "list_price": num, + "name": "product reservation for event type %d" % num, + } + for num in range(3) + ] + ) + cls.events = cls.env["event.event"].create( + [ + { + "date_begin": datetime.now(), + "date_end": datetime.now() + timedelta(days=1), + "event_ticket_ids": [ + ( + 0, + 0, + { + "name": "ticket %d" % num, + "product_id": cls.products[num].id, + }, + ) + ], + "event_type_id": cls.event_types[num].id, + "name": "event %d" % num, + } + for num in range(3) + ] + ) + cls.customers = cls.env["res.partner"].create( + [ + { + "email": "%d@example.com" % num, + "name": "customer %d" % num, + "phone": num, + } + for num in range(3) + ] + ) + cls.orders = cls.env["sale.order"].create( + [ + { + "order_line": [ + ( + 0, + 0, + { + "product_id": cls.products[num].id, + "product_uom_qty": qtys[num], + }, + ), + ], + "partner_id": cls.customers[num].id, + } + for num in range(3) + ] + ) + + def wizard_reservation_to_registration(self, order): + """Generate a wizard to register reservations.""" + return Form( + self.env["registration.editor"].with_context( + active_id=order.id, + active_ids=order.ids, + active_model=order._name, + registering_reservations=True, + ), + view="event_sale_reservation.registration_editor_reservations_view_form", + ) + + def wizard_step_2(self, wizard1): + """Generate a step 2 wizard from the step 1 wizard.""" + multi_action = wizard1.action_convert_to_registration() + # Ensure we first close the first wizard + self.assertEqual(multi_action["type"], "ir.actions.act_multi") + self.assertEqual( + multi_action["actions"][0]["type"], "ir.actions.act_window_close" + ) + # Get form from 2nd chained action + action = multi_action["actions"][1] + return Form( + self.env[action["res_model"]].with_context(**action["context"]), + view=action["view_id"], + ) + + def test_wrong_product_reservation_without_type(self): + """Event reservation products require the type.""" + with self.assertRaises(ReservationWithoutEventTypeError): + self.env["product.product"].create( + { + "detailed_type": "event_reservation", + "list_price": 10, + "name": "event reservation without event type fails", + } + ) + + def test_event_type_open_orders(self): + """Test the smart button that opens orders from an event type.""" + self.orders.action_confirm() + groups = zip(self.event_types, self.orders, (1, 10, 100)) + for ev_type, so, reservations in groups: + self.assertEqual(ev_type.seats_reservation_total, reservations) + action = ev_type.action_open_sale_orders() + found_so = self.env[action["res_model"]].search(action["domain"]) + self.assertEqual(found_so, so) + + def test_sale_workflow(self): + # Start: orders are draft, all is pending, nothing is reserved + self.orders.invalidate_cache(["event_reservations_pending"]) + self.assertEqual(self.orders.mapped("event_reservations_pending"), [1, 10, 100]) + self.assertEqual(self.event_types.mapped("seats_reservation_total"), [0, 0, 0]) + # Confirm orders: all is pending, all is reserved + self.orders.action_confirm() + self.orders.invalidate_cache(["event_reservations_pending"]) + self.assertEqual(self.orders.mapped("event_reservations_pending"), [1, 10, 100]) + self.assertEqual( + self.event_types.mapped("seats_reservation_total"), [1, 10, 100] + ) + # Register 2nd SO to event using wizard + wiz1 = self.wizard_reservation_to_registration(self.orders[1]) + self.assertEqual(len(wiz1.event_registration_ids), 1) + wiz1_line = wiz1.event_registration_ids.edit(0) + wiz1_line.event_id = self.events[1] + wiz1_line.event_ticket_id = self.events[1].event_ticket_ids + wiz1_line.save() + # Click "Next": opens 2nd wizard to configure registrations + wiz2 = self.wizard_step_2(wiz1.save()) + self.assertEqual(len(wiz2.event_registration_ids), 10) + for num in range(len(wiz2.event_registration_ids)): + wiz2_line = wiz2.event_registration_ids.edit(num) + wiz2_line.name = "name %d" % num + wiz2_line.email = "%d@example.com" % num + wiz2_line.phone = "phone %d" % num + wiz2_line.save() + wiz2.save().action_make_registration() + # 1st and 3rd SO are pending and reserved + self.orders.invalidate_cache(["event_reservations_pending"]) + self.assertEqual(self.orders.mapped("event_reservations_pending"), [1, 0, 100]) + self.assertEqual( + self.event_types.mapped("seats_reservation_total"), [1, 0, 100] + ) diff --git a/event_sale_reservation/views/event_type_view.xml b/event_sale_reservation/views/event_type_view.xml new file mode 100644 index 000000000..79b0e3f7b --- /dev/null +++ b/event_sale_reservation/views/event_type_view.xml @@ -0,0 +1,40 @@ + + + + + Link to reservations from type + event.type + + + +
+ +
+ + +
+
+
+
+ + Rerservations info from event category tree + event.type + + + + + + + + +
diff --git a/event_sale_reservation/views/product_template_view.xml b/event_sale_reservation/views/product_template_view.xml new file mode 100644 index 000000000..f2f470b47 --- /dev/null +++ b/event_sale_reservation/views/product_template_view.xml @@ -0,0 +1,19 @@ + + + + + Allow products that are event registrations + product.template + + + + + + + + + diff --git a/event_sale_reservation/views/sale_order_view.xml b/event_sale_reservation/views/sale_order_view.xml new file mode 100644 index 000000000..37f5210a7 --- /dev/null +++ b/event_sale_reservation/views/sale_order_view.xml @@ -0,0 +1,39 @@ + + + + + Event reservation management + sale.order + + + +
+ +
+ + + +
+
+
diff --git a/event_sale_reservation/wizards/__init__.py b/event_sale_reservation/wizards/__init__.py new file mode 100644 index 000000000..750cb451f --- /dev/null +++ b/event_sale_reservation/wizards/__init__.py @@ -0,0 +1,2 @@ +from . import registration_editor +from . import registration_editor_line diff --git a/event_sale_reservation/wizards/registration_editor.py b/event_sale_reservation/wizards/registration_editor.py new file mode 100644 index 000000000..5edbd5473 --- /dev/null +++ b/event_sale_reservation/wizards/registration_editor.py @@ -0,0 +1,75 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class RegistrationEditor(models.TransientModel): + _inherit = "registration.editor" + + @api.model + def default_get(self, fields): + """Get data needed to register reservations.""" + result = super().default_get(fields) + if self.env.context.get("registering_reservations"): + order = self.env["sale.order"].browse(result["sale_order_id"]) + result["event_registration_ids"] = [] + for sol in order.order_line: + if sol.product_id.detailed_type != "event_reservation": + continue + sol_type = sol.event_reservation_type_id + result["event_registration_ids"] += [ + ( + 0, + 0, + { + "sale_order_line_id": sol.id, + "event_reservation_type_id": sol_type.id, + }, + ) + ] + return result + + def action_convert_to_registration(self): + """Convert reservations to registrations. + We use the skip_event_sale_registration_multi_qty context to "skip" the + operation of the event_sale_registration_multi_qty addon because they are + "incompatible with each other".""" + # Modify SO lines to be tickets instead of reservations + for line in self.event_registration_ids: + line.sale_order_line_id.write( + { + "event_id": line.event_id.id, + "event_ticket_id": line.event_ticket_id.id, + "product_id": line.event_ticket_id.product_id.id, + } + ) + # Close current wizard and reopen normally to configure registrations + upstream_view = self.env.ref("event_sale.view_event_registration_editor_form") + return { + "type": "ir.actions.act_multi", + "actions": [ + {"type": "ir.actions.act_window_close"}, + { + "context": dict( + self.env.context, + registering_reservations=False, + skip_event_sale_registration_multi_qty=True, + ), + "res_model": self._name, + "target": "new", + "type": "ir.actions.act_window", + "view_id": upstream_view.id, + "view_mode": "form", + "views": [[upstream_view.id, "form"]], + }, + ], + } + + def action_make_registration(self): + """Force main view reload after finishing.""" + result = super().action_make_registration() + return { + "type": "ir.actions.act_multi", + "actions": [result, {"type": "ir.actions.act_view_reload"}], + } diff --git a/event_sale_reservation/wizards/registration_editor_line.py b/event_sale_reservation/wizards/registration_editor_line.py new file mode 100644 index 000000000..b661fc28e --- /dev/null +++ b/event_sale_reservation/wizards/registration_editor_line.py @@ -0,0 +1,13 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class RegistrationEditorLine(models.TransientModel): + _inherit = "registration.editor.line" + + event_reservation_type_id = fields.Many2one( + related="sale_order_line_id.event_reservation_type_id", + readonly=True, + ) diff --git a/event_sale_reservation/wizards/registration_editor_view.xml b/event_sale_reservation/wizards/registration_editor_view.xml new file mode 100644 index 000000000..c39381dd0 --- /dev/null +++ b/event_sale_reservation/wizards/registration_editor_view.xml @@ -0,0 +1,61 @@ + + + + + + Convert reservation into registrations + registration.editor + +
+ +

+ Convert pending event reservations into registrations for + +

+

+ If there is any line from that order that you still do + not want to convert into real event registrations, you + can remove it from the list below. You will be able to + repeat this process later for those lines. +

+
+ + + + + + + + +
+
+
+
+
+ + Register in event + ir.actions.act_window + registration.editor + form + + new + {'registering_reservations': True} + +
diff --git a/setup/event_sale_reservation/odoo/addons/event_sale_reservation b/setup/event_sale_reservation/odoo/addons/event_sale_reservation new file mode 120000 index 000000000..1837cc3ef --- /dev/null +++ b/setup/event_sale_reservation/odoo/addons/event_sale_reservation @@ -0,0 +1 @@ +../../../../event_sale_reservation \ No newline at end of file diff --git a/setup/event_sale_reservation/setup.py b/setup/event_sale_reservation/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/event_sale_reservation/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)