From dbcbd84a24bfe48dd294b7b0f4732a2d0f8a603a Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 5 Feb 2021 16:42:03 +0000 Subject: [PATCH 1/5] [ADD] event_sale_reservation: sell events before they exist 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. Includes integration workaround to avoid test conflicts with `event_sale_registration_multi_qty`. @Tecnativa TT27664 [UPD] Update event_sale_reservation.pot [UPD] README.rst --- event_sale_reservation/README.rst | 164 ++++++ event_sale_reservation/__init__.py | 3 + event_sale_reservation/__manifest__.py | 31 ++ event_sale_reservation/exceptions.py | 12 + event_sale_reservation/i18n/es.po | 218 ++++++++ .../i18n/event_sale_reservation.pot | 201 +++++++ event_sale_reservation/models/__init__.py | 4 + event_sale_reservation/models/event_type.py | 61 +++ .../models/product_template.py | 47 ++ event_sale_reservation/models/sale_order.py | 52 ++ .../models/sale_order_line.py | 32 ++ event_sale_reservation/readme/CONFIGURE.rst | 5 + .../readme/CONTRIBUTORS.rst | 1 + event_sale_reservation/readme/DESCRIPTION.rst | 3 + event_sale_reservation/readme/INSTALL.rst | 4 + event_sale_reservation/readme/USAGE.rst | 60 +++ event_sale_reservation/reports/__init__.py | 1 + event_sale_reservation/reports/sale_report.py | 25 + .../reports/sale_report_view.xml | 15 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 501 ++++++++++++++++++ event_sale_reservation/tests/__init__.py | 1 + .../tests/test_event_sale.py | 175 ++++++ .../views/event_type_view.xml | 39 ++ .../views/product_template_view.xml | 23 + .../views/sale_order_view.xml | 29 + event_sale_reservation/wizards/__init__.py | 2 + .../wizards/registration_editor.py | 71 +++ .../wizards/registration_editor_line.py | 13 + .../wizards/registration_editor_view.xml | 52 ++ 30 files changed, 1845 insertions(+) create mode 100644 event_sale_reservation/README.rst create mode 100644 event_sale_reservation/__init__.py create mode 100644 event_sale_reservation/__manifest__.py create mode 100644 event_sale_reservation/exceptions.py create mode 100644 event_sale_reservation/i18n/es.po create mode 100644 event_sale_reservation/i18n/event_sale_reservation.pot create mode 100644 event_sale_reservation/models/__init__.py create mode 100644 event_sale_reservation/models/event_type.py create mode 100644 event_sale_reservation/models/product_template.py create mode 100644 event_sale_reservation/models/sale_order.py create mode 100644 event_sale_reservation/models/sale_order_line.py create mode 100644 event_sale_reservation/readme/CONFIGURE.rst create mode 100644 event_sale_reservation/readme/CONTRIBUTORS.rst create mode 100644 event_sale_reservation/readme/DESCRIPTION.rst create mode 100644 event_sale_reservation/readme/INSTALL.rst create mode 100644 event_sale_reservation/readme/USAGE.rst create mode 100644 event_sale_reservation/reports/__init__.py create mode 100644 event_sale_reservation/reports/sale_report.py create mode 100644 event_sale_reservation/reports/sale_report_view.xml create mode 100644 event_sale_reservation/static/description/icon.png create mode 100644 event_sale_reservation/static/description/index.html create mode 100644 event_sale_reservation/tests/__init__.py create mode 100644 event_sale_reservation/tests/test_event_sale.py create mode 100644 event_sale_reservation/views/event_type_view.xml create mode 100644 event_sale_reservation/views/product_template_view.xml create mode 100644 event_sale_reservation/views/sale_order_view.xml create mode 100644 event_sale_reservation/wizards/__init__.py create mode 100644 event_sale_reservation/wizards/registration_editor.py create mode 100644 event_sale_reservation/wizards/registration_editor_line.py create mode 100644 event_sale_reservation/wizards/registration_editor_view.xml diff --git a/event_sale_reservation/README.rst b/event_sale_reservation/README.rst new file mode 100644 index 000000000..aa2af80e1 --- /dev/null +++ b/event_sale_reservation/README.rst @@ -0,0 +1,164 @@ +======================= +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/12.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-12-0/event-12-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/12.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 Categories* 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. +#. Set its basic info (name, price...) and go to *Sales* tab. +#. Under *Events*, tick *Is an 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 ticket was free, 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. + +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 +~~~~~~~~~~~~ + +* Jairo Llopis (https://www.tecnativa.com/) + +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..dc33056ea --- /dev/null +++ b/event_sale_reservation/__manifest__.py @@ -0,0 +1,31 @@ +# 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": "12.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", + ], + # These modules makes totals wrong because they depend currently on count + # and not sum of qtys; integrating with them would require a glue module + "excludes": ["event_registration_multi_qty", "event_sale_registration_multi_qty"], +} 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..d8829a93f --- /dev/null +++ b/event_sale_reservation/i18n/es.po @@ -0,0 +1,218 @@ +# 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: 2021-02-05 16:33+0000\n" +"PO-Revision-Date: 2021-02-05 16:37+0000\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 2.4.2\n" + +#. module: event_sale_reservation +#: model:ir.actions.act_window,name:event_sale_reservation.act_event_registration_from_so +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,name:event_sale_reservation.model_event_type +msgid "Event Category" +msgstr "Categoría de eventos" + +#. 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,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:ir.model.fields,help:event_sale_reservation.field_product_product__event_reservation_ok +#: model:ir.model.fields,help:event_sale_reservation.field_product_template__event_reservation_ok +msgid "" +"If checked, this product enables selling event reservations even before an " +"event of the specified type has been scheduled." +msgstr "" +"Si se activa, este producto permite vender reservas a eventos incluso antes " +"de que un evento de la categoría especificada se haya programado." + +#. 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:ir.model.fields,field_description:event_sale_reservation.field_product_product__event_reservation_ok +#: model:ir.model.fields,field_description:event_sale_reservation.field_product_template__event_reservation_ok +msgid "Is an event reservation" +msgstr "Es una reserva de evento" + +#. 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 +#: code:addons/event_sale_reservation/models/product_template.py:38 +#, python-format +msgid "" +"Product %(name)s cannot be both an event ticket and an event reservation." +msgstr "" +"El producto %(name)s no puede ser al mismo tiempo un ticket y una reserva de " +"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.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_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "Register reservations" +msgstr "Registrar reservas" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "Registrations" +msgstr "Registros" + +#. 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_order +msgid "Sale Order" +msgstr "Pedido de venta" + +#. 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_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:46 +#, 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..2ac4cdcf7 --- /dev/null +++ b/event_sale_reservation/i18n/event_sale_reservation.pot @@ -0,0 +1,201 @@ +# 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" +"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 +#: code:addons/event_sale_reservation/models/sale_order.py:47 +#, 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,name:event_sale_reservation.model_event_type +msgid "Event Category" +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,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:ir.model.fields,help:event_sale_reservation.field_product_product__event_reservation_ok +#: model:ir.model.fields,help:event_sale_reservation.field_product_template__event_reservation_ok +msgid "If checked, this product enables selling event reservations even before an event of the specified type has been scheduled." +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:ir.model.fields,field_description:event_sale_reservation.field_product_product__event_reservation_ok +#: model:ir.model.fields,field_description:event_sale_reservation.field_product_template__event_reservation_ok +msgid "Is an event reservation" +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 +#: code:addons/event_sale_reservation/models/product_template.py:38 +#, python-format +msgid "Product %(name)s cannot be both an event ticket and an event reservation." +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.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_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "Register reservations" +msgstr "" + +#. module: event_sale_reservation +#: model_terms:ir.ui.view,arch_db:event_sale_reservation.registration_editor_reservations_view_form +msgid "Registrations" +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_order +msgid "Sale Order" +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_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:46 +#, python-format +msgid "You must indicate event type for %(name)s." +msgstr "" + 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..6f8f713a0 --- /dev/null +++ b/event_sale_reservation/models/event_type.py @@ -0,0 +1,61 @@ +# 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.event_reservation_ok", "=", True), + ] + + @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.event_reservation_ok", + "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..fbf3c8405 --- /dev/null +++ b/event_sale_reservation/models/product_template.py @@ -0,0 +1,47 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from ..exceptions import TicketAndReservationError, ReservationWithoutEventTypeError + + +class Product(models.Model): + _inherit = "product.template" + + event_reservation_ok = fields.Boolean( + index=True, + string="Is an event reservation", + help=( + "If checked, this product enables selling event reservations " + "even before an event of the specified type has been scheduled." + ), + ) + 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", + ) + + @api.constrains("event_ok", "event_reservation_ok") + 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 not one.event_reservation_ok: + continue + if one.event_ok: + raise TicketAndReservationError( + _( + "Product %(name)s cannot be both an event ticket and " + "an event reservation." + ) + % {"name": one.display_name} + ) + 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..f11acad77 --- /dev/null +++ b/event_sale_reservation/models/sale_order.py @@ -0,0 +1,52 @@ +# 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( + "product_id.event_reservation_ok" + ) + 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", + "view_type": "form", + } 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..7ee45dc9b --- /dev/null +++ b/event_sale_reservation/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Jairo Llopis (https://www.tecnativa.com/) 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/USAGE.rst b/event_sale_reservation/readme/USAGE.rst new file mode 100644 index 000000000..34a87759b --- /dev/null +++ b/event_sale_reservation/readme/USAGE.rst @@ -0,0 +1,60 @@ +To know how many reservations exist for a given event type: + +#. Go to *Events > Configuration > Event Categories* 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. +#. Set its basic info (name, price...) and go to *Sales* tab. +#. Under *Events*, tick *Is an 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 ticket was free, 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..38c3302d2 --- /dev/null +++ b/event_sale_reservation/reports/sale_report.py @@ -0,0 +1,25 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models +from odoo.tools import frozendict + + +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=frozendict(), groupby="", from_clause=""): + fields = dict( + fields, + event_reservation_type_id=""" + , t.event_reservation_type_id as event_reservation_type_id + """, + ) + groupby += ", t.event_reservation_type_id" + return super()._query(with_clause, fields, groupby, 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..f97d9b3d8 --- /dev/null +++ b/event_sale_reservation/reports/sale_report_view.xml @@ -0,0 +1,15 @@ + + + + + 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 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/event_sale_reservation/static/description/index.html b/event_sale_reservation/static/description/index.html new file mode 100644 index 000000000..061419662 --- /dev/null +++ b/event_sale_reservation/static/description/index.html @@ -0,0 +1,501 @@ + + + + + + +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 Categories 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.
  4. +
  5. Set its basic info (name, price…) and go to Sales tab.
  6. +
  7. Under Events, tick Is an event reservation.
  8. +
  9. Select one Event type for reservations.
  10. +
  11. Save.
  12. +
