From 1f428192341f9ed46257a71469e5b0a13f88c74b Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Mon, 23 Mar 2026 23:49:54 +0100 Subject: [PATCH 1/2] [REF] rental_base_extension: move rental_base-dependent code from sale_rental_extension sale_rental_extension referenced rental_base XML IDs (views, menus, rental_sale_type) and sale_order_type fields (type_id) without declaring rental_base as a dependency. Instead of adding coupling, move all rental_base-dependent code to rental_base_extension where it belongs. Moved from sale_rental_extension: - SaleOrder model (rental status tracking, is_rental_order, picking helpers) - StockPicking model (is_rental_picking, sign-to-validate) - ResCompany and ResConfigSettings (signature terms) - Dedicated rental form/tree/kanban/calendar/search views and actions - Rental app menus and rental_base view deactivations - Config settings view, stock picking view, delivery slip report - JS signature widget, SCSS kanban styles, XML signature dialog - Rental status tests All XML IDs, view names, file names and internal identifiers follow OCA naming conventions (sale_order_view_form_rental, sale_order_action_rental, sale_order_menu_rental, etc.). --- rental_base_extension/README.rst | 40 +- rental_base_extension/__init__.py | 1 + rental_base_extension/__manifest__.py | 17 +- rental_base_extension/i18n/ca.po | 254 +++++++ rental_base_extension/i18n/es.po | 254 +++++++ rental_base_extension/models/__init__.py | 4 + rental_base_extension/models/res_company.py | 14 + .../models/res_config_settings.py | 18 + rental_base_extension/models/sale_order.py | 134 ++++ rental_base_extension/models/stock_picking.py | 41 ++ rental_base_extension/readme/DESCRIPTION.rst | 40 +- .../report/report_deliveryslip.xml | 41 ++ .../static/description/index.html | 37 +- .../static/src/js/.eslintrc.yml | 2 + .../static/src/js/signature_widget.js | 66 ++ .../static/src/scss/rental_kanban.scss | 9 + .../static/src/xml/signature_dialog.xml | 39 ++ rental_base_extension/tests/__init__.py | 1 + .../tests/test_rental_status.py | 156 +++++ .../views/res_config_settings_views.xml | 56 ++ .../views/sale_order_views.xml | 661 ++++++++++++++++++ .../views/stock_picking_views.xml | 108 +++ 22 files changed, 1960 insertions(+), 33 deletions(-) create mode 100644 rental_base_extension/models/__init__.py create mode 100644 rental_base_extension/models/res_company.py create mode 100644 rental_base_extension/models/res_config_settings.py create mode 100644 rental_base_extension/models/sale_order.py create mode 100644 rental_base_extension/models/stock_picking.py create mode 100644 rental_base_extension/report/report_deliveryslip.xml create mode 100644 rental_base_extension/static/src/js/.eslintrc.yml create mode 100644 rental_base_extension/static/src/js/signature_widget.js create mode 100644 rental_base_extension/static/src/scss/rental_kanban.scss create mode 100644 rental_base_extension/static/src/xml/signature_dialog.xml create mode 100644 rental_base_extension/tests/__init__.py create mode 100644 rental_base_extension/tests/test_rental_status.py create mode 100644 rental_base_extension/views/res_config_settings_views.xml create mode 100644 rental_base_extension/views/sale_order_views.xml create mode 100644 rental_base_extension/views/stock_picking_views.xml diff --git a/rental_base_extension/README.rst b/rental_base_extension/README.rst index f5a320ea6..2e7845c59 100644 --- a/rental_base_extension/README.rst +++ b/rental_base_extension/README.rst @@ -26,24 +26,44 @@ Rental Base Extension |badge1| |badge2| |badge3| -This module extends ``rental_base`` to improve the rental order experience -with better menu visibility and product filtering. +This module extends ``rental_base`` to provide a complete rental order +management experience. + +Dedicated rental views +~~~~~~~~~~~~~~~~~~~~~~ + +Replaces the default sale order views with dedicated rental-specific views: +form, tree, kanban, calendar and search. The form view hides non-rental fields +and adds rental period, duration and status badges. Actions set the rental sale +type by default. + +Rental status tracking +~~~~~~~~~~~~~~~~~~~~~~ + +Adds computed ``rental_status`` on sale orders based on the state of the +underlying ``sale.rental`` records: draft, pickup, return, returned and cancel. +A "late" indicator warns when the next action date has passed. Rental Positions menu ~~~~~~~~~~~~~~~~~~~~~ -The ``rental_base`` module places the Rental Positions screen (the ``sale.rental`` -list) under Configuration and restricts it to debug mode. This module moves it -to the top level of the Rentals menu as the first entry, visible to all sales -users, since it is the primary screen for tracking rental status. +The ``rental_base`` module places the Rental Positions screen under +Configuration and restricts it to debug mode. This module moves it to the +top level of the Rentals menu as the first entry, visible to all sales users. Product filter on rental order lines ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When creating a rental order, the product dropdown on order lines only shows -rental service products (i.e. products linked to a rented physical product -via ``rented_product_id``). This prevents users from accidentally selecting -non-rental products on rental orders. +When creating a rental order, the product dropdown only shows rental service +products, preventing users from accidentally selecting non-rental products. + +Signature and delivery +~~~~~~~~~~~~~~~~~~~~~~ + +Configurable signature terms for rental delivery orders (Settings → Rental → +Signature). When enabled, the Validate button is replaced by a Sign button +that auto-validates on signature. The signed conditions appear on the delivery +slip PDF. Catalan and Spanish translations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/rental_base_extension/__init__.py b/rental_base_extension/__init__.py index e69de29bb..0650744f6 100644 --- a/rental_base_extension/__init__.py +++ b/rental_base_extension/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/rental_base_extension/__manifest__.py b/rental_base_extension/__manifest__.py index 54fa78a22..2fc4f298a 100644 --- a/rental_base_extension/__manifest__.py +++ b/rental_base_extension/__manifest__.py @@ -3,9 +3,9 @@ { "name": "Rental Base Extension", - "summary": "Improves rental product form with checkbox visibility," - " rental sections and translations.", - "version": "16.0.1.0.0", + "summary": "Dedicated rental views, rental status tracking," + " kanban, calendar, configurable signature terms and translations.", + "version": "16.0.2.0.0", "development_status": "Alpha", "category": "Rental", "author": "NuoBiT Solutions SL", @@ -15,6 +15,17 @@ "rental_base", ], "data": [ + "report/report_deliveryslip.xml", "views/product_views.xml", + "views/sale_order_views.xml", + "views/res_config_settings_views.xml", + "views/stock_picking_views.xml", ], + "assets": { + "web.assets_backend": [ + "rental_base_extension/static/src/js/signature_widget.js", + "rental_base_extension/static/src/scss/rental_kanban.scss", + "rental_base_extension/static/src/xml/signature_dialog.xml", + ], + }, } diff --git a/rental_base_extension/i18n/ca.po b/rental_base_extension/i18n/ca.po index afc86479c..4c43e4d98 100644 --- a/rental_base_extension/i18n/ca.po +++ b/rental_base_extension/i18n/ca.po @@ -1,6 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * rental_base +# * rental_base_extension # msgid "" msgstr "" @@ -384,6 +385,7 @@ msgstr "" #. module: rental_base #: model:ir.model.fields,field_description:rental_base.field_update_sale_line_date__order_id +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental msgid "Order" msgstr "Comanda" @@ -414,6 +416,8 @@ msgstr "Temps de lloguer demanat" #. module: rental_base #: model:ir.ui.menu,name:rental_base.menu_customer_orders +#: model:ir.ui.menu,name:rental_base_extension.sale_order_menu_rental +#: model:ir.ui.menu,name:rental_base_extension.sale_order_menu_rental_orders msgid "Orders" msgstr "Comandes" @@ -490,6 +494,7 @@ msgstr "Comanda de lloguer" #. module: rental_base #: model:ir.actions.act_window,name:rental_base.action_rental_orders #: model:ir.ui.menu,name:rental_base.menu_rental_orders +#: model:ir.actions.act_window,name:rental_base_extension.sale_order_action_rental msgid "Rental Orders" msgstr "Comandes de lloguer" @@ -597,6 +602,7 @@ msgstr "Actualitzar data de les línies de comanda de venda" #: model:ir.actions.act_window,name:rental_base.action_update_sale_line_date #: model_terms:ir.ui.view,arch_db:rental_base.update_sale_line_date_form #: model_terms:ir.ui.view,arch_db:rental_base.view_order_form +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental msgid "Update Times" msgstr "Actualitzar temps" @@ -651,3 +657,251 @@ msgstr "" #: model_terms:ir.ui.view,arch_db:rental_base_extension.product_template_only_form_view msgid "Create Rental Service" msgstr "Crear servei de lloguer" + +#. module: rental_base_extension +#. odoo-javascript +#: code:addons/rental_base_extension/static/src/js/signature_widget.js:0 +#, python-format +msgid "Delivery Signature" +msgstr "Signatura de lliurament" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_res_company__rental_signature_terms +#: model:ir.model.fields,field_description:rental_base_extension.field_res_config_settings__rental_signature_terms +msgid "Rental Signature Terms" +msgstr "Condicions de signatura de lloguer" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.report_delivery_document_rental_signature_terms +msgid "Conditions" +msgstr "Condicions" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.report_delivery_document_rental_signature_terms +#: model_terms:ir.ui.view,arch_db:rental_base_extension.res_config_settings_view_form +msgid "Signature" +msgstr "Signatura" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.res_config_settings_view_form +msgid "" +"Terms shown in the signature dialog when signing a rental delivery order. " +"Leave empty to use the default text." +msgstr "" +"Condicions mostrades al diàleg de signatura en signar un albarà de lloguer. " +"Deixeu buit per utilitzar el text per defecte." + +#. module: rental_base_extension +#: model:ir.model.fields.selection,name:rental_base_extension.selection__sale_order__rental_status__draft +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Quotation" +msgstr "Pressupost" + +#. module: rental_base_extension +#: model:ir.model.fields.selection,name:rental_base_extension.selection__sale_order__rental_status__sent +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Quotation Sent" +msgstr "Pressupost enviat" + +#. module: rental_base_extension +#: model:ir.model.fields.selection,name:rental_base_extension.selection__sale_order__rental_status__pickup +msgid "Reserved" +msgstr "Reservat" + +#. module: rental_base_extension +#: model:ir.model.fields.selection,name:rental_base_extension.selection__sale_order__rental_status__return +msgid "Pickedup" +msgstr "Recollit" + +#. module: rental_base_extension +#: model:ir.model.fields.selection,name:rental_base_extension.selection__sale_order__rental_status__returned +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Returned" +msgstr "Tornat" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Booked" +msgstr "Reservat" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Late Pickup" +msgstr "Recollida amb retard" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Picked-up" +msgstr "Recollit" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Late Return" +msgstr "Retorn amb retard" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "To Pickup" +msgstr "Per recollir" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "To Return" +msgstr "Per retornar" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__rental_duration +msgid "Duration" +msgstr "Durada" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__rental_status +msgid "Rental Status" +msgstr "Estat del lloguer" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Rental Period" +msgstr "Període de lloguer" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "Search Rental Orders" +msgstr "Cerca comandes de lloguer" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "My Orders" +msgstr "Les meves comandes" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +#: model:ir.ui.menu,name:rental_base_extension.sale_order_menu_rental_today +msgid "To Do Today" +msgstr "Avui" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_kanban_rental +msgid "Late" +msgstr "Retard" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "Pickup Date" +msgstr "Data de recollida" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "Return Date" +msgstr "Data de devolució" + +#. module: rental_base_extension +#: model:ir.actions.act_window,name:rental_base_extension.sale_order_action_rental_today_pickup +msgid "Pickups Today" +msgstr "Recollides avui" + +#. module: rental_base_extension +#: model:ir.actions.act_window,name:rental_base_extension.sale_order_action_rental_today_return +msgid "Returns Today" +msgstr "Devolucions avui" + +#. module: rental_base_extension +#: model:ir.ui.menu,name:rental_base_extension.sale_order_menu_rental_today_pickup +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Pickup" +msgstr "Recollida" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Qty" +msgstr "Qtat" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_action_rental +msgid "Create a new rental order!" +msgstr "Crea una nova comanda de lloguer!" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_kanban_rental +msgid "Pickup:" +msgstr "Recollida:" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_kanban_rental +msgid "Return:" +msgstr "Retorn:" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_kanban_rental +msgid "" +msgstr "" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__is_late +msgid "Is Late" +msgstr "Endarrerit" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__is_rental_order +msgid "Is Rental Order" +msgstr "És comanda de lloguer" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_stock_picking__is_rental_picking +msgid "Is Rental Picking" +msgstr "És albarà de lloguer" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__next_action_date +msgid "Next Action Date" +msgstr "Data propera acció" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__pickup_picking_id +msgid "Pickup Picking" +msgstr "Albarà de recollida" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__return_picking_id +msgid "Return Picking" +msgstr "Albarà de retorn" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_res_company__rental_require_signature_validation +#: model:ir.model.fields,field_description:rental_base_extension.field_res_config_settings__rental_require_signature_validation +#: model:ir.model.fields,field_description:rental_base_extension.field_stock_picking__rental_require_signature_validation +msgid "Rental Require Signature Validation" +msgstr "Requerir signatura per validar lloguer" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.res_config_settings_view_form +msgid "Require Signature to Validate Delivery" +msgstr "Requerir signatura per validar l'entrega" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.res_config_settings_view_form +msgid "When enabled, rental delivery orders can only be validated by signing. The Validate button is replaced by the Sign button." +msgstr "Si s'activa, les entregues de lloguer només es poden validar amb signatura. El botó Validar se substitueix pel botó Signar." + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.view_picking_form +msgid "Sign" +msgstr "Signar" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "Salesperson" +msgstr "Comercial" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "Status" +msgstr "Estat" + +#. module: rental_base_extension +#. odoo-javascript +#: code:addons/rental_base_extension/static/src/xml/signature_dialog.xml:0 +#, python-format +msgid "Confirm & Sign" +msgstr "Confirmar i signar" diff --git a/rental_base_extension/i18n/es.po b/rental_base_extension/i18n/es.po index 510a3b1f8..b90ac5f2c 100644 --- a/rental_base_extension/i18n/es.po +++ b/rental_base_extension/i18n/es.po @@ -1,6 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * rental_base +# * rental_base_extension # msgid "" msgstr "" @@ -394,6 +395,7 @@ msgstr "" #. module: rental_base #: model:ir.model.fields,field_description:rental_base.field_update_sale_line_date__order_id +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental msgid "Order" msgstr "Pedido" @@ -424,6 +426,8 @@ msgstr "Tiempo de alquiler pedido" #. module: rental_base #: model:ir.ui.menu,name:rental_base.menu_customer_orders +#: model:ir.ui.menu,name:rental_base_extension.sale_order_menu_rental +#: model:ir.ui.menu,name:rental_base_extension.sale_order_menu_rental_orders msgid "Orders" msgstr "Pedidos" @@ -500,6 +504,7 @@ msgstr "Pedido de alquiler" #. module: rental_base #: model:ir.actions.act_window,name:rental_base.action_rental_orders #: model:ir.ui.menu,name:rental_base.menu_rental_orders +#: model:ir.actions.act_window,name:rental_base_extension.sale_order_action_rental msgid "Rental Orders" msgstr "Pedidos de alquiler" @@ -607,6 +612,7 @@ msgstr "Actualizar fecha de las líneas de pedido de venta" #: model:ir.actions.act_window,name:rental_base.action_update_sale_line_date #: model_terms:ir.ui.view,arch_db:rental_base.update_sale_line_date_form #: model_terms:ir.ui.view,arch_db:rental_base.view_order_form +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental msgid "Update Times" msgstr "Actualizar tiempos" @@ -663,3 +669,251 @@ msgstr "" #: model_terms:ir.ui.view,arch_db:rental_base_extension.product_template_only_form_view msgid "Create Rental Service" msgstr "Crear servicio de alquiler" + +#. module: rental_base_extension +#. odoo-javascript +#: code:addons/rental_base_extension/static/src/js/signature_widget.js:0 +#, python-format +msgid "Delivery Signature" +msgstr "Firma de entrega" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_res_company__rental_signature_terms +#: model:ir.model.fields,field_description:rental_base_extension.field_res_config_settings__rental_signature_terms +msgid "Rental Signature Terms" +msgstr "Condiciones de firma de alquiler" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.report_delivery_document_rental_signature_terms +msgid "Conditions" +msgstr "Condiciones" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.report_delivery_document_rental_signature_terms +#: model_terms:ir.ui.view,arch_db:rental_base_extension.res_config_settings_view_form +msgid "Signature" +msgstr "Firma" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.res_config_settings_view_form +msgid "" +"Terms shown in the signature dialog when signing a rental delivery order. " +"Leave empty to use the default text." +msgstr "" +"Condiciones mostradas en el diálogo de firma al firmar un albarán de alquiler. " +"Dejar vacío para utilizar el texto por defecto." + +#. module: rental_base_extension +#: model:ir.model.fields.selection,name:rental_base_extension.selection__sale_order__rental_status__draft +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Quotation" +msgstr "Presupuesto" + +#. module: rental_base_extension +#: model:ir.model.fields.selection,name:rental_base_extension.selection__sale_order__rental_status__sent +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Quotation Sent" +msgstr "Presupuesto enviado" + +#. module: rental_base_extension +#: model:ir.model.fields.selection,name:rental_base_extension.selection__sale_order__rental_status__pickup +msgid "Reserved" +msgstr "Reservado" + +#. module: rental_base_extension +#: model:ir.model.fields.selection,name:rental_base_extension.selection__sale_order__rental_status__return +msgid "Pickedup" +msgstr "Recogida" + +#. module: rental_base_extension +#: model:ir.model.fields.selection,name:rental_base_extension.selection__sale_order__rental_status__returned +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Returned" +msgstr "Devuelto" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Booked" +msgstr "Reservado" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Late Pickup" +msgstr "Recogida con retraso" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Picked-up" +msgstr "Recogida" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Late Return" +msgstr "Retorno con retraso" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "To Pickup" +msgstr "Por recoger" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "To Return" +msgstr "Por devolver" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__rental_duration +msgid "Duration" +msgstr "Duración" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__rental_status +msgid "Rental Status" +msgstr "Estado de alquiler" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Rental Period" +msgstr "Periodo de alquiler" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "Search Rental Orders" +msgstr "Buscar órdenes de alquiler" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "My Orders" +msgstr "Mis pedidos" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +#: model:ir.ui.menu,name:rental_base_extension.sale_order_menu_rental_today +msgid "To Do Today" +msgstr "Hoy" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_kanban_rental +msgid "Late" +msgstr "Retraso" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "Pickup Date" +msgstr "Fecha de recogida" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "Return Date" +msgstr "Fecha de retorno" + +#. module: rental_base_extension +#: model:ir.actions.act_window,name:rental_base_extension.sale_order_action_rental_today_pickup +msgid "Pickups Today" +msgstr "Recogidas hoy" + +#. module: rental_base_extension +#: model:ir.actions.act_window,name:rental_base_extension.sale_order_action_rental_today_return +msgid "Returns Today" +msgstr "Devoluciones hoy" + +#. module: rental_base_extension +#: model:ir.ui.menu,name:rental_base_extension.sale_order_menu_rental_today_pickup +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Pickup" +msgstr "Recogida" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_form_rental +msgid "Qty" +msgstr "Ctd" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_action_rental +msgid "Create a new rental order!" +msgstr "¡Crea una nueva orden de alquiler!" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_kanban_rental +msgid "Pickup:" +msgstr "Recogida:" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_kanban_rental +msgid "Return:" +msgstr "Retorno:" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_kanban_rental +msgid "" +msgstr "" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__is_late +msgid "Is Late" +msgstr "Atrasado" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__is_rental_order +msgid "Is Rental Order" +msgstr "Es pedido de alquiler" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_stock_picking__is_rental_picking +msgid "Is Rental Picking" +msgstr "Es albarán de alquiler" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__next_action_date +msgid "Next Action Date" +msgstr "Fecha próxima acción" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__pickup_picking_id +msgid "Pickup Picking" +msgstr "Albarán de recogida" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_sale_order__return_picking_id +msgid "Return Picking" +msgstr "Albarán de retorno" + +#. module: rental_base_extension +#: model:ir.model.fields,field_description:rental_base_extension.field_res_company__rental_require_signature_validation +#: model:ir.model.fields,field_description:rental_base_extension.field_res_config_settings__rental_require_signature_validation +#: model:ir.model.fields,field_description:rental_base_extension.field_stock_picking__rental_require_signature_validation +msgid "Rental Require Signature Validation" +msgstr "Requerir firma para validar alquiler" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.res_config_settings_view_form +msgid "Require Signature to Validate Delivery" +msgstr "Requerir firma para validar la entrega" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.res_config_settings_view_form +msgid "When enabled, rental delivery orders can only be validated by signing. The Validate button is replaced by the Sign button." +msgstr "Si se activa, las entregas de alquiler solo se pueden validar con firma. El botón Validar se sustituye por el botón Firmar." + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.view_picking_form +msgid "Sign" +msgstr "Firmar" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "Salesperson" +msgstr "Comercial" + +#. module: rental_base_extension +#: model_terms:ir.ui.view,arch_db:rental_base_extension.sale_order_view_search_rental +msgid "Status" +msgstr "Estado" + +#. module: rental_base_extension +#. odoo-javascript +#: code:addons/rental_base_extension/static/src/xml/signature_dialog.xml:0 +#, python-format +msgid "Confirm & Sign" +msgstr "Confirmar y firmar" diff --git a/rental_base_extension/models/__init__.py b/rental_base_extension/models/__init__.py new file mode 100644 index 000000000..ba1c090c8 --- /dev/null +++ b/rental_base_extension/models/__init__.py @@ -0,0 +1,4 @@ +from . import res_company +from . import res_config_settings +from . import sale_order +from . import stock_picking diff --git a/rental_base_extension/models/res_company.py b/rental_base_extension/models/res_company.py new file mode 100644 index 000000000..3318f68c6 --- /dev/null +++ b/rental_base_extension/models/res_company.py @@ -0,0 +1,14 @@ +# Copyright 2026 NuoBiT Solutions SL - Eric Antones +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + rental_require_signature_validation = fields.Boolean() + + rental_signature_terms = fields.Html( + translate=True, + ) diff --git a/rental_base_extension/models/res_config_settings.py b/rental_base_extension/models/res_config_settings.py new file mode 100644 index 000000000..22ba917a6 --- /dev/null +++ b/rental_base_extension/models/res_config_settings.py @@ -0,0 +1,18 @@ +# Copyright 2026 NuoBiT Solutions SL - Eric Antones +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + rental_require_signature_validation = fields.Boolean( + related="company_id.rental_require_signature_validation", + readonly=False, + ) + + rental_signature_terms = fields.Html( + related="company_id.rental_signature_terms", + readonly=False, + ) diff --git a/rental_base_extension/models/sale_order.py b/rental_base_extension/models/sale_order.py new file mode 100644 index 000000000..2fb3fd29c --- /dev/null +++ b/rental_base_extension/models/sale_order.py @@ -0,0 +1,134 @@ +# Copyright 2026 NuoBiT Solutions SL - Eric Antones +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import api, fields, models + +RENTAL_STATUS = [ + ("draft", "Quotation"), + ("sent", "Quotation Sent"), + ("pickup", "Reserved"), + ("return", "Pickedup"), + ("returned", "Returned"), + ("cancel", "Cancelled"), +] + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + rental_ids = fields.One2many( + comodel_name="sale.rental", + inverse_name="start_order_id", + ) + is_rental_order = fields.Boolean( + compute="_compute_is_rental_order", + store=True, + ) + rental_status = fields.Selection( + selection=RENTAL_STATUS, + compute="_compute_rental_status", + store=True, + ) + next_action_date = fields.Date( + compute="_compute_rental_status", + store=True, + ) + is_late = fields.Boolean( + compute="_compute_is_late", + ) + rental_duration = fields.Integer( + string="Duration", + compute="_compute_rental_duration", + ) + pickup_picking_id = fields.Many2one( + comodel_name="stock.picking", + compute="_compute_rental_picking_ids", + ) + return_picking_id = fields.Many2one( + comodel_name="stock.picking", + compute="_compute_rental_picking_ids", + ) + + def _get_rental_type(self): + return self.env.ref("rental_base.rental_sale_type", raise_if_not_found=False) + + @api.depends("type_id") + def _compute_is_rental_order(self): + rental_type = self._get_rental_type() + for order in self: + order.is_rental_order = bool(rental_type and order.type_id == rental_type) + + @api.depends( + "state", + "is_rental_order", + "rental_ids.state", + "default_start_date", + "default_end_date", + ) + def _compute_rental_status(self): + for order in self: + order.next_action_date = False + if not order.is_rental_order: + order.rental_status = False + elif order.state in ("draft", "sent", "cancel"): + order.rental_status = order.state + elif order.state in ("sale", "done"): + rental_states = order.rental_ids.mapped("state") + if not rental_states or any(s == "ordered" for s in rental_states): + order.rental_status = "pickup" + order.next_action_date = order.default_start_date + elif any(s in ("out", "sell_progress") for s in rental_states): + order.rental_status = "return" + order.next_action_date = order.default_end_date + else: + order.rental_status = "returned" + + @api.depends("default_start_date", "default_end_date") + def _compute_rental_duration(self): + for order in self: + if order.default_start_date and order.default_end_date: + order.rental_duration = ( + order.default_end_date - order.default_start_date + ).days + 1 + else: + order.rental_duration = 0 + + @api.depends( + "rental_ids.out_move_id.picking_id", "rental_ids.in_move_id.picking_id" + ) + def _compute_rental_picking_ids(self): + for order in self: + order.pickup_picking_id = order.rental_ids.mapped("out_move_id.picking_id")[ + :1 + ] + order.return_picking_id = order.rental_ids.mapped("in_move_id.picking_id")[ + :1 + ] + + def action_open_pickup_picking(self): + self.ensure_one() + return { + "type": "ir.actions.act_window", + "res_model": "stock.picking", + "res_id": self.pickup_picking_id.id, + "view_mode": "form", + "views": [(False, "form")], + } + + def action_open_return_picking(self): + self.ensure_one() + return { + "type": "ir.actions.act_window", + "res_model": "stock.picking", + "res_id": self.return_picking_id.id, + "view_mode": "form", + "views": [(False, "form")], + } + + @api.depends("next_action_date") + def _compute_is_late(self): + today = fields.Date.today() + for order in self: + order.is_late = bool( + order.next_action_date and order.next_action_date < today + ) diff --git a/rental_base_extension/models/stock_picking.py b/rental_base_extension/models/stock_picking.py new file mode 100644 index 000000000..a3f750091 --- /dev/null +++ b/rental_base_extension/models/stock_picking.py @@ -0,0 +1,41 @@ +# Copyright 2026 NuoBiT Solutions SL - Eric Antones +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import api, fields, models + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + is_rental_picking = fields.Boolean( + compute="_compute_is_rental_picking", + ) + + rental_require_signature_validation = fields.Boolean( + related="company_id.rental_require_signature_validation", + ) + + @api.depends("sale_id.type_id") + def _compute_is_rental_picking(self): + rental_type = self.env.ref( + "rental_base.rental_sale_type", raise_if_not_found=False + ) + for picking in self: + picking.is_rental_picking = bool( + rental_type + and picking.sale_id + and picking.sale_id.type_id == rental_type + ) + + def _attach_sign(self): + res = super()._attach_sign() + if ( + self.is_rental_picking + and self.company_id.rental_require_signature_validation + and self.state != "done" + ): + self.with_context( + skip_immediate=True, + skip_backorder=True, + ).button_validate() + return res diff --git a/rental_base_extension/readme/DESCRIPTION.rst b/rental_base_extension/readme/DESCRIPTION.rst index 965535953..49dfcc6ed 100644 --- a/rental_base_extension/readme/DESCRIPTION.rst +++ b/rental_base_extension/readme/DESCRIPTION.rst @@ -1,21 +1,41 @@ -This module extends ``rental_base`` to improve the rental order experience -with better menu visibility and product filtering. +This module extends ``rental_base`` to provide a complete rental order +management experience. + +Dedicated rental views +~~~~~~~~~~~~~~~~~~~~~~ + +Replaces the default sale order views with dedicated rental-specific views: +form, tree, kanban, calendar and search. The form view hides non-rental fields +and adds rental period, duration and status badges. Actions set the rental sale +type by default. + +Rental status tracking +~~~~~~~~~~~~~~~~~~~~~~ + +Adds computed ``rental_status`` on sale orders based on the state of the +underlying ``sale.rental`` records: draft, pickup, return, returned and cancel. +A "late" indicator warns when the next action date has passed. Rental Positions menu ~~~~~~~~~~~~~~~~~~~~~ -The ``rental_base`` module places the Rental Positions screen (the ``sale.rental`` -list) under Configuration and restricts it to debug mode. This module moves it -to the top level of the Rentals menu as the first entry, visible to all sales -users, since it is the primary screen for tracking rental status. +The ``rental_base`` module places the Rental Positions screen under +Configuration and restricts it to debug mode. This module moves it to the +top level of the Rentals menu as the first entry, visible to all sales users. Product filter on rental order lines ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When creating a rental order, the product dropdown on order lines only shows -rental service products (i.e. products linked to a rented physical product -via ``rented_product_id``). This prevents users from accidentally selecting -non-rental products on rental orders. +When creating a rental order, the product dropdown only shows rental service +products, preventing users from accidentally selecting non-rental products. + +Signature and delivery +~~~~~~~~~~~~~~~~~~~~~~ + +Configurable signature terms for rental delivery orders (Settings → Rental → +Signature). When enabled, the Validate button is replaced by a Sign button +that auto-validates on signature. The signed conditions appear on the delivery +slip PDF. Catalan and Spanish translations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/rental_base_extension/report/report_deliveryslip.xml b/rental_base_extension/report/report_deliveryslip.xml new file mode 100644 index 000000000..98f5c2494 --- /dev/null +++ b/rental_base_extension/report/report_deliveryslip.xml @@ -0,0 +1,41 @@ + + + + + diff --git a/rental_base_extension/static/description/index.html b/rental_base_extension/static/description/index.html index dad4f9149..fb99c2d80 100644 --- a/rental_base_extension/static/description/index.html +++ b/rental_base_extension/static/description/index.html @@ -375,21 +375,38 @@

