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 new file mode 100644 index 000000000..b7ba372fb --- /dev/null +++ b/stock_reserve_sale/README.rst @@ -0,0 +1,114 @@ +=================== +Stock Reserve Sales +=================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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 +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** + +.. 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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* Leonardo Pistone +* Alexandre Fayolle +* Yannick Vaucher +* Guewen Baconnier + +* `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 + +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/__init__.py b/stock_reserve_sale/__init__.py new file mode 100644 index 000000000..8bf1bb619 --- /dev/null +++ b/stock_reserve_sale/__init__.py @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 000000000..fa4cccfcf --- /dev/null +++ b/stock_reserve_sale/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2013 Camptocamp SA - Guewen Baconnier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Stock Reserve Sales", + "version": "13.0.1.0.0", + "author": "Camptocamp, Odoo Community Association (OCA)", + "category": "Warehouse", + "license": "AGPL-3", + "complexity": "normal", + "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", + ], + "installable": True, + "auto_install": False, +} diff --git a/stock_reserve_sale/i18n/es.po b/stock_reserve_sale/i18n/es.po new file mode 100644 index 000000000..3f8c5aec1 --- /dev/null +++ b/stock_reserve_sale/i18n/es.po @@ -0,0 +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: 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: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 2.0.6\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 "" +"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 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_order__is_stock_reservable +msgid "Can Have Stock Reservations" +msgstr "Puede tener reservas de stock" + +#. 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 "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 "Cancelar" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_order_form_reserve +msgid "Cancell all" +msgstr "Cancelar todo" + +#. module: stock_reserve_sale +#: 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 +#: 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 +#: 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 "Error" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_order__has_stock_reservation +msgid "Has Stock Reservations" +msgstr "Tiene reservas de stock" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__id +msgid "ID" +msgstr "ID" + +#. 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 "" +"Si la fecha de validez está asignada, la reserva va a ser liberada al final " +"de esta." + +#. module: stock_reserve_sale +#: 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 +#: 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 +#: 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 "Localización donde el sistema va a reservar los productos." + +#. 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 "Notas" + +#. 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 "Pre-reservar productos en stock" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_order_form_reserve +msgid "Release Reservation" +msgstr "Liberar" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__location_dest_id +msgid "Reservation Location" +msgstr "Localización de la reserva" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form +msgid "Reserve" +msgstr "Reservar" + +#. 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 "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 "Reservar stock para las lineas del pedido" + +#. module: stock_reserve_sale +#: 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 "Linea de venta" + +#. module: stock_reserve_sale +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_stock_reservation_form +msgid "Sales" +msgstr "Ventas" + +#. module: stock_reserve_sale +#: model:ir.model,name:stock_reserve_sale.model_sale_order +msgid "Sales Order" +msgstr "Pedido" + +#. module: stock_reserve_sale +#: model:ir.model,name:stock_reserve_sale.model_sale_order_line +msgid "Sales Order Line" +msgstr "Líneas de pedidos de venta" + +#. 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 "" +"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 +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__location_id +msgid "Source Location" +msgstr "Ubicación origen" + +#. module: stock_reserve_sale +#: model:ir.model.fields,field_description:stock_reserve_sale.field_sale_stock_reserve__owner_id +msgid "Stock Owner" +msgstr "Propietario" + +#. 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 "Reserva de existencias" + +#. 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 "" +"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: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 +#: model_terms:ir.ui.view,arch_db:stock_reserve_sale.view_sale_stock_reserve_form +msgid "or" +msgstr "o" + +#. 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/i18n/fi.po b/stock_reserve_sale/i18n/fi.po new file mode 100644 index 000000000..2a1ea21fc --- /dev/null +++ b/stock_reserve_sale/i18n/fi.po @@ -0,0 +1,211 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_reserve_sale +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-04-23 15:55+0000\n" +"PO-Revision-Date: 2015-04-23 20:35+0200\n" +"Last-Translator: Miku Laitinen \n" +"Language-Team: Avoin.Systems \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.5.4\n" +"Language: Finnish\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#. module: stock_reserve_sale +#: view:sale.stock.reserve: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 "" +"Tuotevaraus luodaan valittujen rivien tuotteille.\n" +"Jos vapautuspäivä on määritetty, varaus peruutetaan automaattisesti " +"kyseisenä päivänä." + +#. 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 "" +"Muuttaessasi rivin kappalemäärää, tuotevarauksen kappalemäärä päivitetään " +"automaattisesti arvoon %.2f." + +#. module: stock_reserve_sale +#: field:sale.order,is_stock_reservable:0 +msgid "Can Have Stock Reservations" +msgstr "Voi sisältää tuotevarauksia" + +#. module: stock_reserve_sale +#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form +msgid "Cancel" +msgstr "Peruuta" + +#. module: stock_reserve_sale +#: code:addons/stock_reserve_sale/model/sale.py:188 +#, python-format +msgid "Configuration Error!" +msgstr "Konfiguraatiovirhe." + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,create_uid:0 +msgid "Created by" +msgstr "Luonut" + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,create_date:0 +msgid "Created on" +msgstr "Luotu" + +#. module: stock_reserve_sale +#: code:addons/stock_reserve_sale/model/sale.py:210 +#: code:addons/stock_reserve_sale/model/sale.py:222 +#, python-format +msgid "Error" +msgstr "Virhe" + +#. module: stock_reserve_sale +#: field:sale.order,has_stock_reservation:0 +msgid "Has Stock Reservations" +msgstr "Sisältää tuotevarauksia" + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,id:0 +msgid "ID" +msgstr "ID" + +#. module: stock_reserve_sale +#: help:sale.stock.reserve,date_validity:0 +msgid "" +"If a date is given, the reservations will be released at the end of the " +"validity." +msgstr "" +"Jos vapautuspäivä on määritetty, varaukset perutaan kyseisenä päivänä " +"automaattisesti." + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,write_uid:0 +msgid "Last Updated by" +msgstr "Viimeksi päivittänyt" + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,write_date:0 +msgid "Last Updated on" +msgstr "Viimeksi päivitetty" + +#. module: stock_reserve_sale +#: help:sale.stock.reserve,location_dest_id:0 +msgid "Location where the system will reserve the products." +msgstr "Sijainti, jonne tuotteet varataan." + +#. module: stock_reserve_sale +#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form +#: field:sale.stock.reserve,note:0 +msgid "Notes" +msgstr "Huomautukset" + +#. module: stock_reserve_sale +#: view:sale.order:stock_reserve_sale.view_order_form_reserve +msgid "Pre-book products from stock" +msgstr "Varaa tuotteita varastosta" + +#. module: stock_reserve_sale +#: view:sale.order:stock_reserve_sale.view_order_form_reserve +msgid "Release Reservation" +msgstr "Peru varaus" + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,location_dest_id:0 +msgid "Reservation Location" +msgstr "Varauspaikka" + +#. module: stock_reserve_sale +#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form +msgid "Reserve" +msgstr "Varaa" + +#. 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 +msgid "Reserve Stock" +msgstr "Varaa tuotteet" + +#. module: stock_reserve_sale +#: model:ir.actions.act_window,name:stock_reserve_sale.action_sale_stock_reserve +msgid "Reserve Stock for Quotation Lines" +msgstr "Rivien tuotteiden varaus" + +#. module: stock_reserve_sale +#: field:stock.reservation,sale_line_id:0 +msgid "Sale Order Line" +msgstr "Myyntitilausrivi" + +#. module: stock_reserve_sale +#: view:stock.reservation:stock_reserve_sale.view_stock_reservation_form +msgid "Sales" +msgstr "Myynti" + +#. module: stock_reserve_sale +#: model:ir.model,name:stock_reserve_sale.model_sale_order +msgid "Sales Order" +msgstr "Myyntitilaus" + +#. module: stock_reserve_sale +#: model:ir.model,name:stock_reserve_sale.model_sale_order_line +msgid "Sales Order Line" +msgstr "Myyntitilausrivi" + +#. module: stock_reserve_sale +#: code:addons/stock_reserve_sale/model/sale.py:223 +#, 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 "" +"Riviin on linkitetty useita tuotevarauksia. Kappalemäärien muokkaaminen ei " +"ole mahdollista. Ole hyvä ja peruuta varaus ennen määrien muokkaamista." + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,location_id:0 +msgid "Source Location" +msgstr "Lähtöpaikka" + +#. module: stock_reserve_sale +#: model:ir.model,name:stock_reserve_sale.model_stock_reservation +#: field:sale.order.line,reservation_ids:0 +msgid "Stock Reservation" +msgstr "Tuotevaraus" + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,date_validity:0 +msgid "Validity Date" +msgstr "Vapautuspäivä" + +#. module: stock_reserve_sale +#: code:addons/stock_reserve_sale/model/sale.py:211 +#, 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 "" +"Et voi vaihtaa tuotetta tai tuotteen yksikköä, jos rivillä on tuotevaraus. " +"Peruuta varaus ennen tuotteen vaihtamista." + +#. module: stock_reserve_sale +#: view:sale.order:stock_reserve_sale.view_order_form_reserve +msgid "cancel all" +msgstr "peru kaikki" + +#. module: stock_reserve_sale +#: view:sale.order:stock_reserve_sale.view_order_form_reserve +msgid "{\"reload_on_button\": 1}" +msgstr "{\"reload_on_button\": 1}" diff --git a/stock_reserve_sale/i18n/fr.po b/stock_reserve_sale/i18n/fr.po new file mode 100644 index 000000000..79dfac1cc --- /dev/null +++ b/stock_reserve_sale/i18n/fr.po @@ -0,0 +1,209 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_reserve_sale +# +# Translators: +# Pierre Verkest , 2015 +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-27 21:09+0000\n" +"Last-Translator: Pierre Verkest \n" +"Language-Team: French (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-8-0/language/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: stock_reserve_sale +#: view:sale.stock.reserve: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 "Une réservation de stock sera créée pour les produits\ndes lignes de devis sélectionnées. Si une date de validité est\nspécifiée, la réservation sera annulée une fois cette date dépassée." + +#. 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 "Suite au changement de quantité de la ligne, la quantité de stock réservée va être ajusté à %.2f." + +#. module: stock_reserve_sale +#: field:sale.order,is_stock_reservable:0 +msgid "Can Have Stock Reservations" +msgstr "" + +#. module: stock_reserve_sale +#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form +msgid "Cancel" +msgstr "Annuler" + +#. module: stock_reserve_sale +#: code:addons/stock_reserve_sale/model/sale.py:188 +#, python-format +msgid "Configuration Error!" +msgstr "Erreur de configuration!" + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,create_uid:0 +msgid "Created by" +msgstr "Créé par" + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,create_date:0 +msgid "Created on" +msgstr "Créé le" + +#. module: stock_reserve_sale +#: code:addons/stock_reserve_sale/model/sale.py:210 +#: code:addons/stock_reserve_sale/model/sale.py:222 +#, python-format +msgid "Error" +msgstr "Erreur" + +#. module: stock_reserve_sale +#: field:sale.order,has_stock_reservation:0 +msgid "Has Stock Reservations" +msgstr "" + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,id:0 +msgid "ID" +msgstr "ID" + +#. module: stock_reserve_sale +#: help:sale.stock.reserve,date_validity:0 +msgid "" +"If a date is given, the reservations will be released at the end of the " +"validity." +msgstr "Si une date est donnée, la réservation sera annulée à la fin de cette date de validité." + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,write_uid:0 +msgid "Last Updated by" +msgstr "Dernière màj par" + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,write_date:0 +msgid "Last Updated on" +msgstr "Dernière màj le" + +#. module: stock_reserve_sale +#: help:sale.stock.reserve,location_dest_id:0 +msgid "Location where the system will reserve the products." +msgstr "Emplacement de stock où sont réservés les produits (pour le système)." + +#. module: stock_reserve_sale +#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form +#: field:sale.stock.reserve,note:0 +msgid "Notes" +msgstr "Notes" + +#. module: stock_reserve_sale +#: view:sale.order:stock_reserve_sale.view_order_form_reserve +msgid "Pre-book products from stock" +msgstr "" + +#. module: stock_reserve_sale +#: view:sale.order:stock_reserve_sale.view_order_form_reserve +msgid "Release Reservation" +msgstr "" + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,location_dest_id:0 +msgid "Reservation Location" +msgstr "" + +#. module: stock_reserve_sale +#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form +msgid "Reserve" +msgstr "Réserver" + +#. 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 +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 +#: field:stock.reservation,sale_line_id:0 +msgid "Sale Order Line" +msgstr "" + +#. module: stock_reserve_sale +#: view:stock.reservation: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:223 +#, 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 +#: field:sale.stock.reserve,location_id:0 +msgid "Source Location" +msgstr "Emplacement Source" + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,owner_id:0 +msgid "Stock Owner" +msgstr "" + +#. module: stock_reserve_sale +#: model:ir.model,name:stock_reserve_sale.model_stock_reservation +#: field:sale.order.line,reservation_ids:0 +msgid "Stock Reservation" +msgstr "" + +#. module: stock_reserve_sale +#: field:sale.stock.reserve,date_validity:0 +msgid "Validity Date" +msgstr "Date de validité" + +#. module: stock_reserve_sale +#: code:addons/stock_reserve_sale/model/sale.py:211 +#, 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 "Vous ne pouvez pas modifier le produit ou l'unité de mesure des lignes avec une réservation en stock. Veuillez annuler les réservations avant de modifier la ligne." + +#. module: stock_reserve_sale +#: view:sale.order:stock_reserve_sale.view_order_form_reserve +msgid "cancel all" +msgstr "tout annuler" + +#. module: stock_reserve_sale +#: view:sale.stock.reserve:stock_reserve_sale.view_sale_stock_reserve_form +msgid "or" +msgstr "ou" + +#. module: stock_reserve_sale +#: view:sale.order:stock_reserve_sale.view_order_form_reserve +msgid "{\"reload_on_button\": 1}" +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 new file mode 100644 index 000000000..7090ccfa3 --- /dev/null +++ b/stock_reserve_sale/model/__init__.py @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 000000000..f7b07fe2e --- /dev/null +++ b/stock_reserve_sale/model/sale.py @@ -0,0 +1,245 @@ +# 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 _ + +_LINE_KEYS = ["product_id", "product_uom_qty"] + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + @api.depends( + "state", "order_line.reservation_ids", "order_line.is_stock_reservable" + ) + def _compute_stock_reservation(self): + for sale in self: + has_stock_reservation = False + is_stock_reservable = False + for line in sale.order_line: + if line.reservation_ids: + has_stock_reservation = True + if line.is_stock_reservable: + is_stock_reservable = True + if sale.state not in ("draft", "sent"): + is_stock_reservable = False + sale.is_stock_reservable = is_stock_reservable + sale.has_stock_reservation = has_stock_reservation + + has_stock_reservation = fields.Boolean( + compute="_compute_stock_reservation", + readonly=True, + multi="stock_reservation", + store=True, + string="Has Stock Reservations", + ) + is_stock_reservable = fields.Boolean( + compute="_compute_stock_reservation", + readonly=True, + multi="stock_reservation", + store=True, + string="Can Have Stock Reservations", + ) + + 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 + + def action_confirm(self): + self.release_all_stock_reservation() + return super().action_confirm() + + def action_cancel(self): + self.release_all_stock_reservation() + 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" + + def _get_line_rule(self): + """ Get applicable rule for this product + + Reproduce get suitable rule from procurement + to predict source location """ + 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 = StockRule.search( + [("route_id", "in", product_route_ids)], + order="route_sequence, sequence", + limit=1, + ) + + if not rules: + warehouse = self.order_id.warehouse_id + wh_routes = warehouse.route_ids + wh_route_ids = [route.id for route in wh_routes] + domain = [ + "|", + ("warehouse_id", "=", warehouse.id), + ("warehouse_id", "=", False), + ("route_id", "in", wh_route_ids), + ] + + rules = StockRule.search(domain, order="route_sequence, sequence") + + if rules: + fields.first(rules) + return False + + def _get_procure_method(self): + """ Get procure_method depending on product routes """ + rule = self._get_line_rule() + if rule: + return rule.procure_method + return False + + @api.depends("state", "product_id.route_ids", "product_id.type") + def _compute_is_stock_reservable(self): + for line in self: + reservable = False + if ( + not ( + line.state != "draft" + or line._get_procure_method() == "make_to_order" + or not line.product_id + or line.product_id.type == "service" + ) + and not line.reservation_ids + ): + 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="_compute_is_stock_reservable", readonly=True, string="Can be reserved" + ) + is_readonly = fields.Boolean(compute="_compute_is_readonly", store=False) + + 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_reserve() + return True + + def write(self, vals): + 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) + if test_block: + for line in self: + if not line.reservation_ids: + continue + raise except_orm( + _("Error"), + _( + "You cannot change the product or unit of measure " + "of lines with a stock reservation. " + "Release the reservation " + "before changing the product." + ), + ) + res = super().write(vals) + if test_update: + for line in self: + if not line.reservation_ids: + continue + if len(line.reservation_ids) > 1: + raise except_orm( + _("Error"), + _( + "Several stock reservations are linked with the " + "line. Impossible to adjust their quantity. " + "Please release the reservation " + "before changing the quantity." + ), + ) + + line.reservation_ids.write( + { + "price_unit": line.price_unit, + "product_uom_qty": line.product_uom_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 new file mode 100644 index 000000000..c4c1c7378 --- /dev/null +++ b/stock_reserve_sale/model/stock_reserve.py @@ -0,0 +1,19 @@ +# 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): + _inherit = "stock.reservation" + + sale_line_id = fields.Many2one( + "sale.order.line", string="Sale Order Line", ondelete="cascade", copy=False + ) + sale_id = fields.Many2one( + "sale.order", string="Sale Order", related="sale_line_id.order_id" + ) + + def release_reserve(self): + for rec in self: + rec.sale_line_id = False + 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/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 new file mode 100644 index 000000000..2ded550dc --- /dev/null +++ b/stock_reserve_sale/view/sale.xml @@ -0,0 +1,122 @@ + + + + sale.order.form.reserve + sale.order + + + + + {"reload_on_button": 1} + + + +
+ + + + + + + {'readonly': [('is_readonly', '=', True)]} + + + {'readonly': [('is_readonly', '=', True)]} + + + + + + {'readonly': [('is_readonly', '=', True)]} + + + {'readonly': [('is_readonly', '=', True)]} + + + + diff --git a/stock_reserve_sale/view/stock_reserve.xml b/stock_reserve_sale/view/stock_reserve.xml new file mode 100644 index 000000000..976e73e8e --- /dev/null +++ b/stock_reserve_sale/view/stock_reserve.xml @@ -0,0 +1,26 @@ + + + + stock.reservation.form + stock.reservation + + + + + + + + + + + + stock.reservation.tree + stock.reservation + + + + + + + + diff --git a/stock_reserve_sale/wizard/__init__.py b/stock_reserve_sale/wizard/__init__.py new file mode 100644 index 000000000..f52589b97 --- /dev/null +++ b/stock_reserve_sale/wizard/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2013 Camptocamp SA - Guewen Baconnier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import sale_stock_reserve diff --git a/stock_reserve_sale/wizard/sale_stock_reserve.py b/stock_reserve_sale/wizard/sale_stock_reserve.py new file mode 100644 index 000000000..90454f79d --- /dev/null +++ b/stock_reserve_sale/wizard/sale_stock_reserve.py @@ -0,0 +1,112 @@ +# Copyright 2013 Camptocamp SA - Guewen Baconnier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import _, api, exceptions, fields, models + + +class SaleStockReserve(models.TransientModel): + _name = "sale.stock.reserve" + _description = "Sale Stock Reserve" + + @api.model + def _default_location_id(self): + return self.env["stock.reservation"].get_location_from_ref( + "stock.stock_location_stock" + ) + + @api.model + def _default_location_dest_id(self): + return self.env["stock.reservation"]._default_location_dest_id() + + def _default_owner(self): + """If sale_owner_stock_sourcing is installed, it adds an owner field + on sale order lines. Use it. + + """ + model = self.env[self.env.context["active_model"]] + if model._name == "sale.order": + lines = model.browse(self.env.context["active_id"]).order_line + else: + lines = model.browse(self.env.context["active_ids"]) + + try: + owners = {l.stock_owner_id for l in lines} + except AttributeError: + return self.env["res.partner"] + # module sale_owner_stock_sourcing not installed, fine + + if len(owners) == 1: + return owners.pop() + elif len(owners) > 1: + raise exceptions.Warning( + _( + """The lines have different owners. Please reserve them + individually with the reserve button on each one.""" + ) + ) + + return self.env["res.partner"] + + location_id = fields.Many2one( + "stock.location", "Source Location", required=True, default=_default_location_id + ) + location_dest_id = fields.Many2one( + "stock.location", + "Reservation Location", + required=True, + help="Location where the system will reserve the " "products.", + default=_default_location_dest_id, + ) + date_validity = fields.Date( + "Validity Date", + help="If a date is given, the reservations will be released " + "at the end of the validity.", + ) + note = fields.Text("Notes") + owner_id = fields.Many2one("res.partner", "Stock Owner", default=_default_owner) + + def _prepare_stock_reservation(self, line): + self.ensure_one() + return { + "product_id": line.product_id.id, + "product_uom": line.product_uom.id, + "product_uom_qty": line.product_uom_qty, + "date_validity": self.date_validity, + "name": "{} ({})".format(line.order_id.name, line.name), + "location_id": self.location_id.id, + "location_dest_id": self.location_dest_id.id, + "note": self.note, + "price_unit": line.price_unit, + "sale_line_id": line.id, + "restrict_partner_id": self.owner_id.id, + } + + def stock_reserve(self, line_ids): + self.ensure_one() + + lines = self.env["sale.order.line"].browse(line_ids) + for line in lines: + if not line.is_stock_reservable: + continue + vals = self._prepare_stock_reservation(line) + reserv = self.env["stock.reservation"].create(vals) + reserv.reserve() + return True + + def button_reserve(self): + env = self.env + self.ensure_one() + close = {"type": "ir.actions.act_window_close"} + active_model = env.context.get("active_model") + active_ids = env.context.get("active_ids") + if not (active_model and active_ids): + return close + + if active_model == "sale.order": + sales = env["sale.order"].browse(active_ids) + line_ids = [line.id for sale in sales for line in sale.order_line] + + if active_model == "sale.order.line": + line_ids = active_ids + + self.stock_reserve(line_ids) + return close diff --git a/stock_reserve_sale/wizard/sale_stock_reserve_view.xml b/stock_reserve_sale/wizard/sale_stock_reserve_view.xml new file mode 100644 index 000000000..0f338d858 --- /dev/null +++ b/stock_reserve_sale/wizard/sale_stock_reserve_view.xml @@ -0,0 +1,42 @@ + + + + sale.stock.reserve.form + sale.stock.reserve + +
+

+ A stock reservation will be created for the products + of the selected quotation lines. If a validity date is specified, + the reservation will be released once the date has passed. +

+ + + + + + + + + +
+
+
+
+
+ + Reserve Stock for Quotation Lines + ir.actions.act_window + sale.stock.reserve + form + new + +