+

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 ticket was free, 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.

+
+
+

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
  • +
+
+ +
+

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..7bdbf19d2 --- /dev/null +++ b/event_sale_reservation/tests/test_event_sale.py @@ -0,0 +1,175 @@ +# Copyright 2021 Tecnativa - Jairo Llopis +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from datetime import datetime, timedelta +from odoo.tests.common import Form, SavepointCase +from ..exceptions import ( + ReservationWithoutEventTypeError, + TicketAndReservationError, +) + + +class EventSaleCase(SavepointCase): + @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( + [ + { + "event_reservation_ok": True, + "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_event_and_reservation(self): + """A product cannot be both an event and a reservation.""" + with self.assertRaises(TicketAndReservationError): + self.env["product.product"].create( + { + "event_reservation_ok": True, + "event_ok": True, + "event_reservation_type_id": self.event_types[0].id, + "list_price": 10, + "name": "event and reservation fails", + } + ) + + def test_wrong_product_reservation_without_type(self): + """Event reservation products require the type.""" + with self.assertRaises(ReservationWithoutEventTypeError): + self.env["product.product"].create( + { + "event_reservation_ok": True, + "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..893845db5 --- /dev/null +++ b/event_sale_reservation/views/event_type_view.xml @@ -0,0 +1,39 @@ + + + + + + + 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..ecbb29487 --- /dev/null +++ b/event_sale_reservation/views/product_template_view.xml @@ -0,0 +1,23 @@ + + + + + + + Allow products that are event registrations + product.template + + + + + {'readonly': [('event_reservation_ok', '=', True)]} + + + + + + + + + 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..cbae96e18 --- /dev/null +++ b/event_sale_reservation/views/sale_order_view.xml @@ -0,0 +1,29 @@ + + + + + + + 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..e79e379c4 --- /dev/null +++ b/event_sale_reservation/wizards/registration_editor.py @@ -0,0 +1,71 @@ +# 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 not sol.product_id.event_reservation_ok: + continue + result["event_registration_ids"] += [ + ( + 0, + 0, + { + "sale_order_line_id": sol.id, + "event_reservation_type_id": + sol.event_reservation_type_id.id, + }, + ) + ] + return result + + def action_convert_to_registration(self): + """Convert reservations to registrations.""" + # 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), + "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..d3223c4ea --- /dev/null +++ b/event_sale_reservation/wizards/registration_editor_view.xml @@ -0,0 +1,52 @@ + + + + + + + + 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 + form + + new + {'registering_reservations': True} + + +
From 52d491d19ce62c08f6fb021921d3f56f3fecd196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Tue, 25 Jan 2022 13:04:43 +0100 Subject: [PATCH 2/5] [IMP] event_sale_reservation: black, isort, prettier --- event_sale_reservation/models/event_type.py | 9 ++---- .../models/product_template.py | 3 +- event_sale_reservation/reports/sale_report.py | 4 +-- .../reports/sale_report_view.xml | 8 +++-- .../tests/test_event_sale.py | 7 ++--- .../views/event_type_view.xml | 19 ++++++------ .../views/product_template_view.xml | 21 ++++++++----- .../views/sale_order_view.xml | 28 +++++++++++------ .../wizards/registration_editor.py | 8 ++--- .../wizards/registration_editor_line.py | 3 +- .../wizards/registration_editor_view.xml | 30 ++++++++++++------- 11 files changed, 79 insertions(+), 61 deletions(-) diff --git a/event_sale_reservation/models/event_type.py b/event_sale_reservation/models/event_type.py index 6f8f713a0..588d5bc0c 100644 --- a/event_sale_reservation/models/event_type.py +++ b/event_sale_reservation/models/event_type.py @@ -50,12 +50,7 @@ def _compute_reservations_total(self): 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", - ) + 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 index fbf3c8405..46e497d00 100644 --- a/event_sale_reservation/models/product_template.py +++ b/event_sale_reservation/models/product_template.py @@ -2,7 +2,8 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import _, api, fields, models -from ..exceptions import TicketAndReservationError, ReservationWithoutEventTypeError + +from ..exceptions import ReservationWithoutEventTypeError, TicketAndReservationError class Product(models.Model): diff --git a/event_sale_reservation/reports/sale_report.py b/event_sale_reservation/reports/sale_report.py index 38c3302d2..d1328820a 100644 --- a/event_sale_reservation/reports/sale_report.py +++ b/event_sale_reservation/reports/sale_report.py @@ -9,9 +9,7 @@ class SaleReport(models.Model): _inherit = "sale.report" event_reservation_type_id = fields.Many2one( - comodel_name="event.type", - readonly=True, - string="Event reservation type", + comodel_name="event.type", readonly=True, string="Event reservation type", ) def _query(self, with_clause="", fields=frozendict(), groupby="", from_clause=""): diff --git a/event_sale_reservation/reports/sale_report_view.xml b/event_sale_reservation/reports/sale_report_view.xml index f97d9b3d8..474ab40dd 100644 --- a/event_sale_reservation/reports/sale_report_view.xml +++ b/event_sale_reservation/reports/sale_report_view.xml @@ -1,4 +1,4 @@ - + @@ -8,7 +8,11 @@ - + diff --git a/event_sale_reservation/tests/test_event_sale.py b/event_sale_reservation/tests/test_event_sale.py index 7bdbf19d2..16bd304c5 100644 --- a/event_sale_reservation/tests/test_event_sale.py +++ b/event_sale_reservation/tests/test_event_sale.py @@ -1,11 +1,10 @@ # Copyright 2021 Tecnativa - Jairo Llopis # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from datetime import datetime, timedelta + from odoo.tests.common import Form, SavepointCase -from ..exceptions import ( - ReservationWithoutEventTypeError, - TicketAndReservationError, -) + +from ..exceptions import ReservationWithoutEventTypeError, TicketAndReservationError class EventSaleCase(SavepointCase): diff --git a/event_sale_reservation/views/event_type_view.xml b/event_sale_reservation/views/event_type_view.xml index 893845db5..79b0e3f7b 100644 --- a/event_sale_reservation/views/event_type_view.xml +++ b/event_sale_reservation/views/event_type_view.xml @@ -1,14 +1,12 @@ - + - - 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 index ecbb29487..3d8ad4743 100644 --- a/event_sale_reservation/views/product_template_view.xml +++ b/event_sale_reservation/views/product_template_view.xml @@ -1,23 +1,28 @@ - + - - Allow products that are event registrations product.template - + - {'readonly': [('event_reservation_ok', '=', True)]} + {'readonly': [('event_reservation_ok', '=', True)]} - - + + - diff --git a/event_sale_reservation/views/sale_order_view.xml b/event_sale_reservation/views/sale_order_view.xml index cbae96e18..5cf6844bb 100644 --- a/event_sale_reservation/views/sale_order_view.xml +++ b/event_sale_reservation/views/sale_order_view.xml @@ -1,9 +1,7 @@ - + - - Event reservation management sale.order @@ -11,19 +9,31 @@ -
-
- - +
-
diff --git a/event_sale_reservation/wizards/registration_editor.py b/event_sale_reservation/wizards/registration_editor.py index e79e379c4..44d3e5de0 100644 --- a/event_sale_reservation/wizards/registration_editor.py +++ b/event_sale_reservation/wizards/registration_editor.py @@ -23,8 +23,7 @@ def default_get(self, fields): 0, { "sale_order_line_id": sol.id, - "event_reservation_type_id": - sol.event_reservation_type_id.id, + "event_reservation_type_id": sol.event_reservation_type_id.id, }, ) ] @@ -64,8 +63,5 @@ def action_make_registration(self): result = super().action_make_registration() return { "type": "ir.actions.act_multi", - "actions": [ - result, - {"type": "ir.actions.act_view_reload"}, - ], + "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 index b661fc28e..47cae677c 100644 --- a/event_sale_reservation/wizards/registration_editor_line.py +++ b/event_sale_reservation/wizards/registration_editor_line.py @@ -8,6 +8,5 @@ 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, + 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 index d3223c4ea..408f491e9 100644 --- a/event_sale_reservation/wizards/registration_editor_view.xml +++ b/event_sale_reservation/wizards/registration_editor_view.xml @@ -1,9 +1,7 @@ - + - - Convert reservation into registrations @@ -24,20 +22,33 @@ - - - - + + + +
-
- Register in event ir.actions.act_window @@ -48,5 +59,4 @@ new {'registering_reservations': True} -
From d1a6a3718097c1b1979e2b8fc1aefe248b3ba855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Tue, 25 Jan 2022 13:18:50 +0100 Subject: [PATCH 3/5] [MIG] event_sale_reservation: Migration to 13.0 TT31321 [UPD] Update event_sale_reservation.pot [UPD] README.rst --- event_sale_reservation/README.rst | 21 +++++++--- event_sale_reservation/__manifest__.py | 5 +-- .../i18n/event_sale_reservation.pot | 35 +++++++++-------- event_sale_reservation/models/event_type.py | 2 +- event_sale_reservation/models/sale_order.py | 1 - .../readme/CONTRIBUTORS.rst | 4 +- event_sale_reservation/readme/ROADMAP.rst | 3 ++ event_sale_reservation/reports/sale_report.py | 22 ++++++----- .../static/description/index.html | 38 ++++++++++++------- .../views/sale_order_view.xml | 2 +- .../wizards/registration_editor.py | 16 ++++++-- .../wizards/registration_editor_view.xml | 1 - 12 files changed, 93 insertions(+), 57 deletions(-) create mode 100644 event_sale_reservation/readme/ROADMAP.rst diff --git a/event_sale_reservation/README.rst b/event_sale_reservation/README.rst index aa2af80e1..f9686f7b0 100644 --- a/event_sale_reservation/README.rst +++ b/event_sale_reservation/README.rst @@ -14,13 +14,13 @@ Sell event reservations :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/12.0/event_sale_reservation + :target: https://github.com/OCA/event/tree/13.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-12-0/event-12-0-event_sale_reservation + :target: https://translation.odoo-community.org/projects/event-13-0/event-13-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/12.0 + :target: https://runbot.odoo-community.org/runbot/199/13.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -115,13 +115,20 @@ If the ticket was free, 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -136,7 +143,9 @@ Authors Contributors ~~~~~~~~~~~~ -* Jairo Llopis (https://www.tecnativa.com/) +* `Tecnativa `_: + + * Jairo Llopis Maintainers ~~~~~~~~~~~ @@ -159,6 +168,6 @@ Current `maintainer `__: |maintainer-Yajo| -This module is part of the `OCA/event `_ project on GitHub. +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/__manifest__.py b/event_sale_reservation/__manifest__.py index dc33056ea..c1d6603c3 100644 --- a/event_sale_reservation/__manifest__.py +++ b/event_sale_reservation/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Sell event reservations", "summary": "Allow selling event registrations before the event exists", - "version": "12.0.1.0.0", + "version": "13.0.1.0.0", "development_status": "Beta", "category": "Marketing", "website": "https://github.com/OCA/event", @@ -25,7 +25,4 @@ "views/product_template_view.xml", "views/sale_order_view.xml", ], - # These modules makes totals wrong because they depend currently on count - # and not sum of qtys; integrating with them would require a glue module - "excludes": ["event_registration_multi_qty", "event_sale_registration_multi_qty"], } diff --git a/event_sale_reservation/i18n/event_sale_reservation.pot b/event_sale_reservation/i18n/event_sale_reservation.pot index 2ac4cdcf7..9c318db3b 100644 --- a/event_sale_reservation/i18n/event_sale_reservation.pot +++ b/event_sale_reservation/i18n/event_sale_reservation.pot @@ -1,12 +1,12 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * event_sale_reservation +# * event_sale_reservation # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 12.0\n" +"Project-Id-Version: Odoo Server 13.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: <>\n" +"Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -14,7 +14,7 @@ msgstr "" "Plural-Forms: \n" #. module: event_sale_reservation -#: code:addons/event_sale_reservation/models/sale_order.py:47 +#: code:addons/event_sale_reservation/models/sale_order.py:0 #, python-format msgid "Attendees" msgstr "" @@ -91,12 +91,15 @@ msgstr "" #. module: event_sale_reservation #: model:ir.model.fields,help:event_sale_reservation.field_product_product__event_reservation_ok #: model:ir.model.fields,help:event_sale_reservation.field_product_template__event_reservation_ok -msgid "If checked, this product enables selling event reservations even before an event of the specified type has been scheduled." +msgid "" +"If checked, this product enables selling event reservations even before an " +"event of the specified type has been scheduled." 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" +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." @@ -109,7 +112,9 @@ 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." +msgid "" +"Indicates how many event reservations are still not linked to any " +"registration." msgstr "" #. module: event_sale_reservation @@ -129,9 +134,10 @@ msgid "Pending event reservations" msgstr "" #. module: event_sale_reservation -#: code:addons/event_sale_reservation/models/product_template.py:38 +#: code:addons/event_sale_reservation/models/product_template.py:0 #, python-format -msgid "Product %(name)s cannot be both an event ticket and an event reservation." +msgid "" +"Product %(name)s cannot be both an event ticket and an event reservation." msgstr "" #. module: event_sale_reservation @@ -166,13 +172,13 @@ msgid "Reserved seats" msgstr "" #. module: event_sale_reservation -#: model:ir.model,name:event_sale_reservation.model_sale_order -msgid "Sale Order" +#: 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_report -msgid "Sales Analysis Report" +#: model:ir.model,name:event_sale_reservation.model_sale_order +msgid "Sales Order" msgstr "" #. module: event_sale_reservation @@ -194,8 +200,7 @@ 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:46 +#: 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/models/event_type.py b/event_sale_reservation/models/event_type.py index 588d5bc0c..f78c1acef 100644 --- a/event_sale_reservation/models/event_type.py +++ b/event_sale_reservation/models/event_type.py @@ -51,6 +51,6 @@ def _compute_reservations_total(self): 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 = 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/sale_order.py b/event_sale_reservation/models/sale_order.py index f11acad77..4e92b4e30 100644 --- a/event_sale_reservation/models/sale_order.py +++ b/event_sale_reservation/models/sale_order.py @@ -48,5 +48,4 @@ def action_open_event_registrations(self): "res_model": "event.registration", "type": "ir.actions.act_window", "view_mode": "tree,form,calendar,graph", - "view_type": "form", } diff --git a/event_sale_reservation/readme/CONTRIBUTORS.rst b/event_sale_reservation/readme/CONTRIBUTORS.rst index 7ee45dc9b..8c4d96846 100644 --- a/event_sale_reservation/readme/CONTRIBUTORS.rst +++ b/event_sale_reservation/readme/CONTRIBUTORS.rst @@ -1 +1,3 @@ -* Jairo Llopis (https://www.tecnativa.com/) +* `Tecnativa `_: + + * Jairo Llopis 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/reports/sale_report.py b/event_sale_reservation/reports/sale_report.py index d1328820a..7006e66ee 100644 --- a/event_sale_reservation/reports/sale_report.py +++ b/event_sale_reservation/reports/sale_report.py @@ -2,7 +2,6 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import fields, models -from odoo.tools import frozendict class SaleReport(models.Model): @@ -12,12 +11,17 @@ class SaleReport(models.Model): comodel_name="event.type", readonly=True, string="Event reservation type", ) - def _query(self, with_clause="", fields=frozendict(), groupby="", from_clause=""): - fields = dict( - fields, - event_reservation_type_id=""" - , t.event_reservation_type_id as event_reservation_type_id - """, - ) + 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, fields, groupby, from_clause) + return super()._query( + with_clause=with_clause, + fields=fields, + groupby=groupby, + from_clause=from_clause, + ) diff --git a/event_sale_reservation/static/description/index.html b/event_sale_reservation/static/description/index.html index 061419662..0040c60d2 100644 --- a/event_sale_reservation/static/description/index.html +++ b/event_sale_reservation/static/description/index.html @@ -367,7 +367,7 @@

Sell event reservations

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

+

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.

@@ -377,11 +377,12 @@

Sell event reservations

  • Installation
  • Configuration
  • Usage
  • -
  • Bug Tracker
  • -
  • Credits @@ -461,30 +462,39 @@

    Usage

    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

    +

    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.

    +feedback.

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

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • Tecnativa
    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association

    OCA, or the Odoo Community Association, is a nonprofit organization whose @@ -492,7 +502,7 @@

    Maintainers

    promote its widespread use.

    Current maintainer:

    Yajo

    -

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

    +

    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/views/sale_order_view.xml b/event_sale_reservation/views/sale_order_view.xml index 5cf6844bb..37f5210a7 100644 --- a/event_sale_reservation/views/sale_order_view.xml +++ b/event_sale_reservation/views/sale_order_view.xml @@ -5,7 +5,7 @@ Event reservation management sale.order - +