Rental Base Extension

!! source digest: sha256:09ab96bf14c0b83294c077459048e953907a79975688866d7ca219537c451e41 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Alpha License: AGPL-3 nuobit/odoo-addons

-

This module extends rental_base to improve the rental order experience -with better menu visibility and product filtering.

+

This module extends rental_base to provide a complete rental order +management experience.

+
+

Dedicated rental views

+

Replaces the default sale order views with dedicated rental-specific views: +form, tree, kanban, calendar and search. The form view hides non-rental fields +and adds rental period, duration and status badges. Actions set the rental sale +type by default.

+
+
+

Rental status tracking

+

Adds computed rental_status on sale orders based on the state of the +underlying sale.rental records: draft, pickup, return, returned and cancel. +A “late” indicator warns when the next action date has passed.

+

Rental Positions menu

-

The rental_base module places the Rental Positions screen (the sale.rental -list) under Configuration and restricts it to debug mode. This module moves it -to the top level of the Rentals menu as the first entry, visible to all sales -users, since it is the primary screen for tracking rental status.

+

The rental_base module places the Rental Positions screen under +Configuration and restricts it to debug mode. This module moves it to the +top level of the Rentals menu as the first entry, visible to all sales users.

Product filter on rental order lines

-

When creating a rental order, the product dropdown on order lines only shows -rental service products (i.e. products linked to a rented physical product -via rented_product_id). This prevents users from accidentally selecting -non-rental products on rental orders.

