diff --git a/setup/stock_reserve_sale/odoo/addons/stock_reserve_sale b/setup/stock_reserve_sale/odoo/addons/stock_reserve_sale new file mode 120000 index 000000000..c76c7de39 --- /dev/null +++ b/setup/stock_reserve_sale/odoo/addons/stock_reserve_sale @@ -0,0 +1 @@ +../../../../stock_reserve_sale \ No newline at end of file diff --git a/setup/stock_reserve_sale/setup.py b/setup/stock_reserve_sale/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/stock_reserve_sale/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_reserve_sale/README.rst b/stock_reserve_sale/README.rst index abcdfe86d..b7ba372fb 100644 --- a/stock_reserve_sale/README.rst +++ b/stock_reserve_sale/README.rst @@ -1,8 +1,29 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :alt: License: AGPL-3 +=================== +Stock Reserve Sales +=================== -Stock Reserve Sale -================== +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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%2Fstock--logistics--warehouse-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-warehouse/tree/13.0/stock_reserve_sale + :alt: OCA/stock-logistics-warehouse +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-13-0/stock-logistics-warehouse-13-0-stock_reserve_sale + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/153/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| Allows to create stock reservations for quotation lines before the confirmation of the quotation. The reservations might have a validity @@ -28,39 +49,66 @@ will get a message suggesting to reserve each line individually. There is no module dependency: this modules is fully functional even without ownership management. +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +#. Create a new Sale Order +#. Add lines to the Order + +Now you can reserve all the lines by clicking on *Reserve Stock* or you can reserve each +one by pressing on the lock icon at the lines. + +Once they where reserved, you can release the reserves by clicking at *Cancell all* +button or by clicking the undo icon on the lines. 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 -`here `_. +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 +~~~~~~~ + +* Camptocamp + Contributors ------------- +~~~~~~~~~~~~ * Leonardo Pistone * Alexandre Fayolle * Yannick Vaucher * Guewen Baconnier -Maintainer ----------- +* `Tecnativa `_: + + * Carlos Roca + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. .. image:: https://odoo-community.org/logo.png :alt: Odoo Community Association :target: https://odoo-community.org -This module is maintained by the OCA. - 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. -To contribute to this module, please visit http://odoo-community.org. +This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_reserve_sale/__init__.py b/stock_reserve_sale/__init__.py index 4341cc1b5..8bf1bb619 100644 --- a/stock_reserve_sale/__init__.py +++ b/stock_reserve_sale/__init__.py @@ -1,22 +1,4 @@ -############################################################################## -# -# Author: Guewen Baconnier -# Copyright 2013 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - +# Copyright 2013 Camptocamp SA - Guewen Baconnier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from . import model from . import wizard diff --git a/stock_reserve_sale/__manifest__.py b/stock_reserve_sale/__manifest__.py index 3eab77e61..fa4cccfcf 100644 --- a/stock_reserve_sale/__manifest__.py +++ b/stock_reserve_sale/__manifest__.py @@ -1,40 +1,19 @@ -############################################################################## -# -# Author: Guewen Baconnier, Leonardo Pistone -# Copyright 2013-2015 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - +# Copyright 2013 Camptocamp SA - Guewen Baconnier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "Stock Reserve Sales", - "version": "8.0.1.0.0", - "author": "Camptocamp,Odoo Community Association (OCA)", + "version": "13.0.1.0.0", + "author": "Camptocamp, Odoo Community Association (OCA)", "category": "Warehouse", "license": "AGPL-3", "complexity": "normal", - "images": [], - "website": "http://www.camptocamp.com", - "depends": ["sale_stock", "stock_reserve",], - "demo": [], + "website": "https://github.com/stock-logistics-warehouse", + "depends": ["sale_stock", "stock_reserve"], "data": [ "wizard/sale_stock_reserve_view.xml", "view/sale.xml", "view/stock_reserve.xml", ], - "test": ["test/sale_reserve.yml", "test/sale_line_reserve.yml",], - "installable": False, + "installable": True, "auto_install": False, } diff --git a/stock_reserve_sale/i18n/es.po b/stock_reserve_sale/i18n/es.po index 30e87bbb5..3f8c5aec1 100644 --- a/stock_reserve_sale/i18n/es.po +++ b/stock_reserve_sale/i18n/es.po @@ -1,208 +1,237 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * stock_reserve_sale -# +# # Translators: msgid "" msgstr "" "Project-Id-Version: stock-logistics-warehouse (8.0)\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-09-23 20:23+0000\n" -"PO-Revision-Date: 2015-09-15 13:05+0000\n" -"Last-Translator: <>\n" -"Language-Team: Spanish (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-8-0/language/es/)\n" +"POT-Creation-Date: 2021-07-27 07:47+0000\n" +"PO-Revision-Date: 2021-07-27 09:58+0200\n" +"Last-Translator: Carlos \n" +"Language-Team: Spanish (http://www.transifex.com/oca/OCA-stock-logistics-" +"warehouse-8-0/language/es/)\n" +"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Language: es\n" +"Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 2.0.6\n" #. module: stock_reserve_sale -#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form msgid "" "A stock reservation will be created for the products\n" -" of the selected quotation lines. If a validity date is specified,\n" -" the reservation will be released once the date has passed." +" of the selected quotation lines. If a validity date is " +"specified,\n" +" the reservation will be released once the date has " +"passed." msgstr "" +"Una reserva de stock va a ser creada para los productos\n" +" de la orden de venta seleccionada. Si una fecha de " +"validez está\n" +"\t\t especificada, la reserva va a ser liberada cuando la fecha pase." #. module: stock_reserve_sale -#: code:addons/stock_reserve_sale/model/sale.py:179 -#, python-format -msgid "" -"As you changed the quantity of the line, the quantity of the stock " -"reservation will be automatically adjusted to %.2f." -msgstr "" - -#. module: stock_reserve_sale -#: field:sale.order,is_stock_reservable:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_order__is_stock_reservable msgid "Can Have Stock Reservations" -msgstr "" +msgstr "Puede tener reservas de stock" #. module: stock_reserve_sale -#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_order_line__is_stock_reservable +msgid "Can be reserved" +msgstr "Puede ser reservado" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form msgid "Cancel" -msgstr "" +msgstr "Cancelar" #. module: stock_reserve_sale -#: code:addons/stock_reserve_sale/model/sale.py:188 -#, python-format -msgid "Configuration Error!" -msgstr "" +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_order_form_reserve +msgid "Cancell all" +msgstr "Cancelar todo" #. module: stock_reserve_sale -#: field:sale.stock.reserve,create_uid:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__create_uid msgid "Created by" msgstr "Creado por" #. module: stock_reserve_sale -#: field:sale.stock.reserve,create_date:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__create_date msgid "Created on" msgstr "Creado el" #. module: stock_reserve_sale -#: code:addons/stock_reserve_sale/model/sale.py:210 -#: code:addons/stock_reserve_sale/model/sale.py:222 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: stock_reserve_sale +#: code:addons/stock_reserve_sale/model/sale.py:0 #, python-format msgid "Error" -msgstr "" +msgstr "Error" #. module: stock_reserve_sale -#: field:sale.order,has_stock_reservation:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_order__has_stock_reservation msgid "Has Stock Reservations" -msgstr "" +msgstr "Tiene reservas de stock" #. module: stock_reserve_sale -#: field:sale.stock.reserve,id:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__id msgid "ID" msgstr "ID" #. module: stock_reserve_sale -#: help:sale.stock.reserve,date_validity:0 +#: model:ir.model.fields,help:stock_reserve_sale.field_sale_stock_reserve__date_validity msgid "" "If a date is given, the reservations will be released at the end of the " "validity." msgstr "" +"Si la fecha de validez está asignada, la reserva va a ser liberada al final " +"de esta." #. module: stock_reserve_sale -#: field:sale.stock.reserve,write_uid:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__write_uid msgid "Last Updated by" msgstr "Actualizado por última vez por" #. module: stock_reserve_sale -#: field:sale.stock.reserve,write_date:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__write_date msgid "Last Updated on" msgstr "Actualizado por última vez el" #. module: stock_reserve_sale -#: help:sale.stock.reserve,location_dest_id:0 +#: model:ir.model.fields,help:stock_reserve_sale.field_sale_stock_reserve__location_dest_id msgid "Location where the system will reserve the products." -msgstr "" +msgstr "Localización donde el sistema va a reservar los productos." #. module: stock_reserve_sale -#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form -#: field:sale.stock.reserve,note:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__note +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form msgid "Notes" msgstr "Notas" #. module: stock_reserve_sale -#: view:sale.order:stock_reserve_sale.view_order_form_reserve +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_order_form_reserve msgid "Pre-book products from stock" -msgstr "" +msgstr "Pre-reservar productos en stock" #. module: stock_reserve_sale -#: view:sale.order:stock_reserve_sale.view_order_form_reserve +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_order_form_reserve msgid "Release Reservation" -msgstr "" +msgstr "Liberar" #. module: stock_reserve_sale -#: field:sale.stock.reserve,location_dest_id:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__location_dest_id msgid "Reservation Location" -msgstr "" +msgstr "Localización de la reserva" #. module: stock_reserve_sale -#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form msgid "Reserve" msgstr "Reservar" #. module: stock_reserve_sale -#: view:sale.order:stock_reserve_sale.view_order_form_reserve -#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_order_form_reserve +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form msgid "Reserve Stock" -msgstr "" +msgstr "Reservar stock" #. module: stock_reserve_sale #: model:ir.actions.act_window,name:stock_reserve_sale.action_sale_stock_reserve msgid "Reserve Stock for Quotation Lines" -msgstr "" +msgstr "Reservar stock para las lineas del pedido" #. module: stock_reserve_sale -#: field:stock.reservation,sale_line_id:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_stock_reservation__sale_id +msgid "Sale Order" +msgstr "Pedido de venta" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_stock_reservation__sale_line_id msgid "Sale Order Line" -msgstr "" +msgstr "Linea de venta" #. module: stock_reserve_sale -#: view:stock.reservation:stock_reserve_sale.view_stock_reservation_form +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_stock_reservation_form msgid "Sales" -msgstr "" +msgstr "Ventas" #. module: stock_reserve_sale #: model:ir.model,name:stock_reserve_sale.model_sale_order msgid "Sales Order" -msgstr "" +msgstr "Pedido" #. module: stock_reserve_sale #: model:ir.model,name:stock_reserve_sale.model_sale_order_line msgid "Sales Order Line" -msgstr "" +msgstr "Líneas de pedidos de venta" #. module: stock_reserve_sale -#: code:addons/stock_reserve_sale/model/sale.py:223 +#: code:addons/stock_reserve_sale/model/sale.py:0 #, python-format msgid "" "Several stock reservations are linked with the line. Impossible to adjust " "their quantity. Please release the reservation before changing the quantity." msgstr "" +"Algunas reservas de stock están asociadas a la linea. Imposible ajustar la " +"cantidad. Por favor, libera la reserva antes de cambiar la cantidad." #. module: stock_reserve_sale -#: field:sale.stock.reserve,location_id:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__location_id msgid "Source Location" -msgstr "" +msgstr "Ubicación origen" #. module: stock_reserve_sale -#: field:sale.stock.reserve,owner_id:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__owner_id msgid "Stock Owner" -msgstr "" +msgstr "Propietario" #. module: stock_reserve_sale #: model:ir.model,name:stock_reserve_sale.model_stock_reservation -#: field:sale.order.line,reservation_ids:0 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_order_line__reservation_ids msgid "Stock Reservation" msgstr "Reserva de existencias" #. module: stock_reserve_sale -#: field:sale.stock.reserve,date_validity:0 +#: code:addons/stock_reserve_sale/wizard/sale_stock_reserve.py:0 +#, python-format +msgid "" +"The lines have different owners. Please reserve them\n" +" individually with the reserve button on each one." +msgstr "" +"Las lineas tienen distintos propietarios. Por favor, reservalas\n" +" individualmente con el botón de reserva de cada una." + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__date_validity msgid "Validity Date" msgstr "Fecha de validez" #. module: stock_reserve_sale -#: code:addons/stock_reserve_sale/model/sale.py:211 +#: code:addons/stock_reserve_sale/model/sale.py:0 #, python-format msgid "" "You cannot change the product or unit of measure of lines with a stock " "reservation. Release the reservation before changing the product." msgstr "" +"No puedes cambiar el producto o la unidad de medida de las lineas con una " +"reserva de stock. Libera la reserva antes de cambiar el producto." #. module: stock_reserve_sale -#: view:sale.order:stock_reserve_sale.view_order_form_reserve -msgid "cancel all" -msgstr "" - -#. module: stock_reserve_sale -#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form msgid "or" -msgstr "" +msgstr "o" #. module: stock_reserve_sale -#: view:sale.order:stock_reserve_sale.view_order_form_reserve -msgid "{\"reload_on_button\": 1}" +#: model:ir.model,name:stock_reserve_sale.model_sale_stock_reserve +msgid "sale.stock.reserve" msgstr "" diff --git a/stock_reserve_sale/i18n/stock_reserve_sale.pot b/stock_reserve_sale/i18n/stock_reserve_sale.pot new file mode 100644 index 000000000..c6dcbb7b8 --- /dev/null +++ b/stock_reserve_sale/i18n/stock_reserve_sale.pot @@ -0,0 +1,220 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_reserve_sale +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-07-27 07:47+0000\n" +"PO-Revision-Date: 2021-07-27 07:47+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form +msgid "" +"A stock reservation will be created for the products\n" +" of the selected quotation lines. If a validity date is specified,\n" +" the reservation will be released once the date has passed." +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_order__is_stock_reservable +msgid "Can Have Stock Reservations" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_order_line__is_stock_reservable +msgid "Can be reserved" +msgstr "" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form +msgid "Cancel" +msgstr "" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_order_form_reserve +msgid "Cancell all" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__create_uid +msgid "Created by" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__create_date +msgid "Created on" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__display_name +msgid "Display Name" +msgstr "" + +#. module: stock_reserve_sale +#: code:addons/stock_reserve_sale/model/sale.py:0 +#: code:addons/stock_reserve_sale/model/sale.py:0 +#, python-format +msgid "Error" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_order__has_stock_reservation +msgid "Has Stock Reservations" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__id +msgid "ID" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,help:stock_reserve_sale.field_sale_stock_reserve__date_validity +msgid "" +"If a date is given, the reservations will be released at the end of the " +"validity." +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve____last_update +msgid "Last Modified on" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__write_date +msgid "Last Updated on" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,help:stock_reserve_sale.field_sale_stock_reserve__location_dest_id +msgid "Location where the system will reserve the products." +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__note +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form +msgid "Notes" +msgstr "" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_order_form_reserve +msgid "Pre-book products from stock" +msgstr "" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_order_form_reserve +msgid "Release Reservation" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__location_dest_id +msgid "Reservation Location" +msgstr "" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form +msgid "Reserve" +msgstr "" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_order_form_reserve +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form +msgid "Reserve Stock" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.actions.act_window,name:stock_reserve_sale.action_sale_stock_reserve +msgid "Reserve Stock for Quotation Lines" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_stock_reservation__sale_id +msgid "Sale Order" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_stock_reservation__sale_line_id +msgid "Sale Order Line" +msgstr "" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_stock_reservation_form +msgid "Sales" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model,name:stock_reserve_sale.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model,name:stock_reserve_sale.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: stock_reserve_sale +#: code:addons/stock_reserve_sale/model/sale.py:0 +#, python-format +msgid "" +"Several stock reservations are linked with the line. Impossible to adjust " +"their quantity. Please release the reservation before changing the quantity." +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__location_id +msgid "Source Location" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__owner_id +msgid "Stock Owner" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model,name:stock_reserve_sale.model_stock_reservation +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_order_line__reservation_ids +msgid "Stock Reservation" +msgstr "" + +#. module: stock_reserve_sale +#: code:addons/stock_reserve_sale/wizard/sale_stock_reserve.py:0 +#, python-format +msgid "" +"The lines have different owners. Please reserve them\n" +" individually with the reserve button on each one." +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__date_validity +msgid "Validity Date" +msgstr "" + +#. module: stock_reserve_sale +#: code:addons/stock_reserve_sale/model/sale.py:0 +#, python-format +msgid "" +"You cannot change the product or unit of measure of lines with a stock " +"reservation. Release the reservation before changing the product." +msgstr "" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form +msgid "or" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model,name:stock_reserve_sale.model_sale_stock_reserve +msgid "sale.stock.reserve" +msgstr "" diff --git a/stock_reserve_sale/model/__init__.py b/stock_reserve_sale/model/__init__.py index e36320781..7090ccfa3 100644 --- a/stock_reserve_sale/model/__init__.py +++ b/stock_reserve_sale/model/__init__.py @@ -1,22 +1,4 @@ -############################################################################## -# -# Author: Guewen Baconnier -# Copyright 2013 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - +# Copyright 2013 Camptocamp SA - Guewen Baconnier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from . import sale from . import stock_reserve diff --git a/stock_reserve_sale/model/sale.py b/stock_reserve_sale/model/sale.py index 809bb9620..f7b07fe2e 100644 --- a/stock_reserve_sale/model/sale.py +++ b/stock_reserve_sale/model/sale.py @@ -1,36 +1,19 @@ -############################################################################## -# -# Author: Guewen Baconnier -# Copyright 2013 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## +# Copyright 2013 Camptocamp SA - Guewen Baconnier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, fields, models +from odoo.exceptions import UserError, except_orm +from odoo.tools.translate import _ -from openerp import api, fields, models -from openerp.exceptions import except_orm -from openerp.tools.translate import _ +_LINE_KEYS = ["product_id", "product_uom_qty"] class SaleOrder(models.Model): _inherit = "sale.order" - @api.multi @api.depends( "state", "order_line.reservation_ids", "order_line.is_stock_reservable" ) - def _stock_reservation(self): + def _compute_stock_reservation(self): for sale in self: has_stock_reservation = False is_stock_reservable = False @@ -45,53 +28,100 @@ class SaleOrder(models.Model): sale.has_stock_reservation = has_stock_reservation has_stock_reservation = fields.Boolean( - compute="_stock_reservation", + compute="_compute_stock_reservation", readonly=True, multi="stock_reservation", store=True, string="Has Stock Reservations", ) is_stock_reservable = fields.Boolean( - compute="_stock_reservation", + compute="_compute_stock_reservation", readonly=True, multi="stock_reservation", store=True, string="Can Have Stock Reservations", ) - @api.multi def release_all_stock_reservation(self): line_ids = [line.id for order in self for line in order.order_line] lines = self.env["sale.order.line"].browse(line_ids) lines.release_stock_reservation() return True - @api.multi - def action_button_confirm(self): + def action_confirm(self): self.release_all_stock_reservation() - return super(SaleOrder, self).action_button_confirm() + return super().action_confirm() - @api.multi def action_cancel(self): self.release_all_stock_reservation() - return super(SaleOrder, self).action_cancel() + return super().action_cancel() + + def write(self, vals): + old_lines = self.mapped("order_line") + dict_old_lines = {} + for line in old_lines: + dict_old_lines[line.id] = { + "product_id": line.product_id, + "product_uom_qty": line.product_uom_qty, + } + res = super().write(vals) + for order in self: + body = "" + for line in vals.get("order_line", []): + if line[0] == 1 and list(set(line[2].keys()).intersection(_LINE_KEYS)): + body += order.get_message(dict_old_lines.get(line[1]), line[2]) + if body != "": + order.message_post(body=body) + return res + + @api.model + def get_message(self, old_vals, new_vals): + ProductProduct = self.env["product.product"] + body = _("