+

When creating a rental order, the product dropdown only shows rental service +products, preventing users from accidentally selecting non-rental products.

+
+
+

Signature and delivery

+

Configurable signature terms for rental delivery orders (Settings → Rental → +Signature). When enabled, the Validate button is replaced by a Sign button +that auto-validates on signature. The signed conditions appear on the delivery +slip PDF.

Catalan and Spanish translations

diff --git a/rental_base_extension/static/src/js/.eslintrc.yml b/rental_base_extension/static/src/js/.eslintrc.yml new file mode 100644 index 000000000..add5a7856 --- /dev/null +++ b/rental_base_extension/static/src/js/.eslintrc.yml @@ -0,0 +1,2 @@ +parserOptions: + sourceType: module diff --git a/rental_base_extension/static/src/js/signature_widget.js b/rental_base_extension/static/src/js/signature_widget.js new file mode 100644 index 000000000..2b321fbe9 --- /dev/null +++ b/rental_base_extension/static/src/js/signature_widget.js @@ -0,0 +1,66 @@ +/** @odoo-module */ +/* Copyright 2026 NuoBiT Solutions SL - Eric Antones + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + +import {SignatureDialog} from "@web/core/signature/signature_dialog"; +import {SignatureWidget} from "@web/views/widgets/signature/signature"; +import {patch} from "web.utils"; + +patch(SignatureWidget.prototype, "rental_base_extension.SignatureWidget", { + async onClickSignature() { + const {record} = this.props; + const isRental = + record.resModel === "stock.picking" && record.data.is_rental_picking; + if (!isRental) { + return this._super(...arguments); + } + const companyId = record.data.company_id && record.data.company_id[0]; + let rentalTerms = ""; + if (companyId) { + const result = await this.orm.read( + "res.company", + [companyId], + ["rental_signature_terms"] + ); + if (result.length && result[0].rental_signature_terms) { + rentalTerms = owl.markup(result[0].rental_signature_terms); + } + } + const nameAndSignatureProps = { + mode: "draw", + displaySignatureRatio: 3, + signatureType: "signature", + noInputName: true, + }; + const {fullName} = this.props; + let defaultName = ""; + if (fullName) { + let signName = null; + const fullNameData = record.data[fullName]; + if (record.fields[fullName].type === "many2one") { + signName = fullNameData && fullNameData[1]; + } else { + signName = fullNameData; + } + defaultName = signName === "" ? undefined : signName; + } + nameAndSignatureProps.defaultFont = this.props.defaultFont; + const dialogProps = { + defaultName, + nameAndSignatureProps, + uploadSignature: (data) => this.uploadSignature(data), + isRental: true, + rentalTerms, + }; + this.dialogService.add(SignatureDialog, dialogProps); + }, +}); + +patch(SignatureDialog.prototype, "rental_base_extension.SignatureDialog", { + setup() { + this._super(...arguments); + if (this.props.isRental) { + this.title = this.env._t("Delivery Signature"); + } + }, +}); diff --git a/rental_base_extension/static/src/scss/rental_kanban.scss b/rental_base_extension/static/src/scss/rental_kanban.scss new file mode 100644 index 000000000..3475c2e29 --- /dev/null +++ b/rental_base_extension/static/src/scss/rental_kanban.scss @@ -0,0 +1,9 @@ +// Copyright 2026 NuoBiT Solutions SL - Eric Antones +// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +// Prevent the image zoom popup from capturing mouse events. +// Without this, hovering over a small thumbnail causes flickering: +// mouse enters popup → leaves thumbnail → popup closes → loop. +.o_popover:has(.o_image_zoom) { + pointer-events: none; +} diff --git a/rental_base_extension/static/src/xml/signature_dialog.xml b/rental_base_extension/static/src/xml/signature_dialog.xml new file mode 100644 index 000000000..dcc81e89c --- /dev/null +++ b/rental_base_extension/static/src/xml/signature_dialog.xml @@ -0,0 +1,39 @@ + + + + + + +
+
By clicking Adopt & Sign, I agree that the chosen signature/initials will be a valid electronic representation of my hand-written signature/initials for all purposes when it is used on documents, including legally binding contracts.
+ + + + + + + + diff --git a/rental_base_extension/tests/__init__.py b/rental_base_extension/tests/__init__.py new file mode 100644 index 000000000..95d8a8bce --- /dev/null +++ b/rental_base_extension/tests/__init__.py @@ -0,0 +1 @@ +from . import test_rental_status diff --git a/rental_base_extension/tests/test_rental_status.py b/rental_base_extension/tests/test_rental_status.py new file mode 100644 index 000000000..18cfc709b --- /dev/null +++ b/rental_base_extension/tests/test_rental_status.py @@ -0,0 +1,156 @@ +# Copyright 2026 NuoBiT Solutions SL - Eric Antones +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from datetime import timedelta + +from odoo import fields +from odoo.tests import common, tagged + + +@tagged("post_install", "-at_install") +class TestRentalStatus(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.category_all = cls.env.ref("product.product_category_all") + cls.rental_sale_type = cls.env.ref("rental_base.rental_sale_type") + cls.uom_day = cls.env.ref("uom.product_uom_day") + cls.warehouse0 = cls.env.ref("stock.warehouse0") + cls.partner = cls.env["res.partner"].create( + { + "name": "Test Partner", + "email": "test@test.com", + } + ) + cls.product_hw = cls.env["product.product"].create( + { + "name": "Rental Hardware", + "type": "product", + "categ_id": cls.category_all.id, + } + ) + cls.service_rental = ( + cls.env["create.rental.product"] + .with_context(active_model="product.product", active_id=cls.product_hw.id) + .create( + { + "hw_product_id": cls.product_hw.id, + "name": "Rental of Rental Hardware (Day)", + "categ_id": cls.product_hw.categ_id.id, + "copy_image": True, + } + ) + .create_rental_product() + ) + cls.service_rental = cls.env["product.product"].browse( + cls.service_rental["res_id"] + ) + cls.service_rental.write( + { + "uom_id": cls.uom_day.id, + "uom_po_id": cls.uom_day.id, + "list_price": 100, + } + ) + cls.service_rental.rental = True + + def _create_rental_order(self, **kwargs): + today = fields.Date.today() + date_start = kwargs.get("date_start", today + timedelta(days=1)) + date_end = kwargs.get("date_end", today + timedelta(days=5)) + date_qty = (date_end - date_start).days + 1 + return self.env["sale.order"].create( + { + "type_id": self.rental_sale_type.id, + "partner_id": self.partner.id, + "partner_invoice_id": self.partner.id, + "partner_shipping_id": self.partner.id, + "pricelist_id": self.env.ref("product.list0").id, + "picking_policy": "direct", + "warehouse_id": self.warehouse0.id, + "order_line": [ + ( + 0, + 0, + { + "name": "Rental Service", + "product_id": self.service_rental.id, + "rental": True, + "rental_type": "new_rental", + "rental_qty": 1, + "product_uom_qty": date_qty, + "start_date": date_start, + "end_date": date_end, + "price_unit": 100, + "product_uom": self.uom_day.id, + }, + ) + ], + } + ) + + def _create_and_confirm_rental_order(self): + order = self._create_rental_order() + order.action_confirm() + return order + + def test_confirmed_rental_status_pickup(self): + """Confirmed rental with sale.rental records should have 'pickup' status.""" + order = self._create_and_confirm_rental_order() + self.assertTrue(order.rental_ids) + self.assertEqual(order.rental_status, "pickup") + + def test_confirmed_rental_status_pickup_product_without_rental_flag(self): + """Confirmed rental with product missing rental=True on template + should still be recognized as a rental order.""" + self.service_rental.rental = False + order = self._create_and_confirm_rental_order() + self.assertTrue(order.is_rental_order) + self.assertTrue(order.rental_ids) + self.assertEqual(order.rental_status, "pickup") + + def test_confirmed_rental_status_pickup_without_rental_records(self): + """Confirmed rental order without sale.rental records should be 'pickup', + not 'returned'. This happens when a product is not a proper rental + service (missing rented_product_id) but the order is created from + the rental menu.""" + order = self._create_and_confirm_rental_order() + order.rental_ids.unlink() + order._compute_rental_status() + self.assertFalse(order.rental_ids) + self.assertEqual(order.rental_status, "pickup") + + def test_rental_status_draft(self): + """Draft rental order should have 'draft' status.""" + order = self._create_rental_order() + self.assertEqual(order.rental_status, "draft") + + def test_rental_status_cancel(self): + """Cancelled rental order should have 'cancel' status.""" + order = self._create_and_confirm_rental_order() + order._action_cancel() + self.assertEqual(order.rental_status, "cancel") + + def test_rental_status_return(self): + """Rental with completed outgoing picking should have 'return' status.""" + order = self._create_and_confirm_rental_order() + rental = order.rental_ids + out_picking = rental.out_move_id.picking_id + out_picking.action_assign() + out_picking.move_ids.quantity_done = out_picking.move_ids.product_uom_qty + out_picking.button_validate() + self.assertEqual(order.rental_status, "return") + + def test_rental_status_returned(self): + """Rental with both pickings completed should have 'returned' status.""" + order = self._create_and_confirm_rental_order() + rental = order.rental_ids + out_picking = rental.out_move_id.picking_id + out_picking.action_assign() + out_picking.move_ids.quantity_done = out_picking.move_ids.product_uom_qty + out_picking.button_validate() + in_picking = rental.in_move_id.picking_id + in_picking.action_assign() + in_picking.move_ids.quantity_done = in_picking.move_ids.product_uom_qty + in_picking.button_validate() + self.assertEqual(order.rental_status, "returned") diff --git a/rental_base_extension/views/res_config_settings_views.xml b/rental_base_extension/views/res_config_settings_views.xml new file mode 100644 index 000000000..bb24857e9 --- /dev/null +++ b/rental_base_extension/views/res_config_settings_views.xml @@ -0,0 +1,56 @@ + + + + + res.config.settings.view.form.inherit.rental_base_extension + res.config.settings + + +
+

Signature

+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rental_base_extension/views/sale_order_views.xml b/rental_base_extension/views/sale_order_views.xml new file mode 100644 index 000000000..6655fdc29 --- /dev/null +++ b/rental_base_extension/views/sale_order_views.xml @@ -0,0 +1,661 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + sale.order.view.form.rental + sale.order + + primary + 999 + + + + + + + + + + + + + 1 + + + + + - - - - - diff --git a/sale_rental_extension/tests/__init__.py b/sale_rental_extension/tests/__init__.py deleted file mode 100644 index 95d8a8bce..000000000 --- a/sale_rental_extension/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import test_rental_status diff --git a/sale_rental_extension/tests/test_rental_status.py b/sale_rental_extension/tests/test_rental_status.py deleted file mode 100644 index 18cfc709b..000000000 --- a/sale_rental_extension/tests/test_rental_status.py +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright 2026 NuoBiT Solutions SL - Eric Antones -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) - -from datetime import timedelta - -from odoo import fields -from odoo.tests import common, tagged - - -@tagged("post_install", "-at_install") -class TestRentalStatus(common.TransactionCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.category_all = cls.env.ref("product.product_category_all") - cls.rental_sale_type = cls.env.ref("rental_base.rental_sale_type") - cls.uom_day = cls.env.ref("uom.product_uom_day") - cls.warehouse0 = cls.env.ref("stock.warehouse0") - cls.partner = cls.env["res.partner"].create( - { - "name": "Test Partner", - "email": "test@test.com", - } - ) - cls.product_hw = cls.env["product.product"].create( - { - "name": "Rental Hardware", - "type": "product", - "categ_id": cls.category_all.id, - } - ) - cls.service_rental = ( - cls.env["create.rental.product"] - .with_context(active_model="product.product", active_id=cls.product_hw.id) - .create( - { - "hw_product_id": cls.product_hw.id, - "name": "Rental of Rental Hardware (Day)", - "categ_id": cls.product_hw.categ_id.id, - "copy_image": True, - } - ) - .create_rental_product() - ) - cls.service_rental = cls.env["product.product"].browse( - cls.service_rental["res_id"] - ) - cls.service_rental.write( - { - "uom_id": cls.uom_day.id, - "uom_po_id": cls.uom_day.id, - "list_price": 100, - } - ) - cls.service_rental.rental = True - - def _create_rental_order(self, **kwargs): - today = fields.Date.today() - date_start = kwargs.get("date_start", today + timedelta(days=1)) - date_end = kwargs.get("date_end", today + timedelta(days=5)) - date_qty = (date_end - date_start).days + 1 - return self.env["sale.order"].create( - { - "type_id": self.rental_sale_type.id, - "partner_id": self.partner.id, - "partner_invoice_id": self.partner.id, - "partner_shipping_id": self.partner.id, - "pricelist_id": self.env.ref("product.list0").id, - "picking_policy": "direct", - "warehouse_id": self.warehouse0.id, - "order_line": [ - ( - 0, - 0, - { - "name": "Rental Service", - "product_id": self.service_rental.id, - "rental": True, - "rental_type": "new_rental", - "rental_qty": 1, - "product_uom_qty": date_qty, - "start_date": date_start, - "end_date": date_end, - "price_unit": 100, - "product_uom": self.uom_day.id, - }, - ) - ], - } - ) - - def _create_and_confirm_rental_order(self): - order = self._create_rental_order() - order.action_confirm() - return order - - def test_confirmed_rental_status_pickup(self): - """Confirmed rental with sale.rental records should have 'pickup' status.""" - order = self._create_and_confirm_rental_order() - self.assertTrue(order.rental_ids) - self.assertEqual(order.rental_status, "pickup") - - def test_confirmed_rental_status_pickup_product_without_rental_flag(self): - """Confirmed rental with product missing rental=True on template - should still be recognized as a rental order.""" - self.service_rental.rental = False - order = self._create_and_confirm_rental_order() - self.assertTrue(order.is_rental_order) - self.assertTrue(order.rental_ids) - self.assertEqual(order.rental_status, "pickup") - - def test_confirmed_rental_status_pickup_without_rental_records(self): - """Confirmed rental order without sale.rental records should be 'pickup', - not 'returned'. This happens when a product is not a proper rental - service (missing rented_product_id) but the order is created from - the rental menu.""" - order = self._create_and_confirm_rental_order() - order.rental_ids.unlink() - order._compute_rental_status() - self.assertFalse(order.rental_ids) - self.assertEqual(order.rental_status, "pickup") - - def test_rental_status_draft(self): - """Draft rental order should have 'draft' status.""" - order = self._create_rental_order() - self.assertEqual(order.rental_status, "draft") - - def test_rental_status_cancel(self): - """Cancelled rental order should have 'cancel' status.""" - order = self._create_and_confirm_rental_order() - order._action_cancel() - self.assertEqual(order.rental_status, "cancel") - - def test_rental_status_return(self): - """Rental with completed outgoing picking should have 'return' status.""" - order = self._create_and_confirm_rental_order() - rental = order.rental_ids - out_picking = rental.out_move_id.picking_id - out_picking.action_assign() - out_picking.move_ids.quantity_done = out_picking.move_ids.product_uom_qty - out_picking.button_validate() - self.assertEqual(order.rental_status, "return") - - def test_rental_status_returned(self): - """Rental with both pickings completed should have 'returned' status.""" - order = self._create_and_confirm_rental_order() - rental = order.rental_ids - out_picking = rental.out_move_id.picking_id - out_picking.action_assign() - out_picking.move_ids.quantity_done = out_picking.move_ids.product_uom_qty - out_picking.button_validate() - in_picking = rental.in_move_id.picking_id - in_picking.action_assign() - in_picking.move_ids.quantity_done = in_picking.move_ids.product_uom_qty - in_picking.button_validate() - self.assertEqual(order.rental_status, "returned") diff --git a/sale_rental_extension/views/res_config_settings_views.xml b/sale_rental_extension/views/res_config_settings_views.xml deleted file mode 100644 index c06e34da6..000000000 --- a/sale_rental_extension/views/res_config_settings_views.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - res.config.settings.view.form.inherit.sale_rental_extension - res.config.settings - - -
-

Signature

-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/sale_rental_extension/views/sale_order_rental_views.xml b/sale_rental_extension/views/sale_order_rental_views.xml deleted file mode 100644 index d1940d1cb..000000000 --- a/sale_rental_extension/views/sale_order_rental_views.xml +++ /dev/null @@ -1,655 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - sale.order.form.rental - sale.order - - primary - 999 - - - - - - - - - - - - - 1 - - - - -