Modified Order line data

") + if "product_id" in new_vals: + old_product = old_vals["product_id"].display_name + new_product = ProductProduct.browse(new_vals["product_id"]).display_name + body += _("
Product: ") + body += "{} → {}
".format(old_product, new_product) + if "product_uom_qty" in new_vals: + if "product_id" not in new_vals: + body += _("
Product: %s") % ( + old_vals["product_id"].display_name + ) + body += _("
Product qty.: ") + body += "{} → {}
".format( + old_vals["product_uom_qty"], float(new_vals["product_uom_qty"]), + ) + body += "
" + return body + + def unlink(self): + for order in self: + if order.has_stock_reservation: + raise UserError( + _( + "Sale Order %s has some reserved lines.\n" + "Please unreserve this lines before delete the order." + ) + % (order.name) + ) + return super().unlink() class SaleOrderLine(models.Model): _inherit = "sale.order.line" - @api.multi def _get_line_rule(self): """ Get applicable rule for this product Reproduce get suitable rule from procurement to predict source location """ - ProcurementRule = self.env["procurement.rule"] + StockRule = self.env["stock.rule"] product = self.product_id product_route_ids = [ x.id for x in product.route_ids + product.categ_id.total_route_ids ] - rules = ProcurementRule.search( + rules = StockRule.search( [("route_id", "in", product_route_ids)], order="route_sequence, sequence", limit=1, @@ -108,13 +138,12 @@ class SaleOrderLine(models.Model): ("route_id", "in", wh_route_ids), ] - rules = ProcurementRule.search(domain, order="route_sequence, sequence") + rules = StockRule.search(domain, order="route_sequence, sequence") if rules: - return rules[0] + fields.first(rules) return False - @api.multi def _get_procure_method(self): """ Get procure_method depending on product routes """ rule = self._get_line_rule() @@ -122,9 +151,8 @@ class SaleOrderLine(models.Model): return rule.procure_method return False - @api.multi @api.depends("state", "product_id.route_ids", "product_id.type") - def _is_stock_reservable(self): + def _compute_is_stock_reservable(self): for line in self: reservable = False if ( @@ -139,89 +167,30 @@ class SaleOrderLine(models.Model): reservable = True line.is_stock_reservable = reservable + @api.depends("order_id.state", "reservation_ids") + def _compute_is_readonly(self): + for line in self: + line.is_readonly = ( + len(line.reservation_ids) > 0 or line.order_id.state != "draft" + ) + reservation_ids = fields.One2many( "stock.reservation", "sale_line_id", string="Stock Reservation", copy=False ) is_stock_reservable = fields.Boolean( - compute="_is_stock_reservable", readonly=True, string="Can be reserved" + compute="_compute_is_stock_reservable", readonly=True, string="Can be reserved" ) + is_readonly = fields.Boolean(compute="_compute_is_readonly", store=False) - @api.multi def release_stock_reservation(self): reserv_ids = [reserv.id for line in self for reserv in line.reservation_ids] reservations = self.env["stock.reservation"].browse(reserv_ids) - reservations.release() + reservations.release_reserve() return True - def product_id_change( - self, - cr, - uid, - ids, - pricelist, - product, - qty=0, - uom=False, - qty_uos=0, - uos=False, - name="", - partner_id=False, - lang=False, - update_tax=True, - date_order=False, - packaging=False, - fiscal_position=False, - flag=False, - context=None, - ): - result = super(SaleOrderLine, self).product_id_change( - cr, - uid, - ids, - pricelist, - product, - qty=qty, - uom=uom, - qty_uos=qty_uos, - uos=uos, - name=name, - partner_id=partner_id, - lang=lang, - update_tax=update_tax, - date_order=date_order, - packaging=packaging, - fiscal_position=fiscal_position, - flag=flag, - context=context, - ) - if not ids: # warn only if we change an existing line - return result - assert len(ids) == 1, "Expected 1 ID, got %r" % ids - line = self.browse(cr, uid, ids[0], context=context) - if qty != line.product_uom_qty and line.reservation_ids: - msg = ( - _( - "As you changed the quantity of the line, " - "the quantity of the stock reservation will " - "be automatically adjusted to %.2f." - ) - % qty - ) - msg += "\n\n" - result.setdefault("warning", {}) - if result["warning"].get("message"): - result["warning"]["message"] += msg - else: - result["warning"] = { - "title": _("Configuration Error!"), - "message": msg, - } - return result - - @api.multi def write(self, vals): - block_on_reserve = ("product_id", "product_uom", "product_uos", "type") - update_on_reserve = ("price_unit", "product_uom_qty", "product_uos_qty") + block_on_reserve = ("product_id", "product_uom", "type") + update_on_reserve = ("price_unit", "product_uom_qty") keys = set(vals.keys()) test_block = keys.intersection(block_on_reserve) test_update = keys.intersection(update_on_reserve) @@ -238,7 +207,7 @@ class SaleOrderLine(models.Model): "before changing the product." ), ) - res = super(SaleOrderLine, self).write(vals) + res = super().write(vals) if test_update: for line in self: if not line.reservation_ids: @@ -258,7 +227,19 @@ class SaleOrderLine(models.Model): { "price_unit": line.price_unit, "product_uom_qty": line.product_uom_qty, - "product_uos_qty": line.product_uos_qty, } ) return res + + def unlink(self): + for line in self: + if line.reservation_ids: + raise UserError( + _( + 'Sale order line "[%s] %s" has a related reservation.\n' + "Please unreserve this line before " + "delete the line" + ) + % (line.order_id.name, line.name) + ) + return super().unlink() diff --git a/stock_reserve_sale/model/stock_reserve.py b/stock_reserve_sale/model/stock_reserve.py index 097ed67d2..c4c1c7378 100644 --- a/stock_reserve_sale/model/stock_reserve.py +++ b/stock_reserve_sale/model/stock_reserve.py @@ -1,24 +1,6 @@ -############################################################################## -# -# Author: Guewen Baconnier -# Copyright 2013 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from openerp import api, fields, models +# Copyright 2013 Camptocamp SA - Guewen Baconnier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models class StockReservation(models.Model): @@ -31,8 +13,7 @@ class StockReservation(models.Model): "sale.order", string="Sale Order", related="sale_line_id.order_id" ) - @api.multi - def release(self): + def release_reserve(self): for rec in self: rec.sale_line_id = False - return super(StockReservation, self).release() + return super().release_reserve() diff --git a/stock_reserve_sale/readme/CONTRIBUTORS.rst b/stock_reserve_sale/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..9f19ab9b7 --- /dev/null +++ b/stock_reserve_sale/readme/CONTRIBUTORS.rst @@ -0,0 +1,8 @@ +* Leonardo Pistone +* Alexandre Fayolle +* Yannick Vaucher +* Guewen Baconnier + +* `Tecnativa `_: + + * Carlos Roca diff --git a/stock_reserve_sale/readme/DESCRIPTION.rst b/stock_reserve_sale/readme/DESCRIPTION.rst new file mode 100644 index 000000000..cc3426fa1 --- /dev/null +++ b/stock_reserve_sale/readme/DESCRIPTION.rst @@ -0,0 +1,23 @@ +Allows to create stock reservations for quotation lines before the +confirmation of the quotation. The reservations might have a validity +date and in any case they are lifted when the quotation is canceled or +confirmed. + +Reservations can be done only on "make to stock" and stockable products. + +The reserved products are subtracted from the virtual stock. It means +that if you reserved a quantity of products which bring the virtual +stock below the minimum, the orderpoint will be triggered and new +purchase orders will be generated. It also implies that the max may be +exceeded if the reservations are canceled. + +If you want to prevent sales orders to be confirmed when the stock is +insufficient at the order date, you may want to install the +`sale_exception_nostock` module. + +Additionally, if the sale_owner_stock_sourcing module is installed, the owner +specified on the sale order line will be proposed as owner of the reservation. +If you try to make a reservation for an order whose lines have different, you +will get a message suggesting to reserve each line individually. There is no +module dependency: this modules is fully functional even without ownership +management. diff --git a/stock_reserve_sale/readme/USAGE.rst b/stock_reserve_sale/readme/USAGE.rst new file mode 100644 index 000000000..05049fb0a --- /dev/null +++ b/stock_reserve_sale/readme/USAGE.rst @@ -0,0 +1,8 @@ +#. Create a new Sale Order +#. Add lines to the Order + +Now you can reserve all the lines by clicking on *Reserve Stock* or you can reserve each +one by pressing on the lock icon at the lines. + +Once they where reserved, you can release the reserves by clicking at *Cancell all* +button or by clicking the undo icon on the lines. diff --git a/stock_reserve_sale/static/description/index.html b/stock_reserve_sale/static/description/index.html new file mode 100644 index 000000000..0f5c78d06 --- /dev/null +++ b/stock_reserve_sale/static/description/index.html @@ -0,0 +1,456 @@ + + + + + + +Stock Reserve Sales + + + +
+

Stock Reserve Sales

+ + +

Beta License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

+

Allows to create stock reservations for quotation lines before the +confirmation of the quotation. The reservations might have a validity +date and in any case they are lifted when the quotation is canceled or +confirmed.

+

Reservations can be done only on “make to stock” and stockable products.

+

The reserved products are subtracted from the virtual stock. It means +that if you reserved a quantity of products which bring the virtual +stock below the minimum, the orderpoint will be triggered and new +purchase orders will be generated. It also implies that the max may be +exceeded if the reservations are canceled.

+

If you want to prevent sales orders to be confirmed when the stock is +insufficient at the order date, you may want to install the +sale_exception_nostock module.

+

Additionally, if the sale_owner_stock_sourcing module is installed, the owner +specified on the sale order line will be proposed as owner of the reservation. +If you try to make a reservation for an order whose lines have different, you +will get a message suggesting to reserve each line individually. There is no +module dependency: this modules is fully functional even without ownership +management.

+

Table of contents

+ +
+

Usage

+
    +
  1. Create a new Sale Order
  2. +
  3. Add lines to the Order
  4. +
+

Now you can reserve all the lines by clicking on Reserve Stock or you can reserve each +one by pressing on the lock icon at the lines.

+

Once they where reserved, you can release the reserves by clicking at Cancell all +button or by clicking the undo icon on the lines.

+
+
+

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

+
    +
  • Camptocamp
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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

+

This module is part of the OCA/stock-logistics-warehouse project on GitHub.

+

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

+
+
+
+ + diff --git a/stock_reserve_sale/test/sale_line_reserve.yml b/stock_reserve_sale/test/sale_line_reserve.yml deleted file mode 100644 index 3f2552607..000000000 --- a/stock_reserve_sale/test/sale_line_reserve.yml +++ /dev/null @@ -1,101 +0,0 @@ -- I create a product to test the stock reservation -- !record {model: product.product, id: product_yogurt}: - default_code: 001yogurt - name: yogurt - type: product - categ_id: product.product_category_1 - list_price: 100.0 - standard_price: 70.0 - uom_id: product.product_uom_kgm - uom_po_id: product.product_uom_kgm - valuation: real_time - cost_method: average - property_stock_account_input: account.o_expense - property_stock_account_output: account.o_income -- I update the current stock of the yogurt with 10 kgm -- !record {model: stock.change.product.qty, id: change_qty}: - new_quantity: 10 - product_id: product_yogurt -- !python {model: stock.change.product.qty}: | - context['active_id'] = ref('product_yogurt') - self.change_product_qty(cr, uid, [ref('change_qty')], context=context) -- In order to test reservation of the sales order, I create a sales order -- !record {model: sale.order, id: sale_reserve_02}: - partner_id: base.res_partner_2 - payment_term: account.account_payment_term -- And I create a sales order line -- ? !record { - model: sale.order.line, - id: sale_line_reserve_02_01, - view: sale.view_order_line_tree, - } - : name: Yogurt - product_id: product_yogurt - product_uom_qty: 4 - product_uom: product.product_uom_kgm - order_id: sale_reserve_02 -- And I create a stock reserve for this line -- !python {model: sale.stock.reserve}: | - active_id = ref('sale_line_reserve_02_01') - context['active_id'] = active_id - context['active_ids'] = [active_id] - context['active_model'] = 'sale.order.line' - wizard_id = self.create(cr, uid, {}, context=context) - self.button_reserve(cr, uid, [wizard_id], context=context) -- I check Virtual stock of yogurt after update reservation -- !python {model: product.product}: | - product = self.browse(cr, uid, ref('product_yogurt'), context=context) - assert product.virtual_available == 6, "Stock is not updated." -- I set product_12 to MTO (doesn't work) -- !record {model: product.product, id: product.product_product_12}: - route_ids: - - stock.route_warehouse0_mto -- I set MTO for real -- !python {model: product.product}: | - product = self.browse(cr, uid, ref('product.product_product_12'), context=context) - self.write(cr, uid, ref('product.product_product_12'), - {'route_ids': [(6, False, [ref('stock.route_warehouse0_mto')])]}, context=context) -- And I create a MTO sales order line -- ? !record { - model: sale.order.line, - id: sale_line_reserve_02_02, - view: sale.view_order_line_tree, - } - : order_id: sale_reserve_02 - name: Mouse, Wireless - product_id: product.product_product_12 - product_uom_qty: 4 - product_uom: product.product_uom_unit -- And I try to create a stock reserve for this MTO line -- !python {model: sale.stock.reserve}: | - active_id = ref('sale_line_reserve_02_02') - context['active_id'] = active_id - context['active_ids'] = [active_id] - context['active_model'] = 'sale.order.line' - wizard_id = self.create(cr, uid, {}, context=context) - self.button_reserve(cr, uid, [wizard_id], context=context) -- I should not have a stock reservation for a MTO line -- !python {model: stock.reservation}: | - reserv_ids = self.search( - cr, uid, - [('sale_line_id', '=', ref('sale_line_reserve_02_02'))], - context=context) - assert not reserv_ids, "No stock reservation should be created for MTO lines" -- And I change the quantity in the first line -- ? !record { - model: sale.order.line, - id: sale_line_reserve_02_01, - view: sale.view_order_line_tree, - } - : product_uom_qty: 5 -- I check Virtual stock of yogurt after change of reservations -- !python {model: product.product}: | - product = self.browse(cr, uid, ref('product_yogurt'), context=context) - assert product.virtual_available == 5, "Stock is not updated." -- I release the sales order's reservations for the first line -- !python {model: sale.order.line}: | - self.release_stock_reservation(cr, uid, [ref('sale_line_reserve_02_01')], context=context) -- I check Virtual stock of yogurt after release of reservations -- !python {model: product.product}: | - product = self.browse(cr, uid, ref('product_yogurt'), context=context) - assert product.virtual_available == 10, "Stock is not updated." diff --git a/stock_reserve_sale/test/sale_reserve.yml b/stock_reserve_sale/test/sale_reserve.yml deleted file mode 100644 index a02603b49..000000000 --- a/stock_reserve_sale/test/sale_reserve.yml +++ /dev/null @@ -1,56 +0,0 @@ -- I force recomputation of stock.location parent left/right -- !python {model: stock.location}: - # we need this because when running the tests at install time as is done on - # Travis, the hook performing this operation for the new stock reservation - # location is run after the test execution. This causes the stock level - # computation to be wrong at the time the tests are run. - self._parent_store_compute(cr) -- I create a product to test the stock reservation -- !record {model: product.product, id: product_gelato}: - default_code: 001GELATO - name: Gelato - type: product - categ_id: product.product_category_1 - list_price: 100.0 - standard_price: 70.0 - uom_id: product.product_uom_kgm - uom_po_id: product.product_uom_kgm - valuation: real_time - cost_method: average - property_stock_account_input: account.o_expense - property_stock_account_output: account.o_income -- I update the current stock of the Gelato with 10 kgm -- !record {model: stock.change.product.qty, id: change_qty}: - new_quantity: 10 - product_id: product_gelato -- !python {model: stock.change.product.qty}: | - context['active_id'] = ref('product_gelato') - self.change_product_qty(cr, uid, [ref('change_qty')], context=context) -- In order to test reservation of the sales order, I create a sales order -- !record {model: sale.order, id: sale_reserve_01}: - partner_id: base.res_partner_2 - payment_term: account.account_payment_term - order_line: - - product_id: product_gelato - product_uom_qty: 4 - - product_id: product_gelato - product_uom_qty: 1 -- I call the wizard to reserve the products of the sales order -- !python {model: sale.stock.reserve}: | - active_id = ref('sale_reserve_01') - context['active_id'] = active_id - context['active_ids'] = [active_id] - context['active_model'] = 'sale.order' - wizard_id = self.create(cr, uid, {}, context=context) - self.button_reserve(cr, uid, [wizard_id], context=context) -- I check Virtual stock of Gelato after update reservation -- !python {model: product.product}: | - product = self.browse(cr, uid, ref('product_gelato'), context=context) - assert product.virtual_available == 5, "Stock is not updated after reservation." -- I release the sales order's reservations -- !python {model: sale.order}: | - self.release_all_stock_reservation(cr, uid, [ref('sale_reserve_01')], context=context) -- I check Virtual stock of Gelato after release of reservations -- !python {model: product.product}: | - product = self.browse(cr, uid, ref('product_gelato'), context=context) - assert product.virtual_available == 10, "Stock is not updated after releasing reservations." diff --git a/stock_reserve_sale/tests/__init__.py b/stock_reserve_sale/tests/__init__.py new file mode 100644 index 000000000..726598f66 --- /dev/null +++ b/stock_reserve_sale/tests/__init__.py @@ -0,0 +1 @@ +from . import test_stock_reserve_sale diff --git a/stock_reserve_sale/tests/test_stock_reserve_sale.py b/stock_reserve_sale/tests/test_stock_reserve_sale.py new file mode 100644 index 000000000..cb47eacc9 --- /dev/null +++ b/stock_reserve_sale/tests/test_stock_reserve_sale.py @@ -0,0 +1,138 @@ +# Copyright 2021 Tecnativa - Carlos Roca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.exceptions import UserError +from odoo.tests import Form, common + + +class TestStockReserveSale(common.SavepointCase): + def setUp(self): + super().setUp() + partner_form = Form(self.env["res.partner"]) + partner_form.name = "Test partner" + partner_form.country_id = self.env.ref("base.es") + self.partner = partner_form.save() + warehouse_form = Form(self.env["stock.warehouse"]) + warehouse_form.name = "Test warehouse" + warehouse_form.code = "TEST" + self.warehouse = warehouse_form.save() + product_form = Form(self.env["product.product"]) + product_form.name = "Test Product 1" + product_form.type = "product" + self.product_1 = product_form.save() + product_form = Form(self.env["product.product"]) + product_form.name = "Test Product 2" + product_form.type = "product" + self.product_2 = product_form.save() + self.env["stock.quant"].create( + { + "product_id": self.product_1.id, + "location_id": self.warehouse.lot_stock_id.id, + "quantity": 10.0, + } + ) + self.env["stock.quant"].create( + { + "product_id": self.product_2.id, + "location_id": self.warehouse.lot_stock_id.id, + "quantity": 10.0, + } + ) + + def test_reserve_01_tree_reserve_release(self): + sale_order_form = Form(self.env["sale.order"]) + sale_order_form.partner_id = self.partner + with sale_order_form.order_line.new() as order_line_form: + order_line_form.product_id = self.product_1 + order_line_form.product_uom_qty = 3 + so = sale_order_form.save() + wiz = Form( + self.env["sale.stock.reserve"].with_context( + active_model="sale.order.line", active_ids=so.order_line.ids + ) + ).save() + wiz.button_reserve() + self.assertEquals(self.product_1.virtual_available, 7) + so.order_line.release_stock_reservation() + self.assertEquals(self.product_1.virtual_available, 10) + + def test_reserve_02_all_form_reserve_release(self): + sale_order_form = Form(self.env["sale.order"]) + sale_order_form.partner_id = self.partner + with sale_order_form.order_line.new() as order_line_form: + order_line_form.product_id = self.product_1 + order_line_form.product_uom_qty = 3 + with sale_order_form.order_line.new() as order_line_form: + order_line_form.product_id = self.product_2 + order_line_form.product_uom_qty = 5 + so = sale_order_form.save() + wiz = Form( + self.env["sale.stock.reserve"].with_context( + active_model="sale.order", active_id=so.id, active_ids=so.ids + ) + ).save() + wiz.button_reserve() + self.assertEquals(self.product_1.virtual_available, 7) + self.assertEquals(self.product_2.virtual_available, 5) + so.release_all_stock_reservation() + self.assertEquals(self.product_1.virtual_available, 10) + self.assertEquals(self.product_2.virtual_available, 10) + + def test_reserve_03_confirm_order_release(self): + sale_order_form = Form(self.env["sale.order"]) + sale_order_form.partner_id = self.partner + with sale_order_form.order_line.new() as order_line_form: + order_line_form.product_id = self.product_1 + order_line_form.product_uom_qty = 3 + so = sale_order_form.save() + wiz = Form( + self.env["sale.stock.reserve"].with_context( + active_model="sale.order.line", active_ids=so.order_line.ids + ) + ).save() + wiz.button_reserve() + self.assertEquals(self.product_1.virtual_available, 7) + so.action_confirm() + cancelled_reservation = self.env["stock.reservation"].search( + [("product_id", "=", self.product_1.id), ("state", "=", "cancel")] + ) + self.assertEquals(len(cancelled_reservation), 1) + self.assertEquals(self.product_1.virtual_available, 7) + + def test_reserve_04_cancel_order_release(self): + sale_order_form = Form(self.env["sale.order"]) + sale_order_form.partner_id = self.partner + with sale_order_form.order_line.new() as order_line_form: + order_line_form.product_id = self.product_1 + order_line_form.product_uom_qty = 3 + so = sale_order_form.save() + wiz = Form( + self.env["sale.stock.reserve"].with_context( + active_model="sale.order.line", active_ids=so.order_line.ids + ) + ).save() + wiz.button_reserve() + self.assertEquals(self.product_1.virtual_available, 7) + so.action_cancel() + cancelled_reservation = self.env["stock.reservation"].search( + [("product_id", "=", self.product_1.id), ("state", "=", "cancel")] + ) + self.assertEquals(len(cancelled_reservation), 1) + self.assertEquals(self.product_1.virtual_available, 10) + + def test_reserve_05_unlink_order(self): + sale_order_form = Form(self.env["sale.order"]) + sale_order_form.partner_id = self.partner + with sale_order_form.order_line.new() as order_line_form: + order_line_form.product_id = self.product_1 + order_line_form.product_uom_qty = 3 + so = sale_order_form.save() + wiz = Form( + self.env["sale.stock.reserve"].with_context( + active_model="sale.order.line", active_ids=so.order_line.ids + ) + ).save() + wiz.button_reserve() + with self.assertRaises(UserError): + so.unlink() + with self.assertRaises(UserError): + so.order_line.unlink() diff --git a/stock_reserve_sale/view/sale.xml b/stock_reserve_sale/view/sale.xml index 487344b05..2ded550dc 100644 --- a/stock_reserve_sale/view/sale.xml +++ b/stock_reserve_sale/view/sale.xml @@ -1,79 +1,122 @@ - - - - sale.order.form.reserve - sale.order - - - - - {"reload_on_button": 1} - - - -
- + + + sale.order.form.reserve + sale.order + + + + + {"reload_on_button": 1} - - - + + +