Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

165
sale_block_no_stock/README.rst Executable file
View File

@@ -0,0 +1,165 @@
===================
Sale Block no Stock
===================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:c16ad9d6d37bb710e3897f013a183f2653b71255c3092b60129cd8f9245149bb
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/sale-workflow/tree/18.0/sale_block_no_stock
:alt: OCA/sale-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/sale-workflow-18-0/sale-workflow-18-0-sale_block_no_stock
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=18.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module extends the functionality of Sales to support blocking sales
and to allow you to ensure you are capable to send product you have in
stock.
When a Sale Order is going to be confirmed, it will be checked if the
quantity demanded exceeds that of the field selected in the
configuration, and in this case a wizard will appear to allow you to fix
quantities indicating what is the maximum quantity that can be ordered.
Then, you can adjust UoM quantities, Packaging quantities or move
remaining unfixed lines to a new order.
If the user who is confirming an order has a group that is allowed, an
extra option to confirm the Order with errors will appear on the Wizard.
This module only can block lines with product type 'product' (storable
products).
Internal notes will be posted on sales when adjusting quantities or
moving to another orders.
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_
**Table of contents**
.. contents::
:local:
Use Cases / Context
===================
This module was developed because sometimes you don't want to allow a
sale to be confirmed when there is no enough product to sold.
It will be useful for you if selling more than you have in stock or
planned is not a desired behaviour.
Configuration
=============
To configure this module, you need to:
1. Go to Settings > Sales > Quotations & Orders > Blocking sales due to
lack of stock
2. Fill *Field to compare against the quantity demanded*. Possible
values:
- *virtual_available_at_date*: Planned quantity to be in stock on the
day of delivery
- *qty_available_today*: Quantity available in stock today
- *free_qty_today*: Quantity available without reserve in stock
3. Fill *Groups allowed to bypass the block*
Usage
=====
To use this module, you need to:
1. Go to Settings > Sales > Quotations & Orders > Blocking sales due to
lack of stock
2. Fill *Field to compare against the quantity demanded*: This field
will be used to check if the quantity demanded is less than or equal
to the value marked in this field. Set it to
*virtual_available_at_date* for this test.
3. Fill *Groups allowed to bypass the block*: These groups will allow
the blocking to be bypassed if the quantity demanded exceeds the
quantity we want to check. Leave it blank to not allow any group to
bypass that restriction.
4. Create a Product and set it to be storable.
5. Create a Purchase Order for the product and confirm it. Set the
Picking Schedule Date on 3 days.
6. Create a Sale Order with the product and set the Commitment Date on 2
days. Confirm it and check the wizard.
7. Click on *Ajust UoM Quantity* and see the order has been modified to
match forecasted quantity.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_block_no_stock%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Moduon
Contributors
------------
- Eduardo de Miguel (`Moduon <https://www.moduon.team/>`__)
- Rafael Blasco (`Moduon <https://www.moduon.team/>`__)
Other credits
-------------
The development of this module has been financially supported by:
- Ulzama
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
.. |maintainer-Shide| image:: https://github.com/Shide.png?size=40px
:target: https://github.com/Shide
:alt: Shide
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-Shide|
This module is part of the `OCA/sale-workflow <https://github.com/OCA/sale-workflow/tree/18.0/sale_block_no_stock>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,2 @@
from . import models
from . import wizard

View File

@@ -0,0 +1,23 @@
# Copyright 2024 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
{
"name": "Sale Block no Stock",
"summary": """Block Sales if products has not enough Quantity
based on a chosen field""",
"version": "18.0.1.0.0",
"development_status": "Alpha",
"category": "Sales/Sales",
"website": "https://github.com/OCA/sale-workflow",
"author": "Moduon, Odoo Community Association (OCA)",
"maintainers": ["Shide"],
"license": "LGPL-3",
"application": False,
"installable": True,
"depends": ["sale_stock", "mail_message_destiny_link_template"],
"data": [
"security/ir.model.access.csv",
"views/res_config_settings_views.xml",
"wizard/sale_order_block_wizard_views.xml",
],
}

314
sale_block_no_stock/i18n/es.po Executable file
View File

@@ -0,0 +1,314 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_block_no_stock
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-28 09:31+0000\n"
"PO-Revision-Date: 2024-06-28 11:32+0200\n"
"Last-Translator: \n"
"Language-Team: \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 3.4.4\n"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid ""
"<span class=\"fa fa-lg fa-building-o\" title=\"Values set here are company-"
"specific.\" groups=\"base.group_multi_company\"/>"
msgstr ""
"<span class=\"fa fa-lg fa-building-o\" title=\"Los valores configurados aquí "
"son específicos de la empresa.\" groups=\"base.group_multi_company\"/>"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Adjust Packaging Quantity"
msgstr "Ajustar cantidad Envasada"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Adjust UoM Quantity"
msgstr "Ajustar cantidad en UdM"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_company__sale_line_block_allowed_groups
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_config_settings__sale_line_block_allowed_groups
msgid "Allowed Groups to bypass the block"
msgstr "Grupos permitidos para evitar el bloqueo"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__confirmation_allowed
msgid "Allowed to confirm"
msgstr "Permitido para confirmar"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Avoid selling more product than you have in stock or planned"
msgstr "Evite vender más producto del que tiene en stock o planificado"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Blocking sales due to lack of stock"
msgstr "Bloqueo de ventas por falta de stock"
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
#, python-format
msgid "Cannot launch wizard from sale orders from different companies."
msgstr ""
"No se puede abrir el asistente para Pedidos de Venta de diferentes compañías."
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Close"
msgstr "Cerrar"
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_res_company
msgid "Companies"
msgstr "Compañías"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__company_id
msgid "Company"
msgstr "Compañía"
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_res_config_settings
msgid "Config Settings"
msgstr "Opciones de configuración"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Confirm anyway"
msgstr "Confirmar igualmente"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__create_uid
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__create_uid
msgid "Created by"
msgstr "Creado por"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__create_date
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__create_date
msgid "Created on"
msgstr "Creado el"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__display_name
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__display_name
msgid "Display Name"
msgstr "Nombre mostrado"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Field to compare against the quantity demanded"
msgstr "Campo a comparar con la cantidad demandada"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_company__sale_line_field_block
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_config_settings__sale_line_field_block
msgid "Field to compare quantities on Sale Order Lines"
msgstr "Campo para comparar cantidades en líneas de orden de venta"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Groups allowed to bypass the block"
msgstr "Grupos autorizados a saltarse el bloqueo"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__id
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__id
msgid "ID"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__is_packaging_adjustable
msgid "Is Packaging Adjustable"
msgstr "Es ajustable por Paquetes"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__is_uom_adjustable
msgid "Is Uom Adjustable"
msgstr "Es ajustable por UdM"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard____last_update
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line____last_update
msgid "Last Modified on"
msgstr "Última modificación el"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__write_uid
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__write_uid
msgid "Last Updated by"
msgstr "Última actualización por"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__write_date
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__write_date
msgid "Last Updated on"
msgstr "Última actualización el"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_packaging_allowed_max_qty
msgid "Max. Qty. (Pkg.)"
msgstr "Cant. Máx. (Env.)"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_uom_allowed_max_qty
msgid "Max. Qty. (UoM)"
msgstr "Cant. Máx. (UdM)"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Move to New Order"
msgstr "Mover a un nuevo Pedido"
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
#, python-format
msgid "Order confirmed with errors by %s."
msgstr "Pedido confirmado con errores por %s."
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_packaging_id
msgid "Packaging"
msgstr "Envase"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_id
msgid "Product"
msgstr "Producto"
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
#, python-format
msgid ""
"Product <b>%(product)s</b> adjusted from <b>%(init_qty)s</b> %(uom)s to <b>"
"%(final_qty)s</b> %(uom)s."
msgstr ""
"Producto <b>%(product)s</b> ajustado de <b>%(init_qty)s</b> %(uom)s a <b>"
"%(final_qty)s</b> %(uom)s."
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_packaging_qty
msgid "Qty. (Pkg.)"
msgstr "Cant. (Env.)"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_uom_qty
msgid "Qty. (UoM)"
msgstr "Cant. (UdM)"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__sale_line_block_ids
msgid "Sale Block Lines"
msgstr "Líneas de Venta Bloqueadas"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__sale_line_id
msgid "Sale Line"
msgstr "Línea de venta"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__order_id
msgid "Sale Order"
msgstr "Pedido de venta"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Sale Order Block"
msgstr "Bloqueo de Pedidos de Venta"
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_sale_order_block_wizard
msgid "Sale Order Block Wizard"
msgstr "Asistente de Bloqueo de Pedidos de Venta"
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_sale_order_block_wizard_line
msgid "Sale Order Block Wizard Line"
msgstr "Línea del Asistente de Bloqueo de Pedidos de Venta"
#. module: sale_block_no_stock
#: model:ir.actions.act_window,name:sale_block_no_stock.sale_order_block_wizard_action
msgid "Sale Order Blocked to Confirm"
msgstr "Pedido de Venta bloqueado en la Confirmación"
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_sale_order
msgid "Sales Order"
msgstr "Pedido de venta"
#. module: sale_block_no_stock
#: model:ir.model.fields,help:sale_block_no_stock.field_res_company__sale_line_block_allowed_groups
#: model:ir.model.fields,help:sale_block_no_stock.field_res_config_settings__sale_line_block_allowed_groups
msgid ""
"These groups will be able to bypass the block on the Sale Order Lines if the "
"quantity is not enough"
msgstr ""
"Estos grupos podrán evitar el bloqueo en las Líneas de Orden de Venta si la "
"cantidad no es suficiente"
#. module: sale_block_no_stock
#: model:ir.model.fields,help:sale_block_no_stock.field_res_company__sale_line_field_block
#: model:ir.model.fields,help:sale_block_no_stock.field_res_config_settings__sale_line_field_block
msgid ""
"This field will be checked to block the Sale Order Lines if the quantity is "
"not enough"
msgstr ""
"Este campo se comprobará para bloquear las líneas de orden de venta si la "
"cantidad no es suficiente"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid ""
"This order cannot be confirmed because it contains products that are not in "
"stock. Please manage the following products before confirming the order."
msgstr ""
"Este pedido no puede ser confirmado porque contiene productos que no están "
"en stock. Por favor, gestiona los siguientes productos antes de confirmar el "
"pedido."
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_uom
msgid "UoM"
msgstr "UdM"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__wizard_id
msgid "Wizard"
msgstr "Asistente"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "You are going to confirm the Sale Order with not solved lines"
msgstr "Vas a confirmar Pedidos de Venta con líneas sin solventar"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "You are going to move pending lines to a new order"
msgstr "Vas a mover líneas a un nuevo pedido"
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
#, python-format
msgid "You are not allowed to confirm these orders."
msgstr "No tienes permitido confirmar estos pedidos."
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "or"
msgstr "o"

313
sale_block_no_stock/i18n/it.po Executable file
View File

@@ -0,0 +1,313 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_block_no_stock
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-07-08 08:59+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.6.2\n"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid ""
"<span class=\"fa fa-lg fa-building-o\" title=\"Values set here are company-"
"specific.\" groups=\"base.group_multi_company\"/>"
msgstr ""
"<span class=\"fa fa-lg fa-building-o\" title=\"I valori impostati qui sono "
"specifici per azienda.\" groups=\"base.group_multi_company\"/>"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Adjust Packaging Quantity"
msgstr "Correzione quantità confezione"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Adjust UoM Quantity"
msgstr "Correzione UdM quantità"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_company__sale_line_block_allowed_groups
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_config_settings__sale_line_block_allowed_groups
msgid "Allowed Groups to bypass the block"
msgstr "Gruppi autorizzati a superare il blocco"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__confirmation_allowed
msgid "Allowed to confirm"
msgstr "Autorizzato alla conferma"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Avoid selling more product than you have in stock or planned"
msgstr "Evita di vendere più prodotti di quelli in giacenza o pianificati"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Blocking sales due to lack of stock"
msgstr "Blocco delle vendite per mancanza di giacenza"
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
#, python-format
msgid "Cannot launch wizard from sale orders from different companies."
msgstr ""
"Non si può avviare la procedura guidata da ordini di vendita da aziende "
"diverse."
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Close"
msgstr "Chiudi"
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_res_company
msgid "Companies"
msgstr "Aziende"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__company_id
msgid "Company"
msgstr "Azienda"
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_res_config_settings
msgid "Config Settings"
msgstr "Impostazioni configurazione"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Confirm anyway"
msgstr "Conferma comunque"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__create_uid
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__create_date
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__create_date
msgid "Created on"
msgstr "Creato il"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__display_name
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Field to compare against the quantity demanded"
msgstr "Campo ca confrontare con la quantità richiesta"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_company__sale_line_field_block
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_config_settings__sale_line_field_block
msgid "Field to compare quantities on Sale Order Lines"
msgstr "Campo per confrontare le quantità nelle righe dell'ordine di vendita"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Groups allowed to bypass the block"
msgstr "Gruppi autorizzati a superare il blocco"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__id
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__id
msgid "ID"
msgstr "ID"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__is_packaging_adjustable
msgid "Is Packaging Adjustable"
msgstr "È possibile correggere l'imballaggio"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__is_uom_adjustable
msgid "Is Uom Adjustable"
msgstr "È possibile correggere l'UdM"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard____last_update
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line____last_update
msgid "Last Modified on"
msgstr "Ultima modifica il"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__write_uid
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__write_date
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_packaging_allowed_max_qty
msgid "Max. Qty. (Pkg.)"
msgstr "Qt.à max (Imb.)"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_uom_allowed_max_qty
msgid "Max. Qty. (UoM)"
msgstr "Qt.à max (UdM)"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Move to New Order"
msgstr "Sposta a nuovo ordine"
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
#, python-format
msgid "Order confirmed with errors by %s."
msgstr "Ordine confermato con errori da %s."
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_packaging_id
msgid "Packaging"
msgstr "Imballaggio"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_id
msgid "Product"
msgstr "Prodotto"
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
#, python-format
msgid ""
"Product <b>%(product)s</b> adjusted from <b>%(init_qty)s</b> %(uom)s to <b>"
"%(final_qty)s</b> %(uom)s."
msgstr ""
"Prodotto <b>%(product)s</b> rettificato da <b>%(init_qty)s</b> %(uom)s a "
"<b>%(final_qty)s</b> %(uom)s."
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_packaging_qty
msgid "Qty. (Pkg.)"
msgstr "Qt.à (Imb.)"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_uom_qty
msgid "Qty. (UoM)"
msgstr "Qt.à (UdM)"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__sale_line_block_ids
msgid "Sale Block Lines"
msgstr "Righe vendite bloccate"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__sale_line_id
msgid "Sale Line"
msgstr "Riga di vendita"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__order_id
msgid "Sale Order"
msgstr "Ordine di vendita"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Sale Order Block"
msgstr "Blocco ordine di vendita"
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_sale_order_block_wizard
msgid "Sale Order Block Wizard"
msgstr "Procedura guidata blocco ordine di vendita"
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_sale_order_block_wizard_line
msgid "Sale Order Block Wizard Line"
msgstr "Riga procedura guidata blocco ordine di vendita"
#. module: sale_block_no_stock
#: model:ir.actions.act_window,name:sale_block_no_stock.sale_order_block_wizard_action
msgid "Sale Order Blocked to Confirm"
msgstr "Ordine di vendita bloccato da confermare"
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_sale_order
msgid "Sales Order"
msgstr "Ordine di vendita"
#. module: sale_block_no_stock
#: model:ir.model.fields,help:sale_block_no_stock.field_res_company__sale_line_block_allowed_groups
#: model:ir.model.fields,help:sale_block_no_stock.field_res_config_settings__sale_line_block_allowed_groups
msgid ""
"These groups will be able to bypass the block on the Sale Order Lines if the "
"quantity is not enough"
msgstr ""
"Questi gruppi potranno superare il blocco nelle righe ordine di vendita se "
"la quantità non è sufficiente"
#. module: sale_block_no_stock
#: model:ir.model.fields,help:sale_block_no_stock.field_res_company__sale_line_field_block
#: model:ir.model.fields,help:sale_block_no_stock.field_res_config_settings__sale_line_field_block
msgid ""
"This field will be checked to block the Sale Order Lines if the quantity is "
"not enough"
msgstr ""
"Questo campo sarà selezionato per bloccare le righe ordine di vendita se la "
"quantità non è sufficiente"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid ""
"This order cannot be confirmed because it contains products that are not in "
"stock. Please manage the following products before confirming the order."
msgstr ""
"Questo ordine non può essere confermato perché contiene prodotti che non "
"hanno giacenza. Gestire i seguenti prodotti prima di confermare l'ordine."
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_uom
msgid "UoM"
msgstr "UdM"
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__wizard_id
msgid "Wizard"
msgstr "Procedura guidata"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "You are going to confirm the Sale Order with not solved lines"
msgstr "Si sta confermando l'ordinei di vendita senza righe risolte"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "You are going to move pending lines to a new order"
msgstr "Si stanno spostando le righe in attesa in un nuovo ordine"
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
#, python-format
msgid "You are not allowed to confirm these orders."
msgstr "Non si è autorizzati a confermare questo ordine."
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "or"
msgstr "o"

276
sale_block_no_stock/i18n/pt.po Executable file
View File

@@ -0,0 +1,276 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_block_no_stock
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Adjust Packaging Quantity"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Adjust UoM Quantity"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_company__sale_line_block_allowed_groups
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_config_settings__sale_line_block_allowed_groups
msgid "Allowed Groups to bypass the block"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__confirmation_allowed
msgid "Allowed to confirm"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Avoid selling more product than you have in stock or planned"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Blocking sales due to lack of stock"
msgstr ""
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
msgid ""
"Cannot launch wizard from sale orders\n"
" from different companies."
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Close"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_res_company
msgid "Companies"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__company_id
msgid "Company"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Confirm anyway"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__create_uid
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__create_uid
msgid "Created by"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__create_date
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__create_date
msgid "Created on"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__display_name
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__display_name
msgid "Display Name"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Field to compare against the quantity demanded"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_company__sale_line_field_block
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_config_settings__sale_line_field_block
msgid "Field to compare quantities on Sale Order Lines"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Groups allowed to bypass the block"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__id
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__id
msgid "ID"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__is_packaging_adjustable
msgid "Is Packaging Adjustable"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__is_uom_adjustable
msgid "Is Uom Adjustable"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__write_uid
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__write_uid
msgid "Last Updated by"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__write_date
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__write_date
msgid "Last Updated on"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_packaging_allowed_max_qty
msgid "Max. Qty. (Pkg.)"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_uom_allowed_max_qty
msgid "Max. Qty. (UoM)"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Move to New Order"
msgstr ""
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
msgid "Order confirmed with errors by %s."
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_packaging_id
msgid "Packaging"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_id
msgid "Product"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_packaging_qty
msgid "Qty. (Pkg.)"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_uom_qty
msgid "Qty. (UoM)"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__sale_line_block_ids
msgid "Sale Block Lines"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__sale_line_id
msgid "Sale Line"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__order_id
msgid "Sale Order"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Sale Order Block"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_sale_order_block_wizard
msgid "Sale Order Block Wizard"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_sale_order_block_wizard_line
msgid "Sale Order Block Wizard Line"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.actions.act_window,name:sale_block_no_stock.sale_order_block_wizard_action
msgid "Sale Order Blocked to Confirm"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,help:sale_block_no_stock.field_res_company__sale_line_block_allowed_groups
#: model:ir.model.fields,help:sale_block_no_stock.field_res_config_settings__sale_line_block_allowed_groups
msgid ""
"These groups will be able to bypass the block on the Sale Order Lines if the"
" quantity is not enough"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,help:sale_block_no_stock.field_res_company__sale_line_field_block
#: model:ir.model.fields,help:sale_block_no_stock.field_res_config_settings__sale_line_field_block
msgid ""
"This field will be checked to block the Sale Order Lines if the quantity is "
"not enough"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid ""
"This order cannot be confirmed because it contains products that are not in "
"stock. Please manage the following products before confirming the order."
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_uom
msgid "UoM"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__wizard_id
msgid "Wizard"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "You are going to confirm the Sale Order with not solved lines"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "You are going to move pending lines to a new order"
msgstr ""
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
msgid "You are not allowed to confirm these orders."
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "or"
msgstr ""

View File

@@ -0,0 +1,275 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_block_no_stock
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Adjust Packaging Quantity"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Adjust UoM Quantity"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_company__sale_line_block_allowed_groups
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_config_settings__sale_line_block_allowed_groups
msgid "Allowed Groups to bypass the block"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__confirmation_allowed
msgid "Allowed to confirm"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Avoid selling more product than you have in stock or planned"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Blocking sales due to lack of stock"
msgstr ""
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
msgid ""
"Cannot launch wizard from sale orders\n"
" from different companies."
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Close"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_res_company
msgid "Companies"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__company_id
msgid "Company"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Confirm anyway"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__create_uid
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__create_uid
msgid "Created by"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__create_date
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__create_date
msgid "Created on"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__display_name
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__display_name
msgid "Display Name"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Field to compare against the quantity demanded"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_company__sale_line_field_block
#: model:ir.model.fields,field_description:sale_block_no_stock.field_res_config_settings__sale_line_field_block
msgid "Field to compare quantities on Sale Order Lines"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.res_config_settings_view_form
msgid "Groups allowed to bypass the block"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__id
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__id
msgid "ID"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__is_packaging_adjustable
msgid "Is Packaging Adjustable"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__is_uom_adjustable
msgid "Is Uom Adjustable"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__write_uid
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__write_uid
msgid "Last Updated by"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__write_date
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__write_date
msgid "Last Updated on"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_packaging_allowed_max_qty
msgid "Max. Qty. (Pkg.)"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_uom_allowed_max_qty
msgid "Max. Qty. (UoM)"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Move to New Order"
msgstr ""
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
msgid "Order confirmed with errors by %s."
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_packaging_id
msgid "Packaging"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_id
msgid "Product"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_packaging_qty
msgid "Qty. (Pkg.)"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_uom_qty
msgid "Qty. (UoM)"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard__sale_line_block_ids
msgid "Sale Block Lines"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__sale_line_id
msgid "Sale Line"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__order_id
msgid "Sale Order"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "Sale Order Block"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_sale_order_block_wizard
msgid "Sale Order Block Wizard"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_sale_order_block_wizard_line
msgid "Sale Order Block Wizard Line"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.actions.act_window,name:sale_block_no_stock.sale_order_block_wizard_action
msgid "Sale Order Blocked to Confirm"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model,name:sale_block_no_stock.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,help:sale_block_no_stock.field_res_company__sale_line_block_allowed_groups
#: model:ir.model.fields,help:sale_block_no_stock.field_res_config_settings__sale_line_block_allowed_groups
msgid ""
"These groups will be able to bypass the block on the Sale Order Lines if the"
" quantity is not enough"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,help:sale_block_no_stock.field_res_company__sale_line_field_block
#: model:ir.model.fields,help:sale_block_no_stock.field_res_config_settings__sale_line_field_block
msgid ""
"This field will be checked to block the Sale Order Lines if the quantity is "
"not enough"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid ""
"This order cannot be confirmed because it contains products that are not in "
"stock. Please manage the following products before confirming the order."
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__product_uom
msgid "UoM"
msgstr ""
#. module: sale_block_no_stock
#: model:ir.model.fields,field_description:sale_block_no_stock.field_sale_order_block_wizard_line__wizard_id
msgid "Wizard"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "You are going to confirm the Sale Order with not solved lines"
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "You are going to move pending lines to a new order"
msgstr ""
#. module: sale_block_no_stock
#. odoo-python
#: code:addons/sale_block_no_stock/wizard/sale_order_block_wizard.py:0
msgid "You are not allowed to confirm these orders."
msgstr ""
#. module: sale_block_no_stock
#: model_terms:ir.ui.view,arch_db:sale_block_no_stock.sale_order_block_wizard_view
msgid "or"
msgstr ""

View File

@@ -0,0 +1,3 @@
from . import res_company
from . import res_config_settings
from . import sale_order

View File

@@ -0,0 +1,33 @@
# Copyright 2024 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from odoo import fields, models
class ResCompany(models.Model):
_inherit = "res.company"
sale_line_field_block = fields.Many2one(
comodel_name="ir.model.fields",
string="Field to compare quantities on Sale Order Lines",
help="This field will be checked to block the Sale Order Lines "
"if the quantity is not enough",
domain=[
("model_id.model", "=", "sale.order.line"),
(
"name",
"in",
(
"free_qty_today",
"qty_available_today",
"virtual_available_at_date",
),
),
],
)
sale_line_block_allowed_groups = fields.Many2many(
comodel_name="res.groups",
string="Allowed Groups to bypass the block",
help="These groups will be able to bypass the block on the Sale Order Lines "
"if the quantity is not enough",
)

View File

@@ -0,0 +1,22 @@
# Copyright 2024 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
sale_line_field_block = fields.Many2one(
related="company_id.sale_line_field_block",
help="This field will be checked to block the Sale Order Lines "
"if the quantity is not enough",
readonly=False,
)
sale_line_block_allowed_groups = fields.Many2many(
related="company_id.sale_line_block_allowed_groups",
help="These groups will be able to bypass the block on the Sale Order Lines "
"if the quantity is not enough",
readonly=False,
)

View File

@@ -0,0 +1,41 @@
# Copyright 2024 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from odoo import models
class SaleOrder(models.Model):
_inherit = "sale.order"
def action_confirm(self):
"""Check the stock of the products before confirming the sale order."""
if self.env.context.get("skip_block_no_stock_check") and all(
self.env.user in company.sale_line_block_allowed_groups.users
for company in self.mapped("company_id")
):
return super().action_confirm()
for record in self:
field_to_check = record.sudo().company_id.sale_line_field_block
if not field_to_check:
continue
blocked_lines = self.env["sale.order.line"].browse()
lines = record.order_line.filtered_domain(
[("product_type", "=", "consu"), ("is_storable", "=", True)]
)
for line in lines:
if line.product_uom_qty > line[field_to_check.name]:
blocked_lines |= line
if blocked_lines:
action = self.env["ir.actions.actions"]._for_xml_id(
"sale_block_no_stock.sale_order_block_wizard_action"
)
action["context"] = {
"default_sale_line_block_ids": [
(0, 0, {"sale_line_id": line.id}) for line in blocked_lines
]
}
return action
return super().action_confirm()

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,10 @@
To configure this module, you need to:
1. Go to Settings > Sales > Quotations & Orders > Blocking sales due to lack of stock
2. Fill *Field to compare against the quantity demanded*. Possible values:
- *virtual_available_at_date*: Planned quantity to be in stock on the day of delivery
- *qty_available_today*: Quantity available in stock today
- *free_qty_today*: Quantity available without reserve in stock
3. Fill *Groups allowed to bypass the block*

View File

@@ -0,0 +1,3 @@
This module was developed because sometimes you don't want to allow a sale to be confirmed when there is no enough product to sold.
It will be useful for you if selling more than you have in stock or planned is not a desired behaviour.

View File

@@ -0,0 +1,2 @@
- Eduardo de Miguel ([Moduon](https://www.moduon.team/))
- Rafael Blasco ([Moduon](https://www.moduon.team/))

View File

@@ -0,0 +1,3 @@
The development of this module has been financially supported by:
- Ulzama

View File

@@ -0,0 +1,11 @@
This module extends the functionality of Sales to support blocking sales and to allow you to ensure you are capable to send product you have in stock.
When a Sale Order is going to be confirmed, it will be checked if the quantity demanded exceeds that of the field selected in the configuration, and in this case a wizard will appear to allow you to fix quantities indicating what is the maximum quantity that can be ordered.
Then, you can adjust UoM quantities, Packaging quantities or move remaining unfixed lines to a new order.
If the user who is confirming an order has a group that is allowed, an extra option to confirm the Order with errors will appear on the Wizard.
This module only can block lines with product type 'product' (storable products).
Internal notes will be posted on sales when adjusting quantities or moving to another orders.

View File

@@ -0,0 +1,9 @@
To use this module, you need to:
1. Go to Settings > Sales > Quotations & Orders > Blocking sales due to lack of stock
2. Fill *Field to compare against the quantity demanded*: This field will be used to check if the quantity demanded is less than or equal to the value marked in this field. Set it to *virtual_available_at_date* for this test.
3. Fill *Groups allowed to bypass the block*: These groups will allow the blocking to be bypassed if the quantity demanded exceeds the quantity we want to check. Leave it blank to not allow any group to bypass that restriction.
4. Create a Product and set it to be storable.
5. Create a Purchase Order for the product and confirm it. Set the Picking Schedule Date on 3 days.
6. Create a Sale Order with the product and set the Commitment Date on 2 days. Confirm it and check the wizard.
7. Click on *Ajust UoM Quantity* and see the order has been modified to match forecasted quantity.

View File

@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sale_order_block_wizard,access.sale.order.block.wizard,model_sale_order_block_wizard,base.group_user,1,1,1,1
access_sale_order_block_wizard_line,access.sale.order.block.wizard.line,model_sale_order_block_wizard_line,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sale_order_block_wizard access.sale.order.block.wizard model_sale_order_block_wizard base.group_user 1 1 1 1
3 access_sale_order_block_wizard_line access.sale.order.block.wizard.line model_sale_order_block_wizard_line base.group_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,506 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Sale Block no Stock</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="sale-block-no-stock">
<h1 class="title">Sale Block no Stock</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:c16ad9d6d37bb710e3897f013a183f2653b71255c3092b60129cd8f9245149bb
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Alpha" src="https://img.shields.io/badge/maturity-Alpha-red.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/sale-workflow/tree/18.0/sale_block_no_stock"><img alt="OCA/sale-workflow" src="https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/sale-workflow-18-0/sale-workflow-18-0-sale_block_no_stock"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module extends the functionality of Sales to support blocking sales
and to allow you to ensure you are capable to send product you have in
stock.</p>
<p>When a Sale Order is going to be confirmed, it will be checked if the
quantity demanded exceeds that of the field selected in the
configuration, and in this case a wizard will appear to allow you to fix
quantities indicating what is the maximum quantity that can be ordered.</p>
<p>Then, you can adjust UoM quantities, Packaging quantities or move
remaining unfixed lines to a new order.</p>
<p>If the user who is confirming an order has a group that is allowed, an
extra option to confirm the Order with errors will appear on the Wizard.</p>
<p>This module only can block lines with product type product (storable
products).</p>
<p>Internal notes will be posted on sales when adjusting quantities or
moving to another orders.</p>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
<a class="reference external" href="https://odoo-community.org/page/development-status">More details on development status</a></p>
</div>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#use-cases-context" id="toc-entry-1">Use Cases / Context</a></li>
<li><a class="reference internal" href="#configuration" id="toc-entry-2">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-3">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-7">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-8">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-9">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="use-cases-context">
<h1><a class="toc-backref" href="#toc-entry-1">Use Cases / Context</a></h1>
<p>This module was developed because sometimes you dont want to allow a
sale to be confirmed when there is no enough product to sold.</p>
<p>It will be useful for you if selling more than you have in stock or
planned is not a desired behaviour.</p>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-2">Configuration</a></h1>
<p>To configure this module, you need to:</p>
<ol class="arabic simple">
<li>Go to Settings &gt; Sales &gt; Quotations &amp; Orders &gt; Blocking sales due to
lack of stock</li>
<li>Fill <em>Field to compare against the quantity demanded</em>. Possible
values:</li>
</ol>
<ul class="simple">
<li><em>virtual_available_at_date</em>: Planned quantity to be in stock on the
day of delivery</li>
<li><em>qty_available_today</em>: Quantity available in stock today</li>
<li><em>free_qty_today</em>: Quantity available without reserve in stock</li>
</ul>
<ol class="arabic simple" start="3">
<li>Fill <em>Groups allowed to bypass the block</em></li>
</ol>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-3">Usage</a></h1>
<p>To use this module, you need to:</p>
<ol class="arabic simple">
<li>Go to Settings &gt; Sales &gt; Quotations &amp; Orders &gt; Blocking sales due to
lack of stock</li>
<li>Fill <em>Field to compare against the quantity demanded</em>: This field
will be used to check if the quantity demanded is less than or equal
to the value marked in this field. Set it to
<em>virtual_available_at_date</em> for this test.</li>
<li>Fill <em>Groups allowed to bypass the block</em>: These groups will allow
the blocking to be bypassed if the quantity demanded exceeds the
quantity we want to check. Leave it blank to not allow any group to
bypass that restriction.</li>
<li>Create a Product and set it to be storable.</li>
<li>Create a Purchase Order for the product and confirm it. Set the
Picking Schedule Date on 3 days.</li>
<li>Create a Sale Order with the product and set the Commitment Date on 2
days. Confirm it and check the wizard.</li>
<li>Click on <em>Ajust UoM Quantity</em> and see the order has been modified to
match forecasted quantity.</li>
</ol>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/sale-workflow/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_block_no_stock%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-6">Authors</a></h2>
<ul class="simple">
<li>Moduon</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-7">Contributors</a></h2>
<ul class="simple">
<li>Eduardo de Miguel (<a class="reference external" href="https://www.moduon.team/">Moduon</a>)</li>
<li>Rafael Blasco (<a class="reference external" href="https://www.moduon.team/">Moduon</a>)</li>
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#toc-entry-8">Other credits</a></h2>
<p>The development of this module has been financially supported by:</p>
<ul class="simple">
<li>Ulzama</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-9">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>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.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/Shide"><img alt="Shide" src="https://github.com/Shide.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/sale-workflow/tree/18.0/sale_block_no_stock">OCA/sale-workflow</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
from . import test_sale_block_no_stock

View File

@@ -0,0 +1,317 @@
# Copyright 2024 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from freezegun import freeze_time
from odoo.fields import Command
from odoo.tests.common import TransactionCase, new_test_user, tagged
@freeze_time("2024-01-01")
@tagged("post_install", "-at_install")
class TestSaleBlockNoStock(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.company = cls.env.user.company_id
cls.saleblock_user = new_test_user(
cls.env,
"saleblock",
"sales_team.group_sale_manager,stock.group_stock_manager,base.group_system",
company_ids=[Command.set(cls.company.ids)],
)
cls.f_qty_today = cls.env.ref(
"sale_stock.field_sale_order_line__qty_available_today"
)
cls.f_free = cls.env.ref("sale_stock.field_sale_order_line__free_qty_today")
cls.f_virtual = cls.env.ref(
"sale_stock.field_sale_order_line__virtual_available_at_date"
)
cls.partner = cls.env["res.partner"].create({"name": "Test Partner"})
cls.product = cls.env["product.product"].create(
{
"name": "Test Product",
"type": "consu",
"is_storable": True,
"uom_id": cls.env.ref("uom.product_uom_unit").id,
}
)
cls.product_packaging = cls.env["product.packaging"].create(
{
"name": "Half Unit",
"product_id": cls.product.id,
"qty": 0.5,
}
)
cls.quant = cls.env["stock.quant"].create(
{
"product_id": cls.product.id,
"location_id": cls.env.ref("stock.stock_location_stock").id,
"quantity": 1,
"company_id": cls.company.id,
}
)
cls.sale = cls.env["sale.order"].create(
{
"state": "draft",
"company_id": cls.company.id,
"partner_id": cls.partner.id,
"commitment_date": "2024-01-01",
"user_id": cls.saleblock_user.id,
"order_line": [
(
0,
0,
{
"product_id": cls.product.id,
"product_uom_qty": 1,
"product_uom": cls.env.ref("uom.product_uom_dozen").id,
},
)
],
}
)
cls.in_picking = cls.env["stock.picking"].create(
{
"picking_type_id": cls.env.ref("stock.picking_type_in").id,
"company_id": cls.company.id,
"location_id": cls.env.ref("stock.stock_location_suppliers").id,
"location_dest_id": cls.env.ref("stock.stock_location_stock").id,
"scheduled_date": "2024-01-05",
"state": "assigned",
"move_ids": [
(
0,
0,
{
"name": "Test Move",
"product_id": cls.product.id,
"product_uom_qty": 11.0,
"product_uom": cls.product.uom_id.id,
"location_id": cls.env.ref(
"stock.stock_location_suppliers"
).id,
"location_dest_id": cls.env.ref(
"stock.stock_location_stock"
).id,
},
)
],
}
)
cls.in_picking.move_ids[0].quantity = 11.0
def _get_wizard(self, wizard_user, wizard_action):
"""Returns a new wizard instance from the given action."""
self.assertEqual(isinstance(wizard_action, dict), True)
self.assertEqual(wizard_action["res_model"], "sale.order.block.wizard")
wiz_vals = (
self.env[wizard_action["res_model"]]
.with_context(**wizard_action["context"])
.default_get(["sale_line_block_ids"])
)
return (
self.env[wizard_action["res_model"]]
.with_user(wizard_user.id)
.with_context(uid=wizard_user.id)
.create(wiz_vals)
)
def test_sale_blocking_qty_available_today(self):
"""Test Sale Order Blocking with Quantity Available Today."""
self.company.sale_line_block_allowed_groups = False
self.company.sale_line_field_block = self.f_qty_today
# Block: No 1 Dozen in stock
wizard = self._get_wizard(
self.saleblock_user,
self.sale.with_user(self.saleblock_user.id).action_confirm(),
)
self.assertEqual(len(wizard.sale_line_block_ids), 1)
# Block: No 2 Units in stock
self.sale.order_line[0].product_uom_qty = 2
self.sale.order_line[0].product_uom = self.env.ref("uom.product_uom_unit").id
wizard = self._get_wizard(
self.saleblock_user,
self.sale.with_user(self.saleblock_user.id).action_confirm(),
)
self.assertEqual(len(wizard.sale_line_block_ids), 1)
self.assertFalse(wizard.confirmation_allowed)
def test_sale_blocking_free_qty_today(self):
"""Test Sale Order Blocking with Free Quantity Available Today."""
self.company.sale_line_block_allowed_groups = False
self.company.sale_line_field_block = self.f_free
# Change quantities to 1 Unit
self.sale.order_line[0].product_uom_qty = 1
self.sale.order_line[0].product_uom = self.env.ref("uom.product_uom_unit").id
# Confirm sale and create a new Sale to check reserved quantity
self.sale.with_user(self.saleblock_user.id).action_confirm()
self.assertNotEqual(self.sale.state, "draft")
self.sale.picking_ids.action_confirm()
new_sale = self.env["sale.order"].create(
{
"state": "draft",
"company_id": self.company.id,
"partner_id": self.partner.id,
"commitment_date": "2024-01-01",
"user_id": self.saleblock_user.id,
"order_line": [
(
0,
0,
{
"product_id": self.product.id,
"product_uom_qty": 1,
"product_uom": self.env.ref("uom.product_uom_dozen").id,
},
)
],
}
)
# Block: No unreserved 1 Unit on 2024-01-01
wizard = self._get_wizard(
self.saleblock_user,
new_sale.with_user(self.saleblock_user.id).action_confirm(),
)
self.assertEqual(len(wizard.sale_line_block_ids), 1)
self.assertFalse(wizard.confirmation_allowed)
def test_sale_blocking_virtual_available_at_date(self):
"""Test Sale Order Blocking with Virtual Available at Date (Forecast)."""
self.company.sale_line_block_allowed_groups = False
self.company.sale_line_field_block = self.f_virtual
# Block: No 1 Dozen on 2024-01-01
wizard = self._get_wizard(
self.saleblock_user,
self.sale.with_user(self.saleblock_user.id).action_confirm(),
)
self.assertEqual(len(wizard.sale_line_block_ids), 1)
self.assertFalse(wizard.confirmation_allowed)
self.sale.commitment_date = "2024-01-05"
self.in_picking.action_assign()
# No Block: 1 Dozen on 2024-01-05
self.sale.with_user(self.saleblock_user.id).action_confirm()
self.assertNotEqual(self.sale.state, "draft")
def test_sale_blocking_allowed_groups(self):
"""Test Sale Order Blocking with Allowed Groups."""
self.company.sale_line_block_allowed_groups = [
(6, 0, self.env.ref("sales_team.group_sale_manager").ids)
]
self.company.sale_line_field_block = self.f_qty_today
# No Block: No 1 Dozen on 2024-01-01 + allowed groups
wizard = self._get_wizard(
self.saleblock_user,
self.sale.with_user(self.saleblock_user.id).action_confirm(),
)
self.assertEqual(len(wizard.sale_line_block_ids), 1)
self.assertTrue(wizard.confirmation_allowed)
wizard.with_user(self.saleblock_user.id).confirm()
self.assertNotEqual(self.sale.state, "draft")
def test_sale_blocking_not_allowed_groups(self):
"""Test Sale Order Blocking with Not Allowed Groups."""
self.company.sale_line_block_allowed_groups = False
self.company.sale_line_field_block = self.f_qty_today
# No Block: No 1 Dozen on 2024-01-01 + not allowed groups
wizard = self._get_wizard(
self.saleblock_user,
self.sale.with_user(self.saleblock_user.id).action_confirm(),
)
self.assertEqual(len(wizard.sale_line_block_ids), 1)
self.assertFalse(wizard.confirmation_allowed)
def test_sale_adjust_uom_quantity(self):
"""Test Wizard Adjusting Quantity."""
self.company.sale_line_block_allowed_groups = False
self.company.sale_line_field_block = self.f_qty_today
# Block: No 2 Units in stock
self.sale.order_line[0].product_uom_qty = 2
self.sale.order_line[0].product_uom = self.env.ref("uom.product_uom_unit").id
wizard = self._get_wizard(
self.saleblock_user,
self.sale.with_user(self.saleblock_user.id).action_confirm(),
)
self.assertEqual(len(wizard.sale_line_block_ids), 1)
# Wizard: Adjust Quantities to 1 Unit
wizard.action_adjust_uom_quantity()
self.assertEqual(self.sale.order_line[0].product_uom_qty, 1)
# No Block: 1 Unit in stock
self.sale.with_user(self.saleblock_user.id).action_confirm()
self.assertNotEqual(self.sale.state, "draft")
# Check messages
self.assertTrue(
any(
[
"adjusted from" in body
for body in self.sale.message_ids.mapped("body")
]
)
)
def test_sale_adjust_packaging_quantity(self):
"""Test Wizard Adjusting Packaging Quantity."""
self.company.sale_line_block_allowed_groups = False
self.company.sale_line_field_block = self.f_qty_today
# Block: No 2 Units in stock (4 Packagings)
self.sale.order_line[0].product_uom = self.env.ref("uom.product_uom_unit").id
self.sale.order_line[0].product_uom_qty = 4 * self.product_packaging.qty
self.sale.order_line[0].product_packaging_id = self.product_packaging.id
self.sale.order_line[0].product_packaging_qty = 4
wizard = self._get_wizard(
self.saleblock_user,
self.sale.with_user(self.saleblock_user.id).action_confirm(),
)
self.assertEqual(len(wizard.sale_line_block_ids), 1)
# Wizard: Adjust Packaging Quantities
wizard.action_adjust_packaging_quantity()
self.assertEqual(self.sale.order_line[0].product_uom_qty, 1)
self.assertEqual(self.sale.order_line[0].product_packaging_qty, 2)
# No Block: 1 Unit (2 Packagings) in stock
self.sale.with_user(self.saleblock_user.id).action_confirm()
self.assertNotEqual(self.sale.state, "draft")
# Check messages
self.assertTrue(
any(
[
"adjusted from" in body
for body in self.sale.message_ids.mapped("body")
]
)
)
def test_sale_move_to_new_order(self):
"""Test Wizard Moving to New Order."""
self.company.sale_line_block_allowed_groups = False
self.company.sale_line_field_block = self.f_qty_today
# Block: No 4 Units in stock
self.sale.order_line[0].product_uom = self.env.ref("uom.product_uom_unit").id
self.sale.order_line[0].product_uom_qty = 4
wizard = self._get_wizard(
self.saleblock_user,
self.sale.with_user(self.saleblock_user.id).action_confirm(),
)
self.assertEqual(len(wizard.sale_line_block_ids), 1)
# Wizard: Move to new order
new_orders = wizard.action_move_to_new_order()
self.assertEqual(len(self.sale.order_line), 0)
self.assertEqual(len(new_orders.order_line), 1)
# Check messages
self.assertTrue(
any(
[
"This sales order has created" in body
for body in self.sale.message_ids.mapped("body")
]
)
)
self.assertTrue(
any(
[
"This sales order has been modified from" in body
for body in new_orders.message_ids.mapped("body")
]
)
)

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.sale.block</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="sale.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//setting[@id='no_edit_order']" position="after">
<setting
id="sale_line_field_block"
string="Blocking sales due to lack of stock"
help="Avoid selling more product than you have in stock or planned"
company_dependent="1"
>
<div class="content-group">
<div class="mt-2">
<label
for="sale_line_field_block"
string="Field to compare against the quantity demanded"
class="w-100"
/>
<field
name="sale_line_field_block"
options="{'no_create': True, 'no_open': True}"
class="w-75"
/>
</div>
<div class="mt-1">
<label
for="sale_line_block_allowed_groups"
string="Groups allowed to bypass the block"
class="w-100"
/>
<field
name="sale_line_block_allowed_groups"
widget="many2many_tags"
options="{'no_create': True, 'no_open': True}"
class="w-100"
/>
</div>
</div>
</setting>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
from . import sale_order_block_wizard

View File

@@ -0,0 +1,312 @@
from markupsafe import Markup
from odoo import _, api, exceptions, fields, models
from odoo.tools import groupby
class SaleOrderBlockWizard(models.TransientModel):
_name = "sale.order.block.wizard"
_description = "Sale Order Block Wizard"
_transient_max_hours = 0.25 # 15 minutes until destroyed
sale_line_block_ids = fields.One2many(
comodel_name="sale.order.block.wizard.line",
inverse_name="wizard_id",
string="Sale Block Lines",
)
confirmation_allowed = fields.Boolean(
string="Allowed to confirm",
compute="_compute_confirmation_allowed",
)
is_uom_adjustable = fields.Boolean(
compute="_compute_is_adjustable",
store=True,
readonly=True,
compute_sudo=True,
)
is_packaging_adjustable = fields.Boolean(
compute="_compute_is_adjustable",
store=True,
readonly=True,
compute_sudo=True,
)
@api.depends_context("uid")
@api.depends("sale_line_block_ids.company_id")
def _compute_confirmation_allowed(self):
"""Compute if the user is allowed to confirm the sale orders."""
self.confirmation_allowed = (
self.env.user
in self.sale_line_block_ids.company_id.sale_line_block_allowed_groups.users
)
@api.depends(
"sale_line_block_ids.product_packaging_allowed_max_qty",
"sale_line_block_ids.product_uom_allowed_max_qty",
)
def _compute_is_adjustable(self):
"""Compute if the sale lines are adjustable."""
for record in self:
lines = record.mapped("sale_line_block_ids")
record.is_packaging_adjustable = bool(
lines.filtered(
lambda line: line.product_packaging_allowed_max_qty > 0.0
)
)
record.is_uom_adjustable = bool(
lines.filtered(lambda line: line.product_uom_allowed_max_qty > 0.0)
)
def confirm(self):
"""Confirm the sale orders ignoring next possible wizards."""
if not all(self.mapped("confirmation_allowed")):
raise exceptions.UserError(
_("You are not allowed to confirm these orders.")
)
orders = self.mapped("sale_line_block_ids.order_id")
orders.message_post(
body=_("Order confirmed with errors by %s.", self.env.user.name),
subtype_id=self.env.ref("mail.mt_note").id,
)
return orders.with_context(skip_block_no_stock_check=True).action_confirm()
def action_adjust_uom_quantity(self):
"""Adjust the quantity of the sale lines to the maximum allowed by the
UoM."""
return self.sale_line_block_ids._action_adjust_uom_quantity()
def action_adjust_packaging_quantity(self):
"""Adjust the quantity of the sale lines to the maximum allowed by the
packaging."""
return self.sale_line_block_ids._action_adjust_packaging_quantity()
def action_move_to_new_order(self):
"""Move the sale lines to a new sale order."""
return self.sale_line_block_ids._action_move_to_new_order()
@api.constrains("sale_line_block_ids")
def _check_sale_line_block_ids(self):
"""Check that all sale lines are from the same company."""
for record in self:
companies = record.mapped(
"sale_line_block_ids.sale_line_id.order_id.company_id"
)
if len(companies) > 1:
raise exceptions.UserError(
_(
"""Cannot launch wizard from sale orders
from different companies."""
)
)
class SaleOrderBlockWizardLine(models.TransientModel):
_name = "sale.order.block.wizard.line"
_description = "Sale Order Block Wizard Line"
_transient_max_hours = 0.25 # 15 minutes until destroyed
wizard_id = fields.Many2one(
comodel_name="sale.order.block.wizard",
string="Wizard",
required=True,
)
sale_line_id = fields.Many2one(
comodel_name="sale.order.line",
string="Sale Line",
required=True,
)
company_id = fields.Many2one(
related="sale_line_id.order_id.company_id",
string="Company",
readonly=True,
store=True,
)
order_id = fields.Many2one(
related="sale_line_id.order_id",
string="Sale Order",
readonly=True,
store=True,
)
product_id = fields.Many2one(
related="sale_line_id.product_id",
string="Product",
readonly=True,
store=True,
)
product_uom_qty = fields.Float(
related="sale_line_id.product_uom_qty",
string="Qty. (UoM)",
readonly=True,
)
product_uom = fields.Many2one(
related="sale_line_id.product_uom",
string="UoM",
readonly=True,
store=True,
)
product_uom_allowed_max_qty = fields.Float(
string="Max. Qty. (UoM)",
compute="_compute_allowed_max_qty",
readonly=True,
store=True,
compute_sudo=True,
)
product_packaging_qty = fields.Float(
related="sale_line_id.product_packaging_qty",
string="Qty. (Pkg.)",
readonly=True,
store=True,
)
product_packaging_id = fields.Many2one(
related="sale_line_id.product_packaging_id",
string="Packaging",
readonly=True,
store=True,
)
product_packaging_allowed_max_qty = fields.Float(
string="Max. Qty. (Pkg.)",
compute="_compute_allowed_max_qty",
readonly=True,
store=True,
compute_sudo=True,
)
@api.depends("sale_line_id", "product_uom_qty", "company_id.sale_line_field_block")
def _compute_allowed_max_qty(self):
"""Compute the maximum allowed quantity by UoM and Packaging of
storable products."""
self.product_uom_allowed_max_qty = 0.0
self.product_packaging_allowed_max_qty = 0.0
for record in self:
field_to_check = record.company_id.sale_line_field_block
if not field_to_check:
self.env.cr.postcommit.add(record.unlink)
continue
if (
record.sale_line_id.product_type != "consu"
and record.sale_line_id.product_id.is_storable
):
self.env.cr.postcommit.add(record.unlink)
continue
allowed_max_qty = record.sale_line_id[field_to_check.name]
if (
allowed_max_qty > 0
and record.sale_line_id.product_uom_qty <= allowed_max_qty
):
self.env.cr.postcommit.add(record.unlink)
continue
record.product_uom_allowed_max_qty = allowed_max_qty
if record.product_packaging_id:
record.product_packaging_allowed_max_qty = (
allowed_max_qty // record.product_packaging_id.qty
)
def _get_adjustable_records(self, packaging=False):
"""Return the records that can be adjusted by UoM or Packaging."""
if packaging:
return self.filtered(
lambda r: r.product_packaging_allowed_max_qty > 0.0
and r.product_packaging_qty > r.product_packaging_allowed_max_qty
)
return self.filtered(
lambda r: r.product_uom_allowed_max_qty > 0.0
and r.product_uom_qty > r.product_uom_allowed_max_qty
)
def _get_reopen_action(self):
"""Return the action to reopen the wizard."""
action = self.env["ir.actions.actions"]._for_xml_id(
"sale_block_no_stock.sale_order_block_wizard_action"
)
action["context"] = {
"default_sale_line_block_ids": [
(0, 0, {"sale_line_id": line.id})
for line in self.mapped("sale_line_id")
]
}
return action
def _action_move_to_new_order(self):
"""Move the sale lines to a new sale order."""
mt_note_id = self.env.ref("mail.mt_note").id
partner_id = self.env.user.partner_id.id
new_orders = self.env["sale.order"].browse()
for order, records in groupby(self, lambda r: r.order_id):
new_order = order.copy(default={"order_line": None})
new_order.message_post_with_source(
"mail.message_origin_link",
render_values={"self": new_order, "origin": order, "edit": True},
subtype_id=mt_note_id,
author_id=partner_id,
)
order.message_post_with_source(
"mail_message_destiny_link_template.message_destiny_link",
render_values={"self": order, "destiny": new_order, "edit": False},
subtype_id=mt_note_id,
author_id=partner_id,
)
for record in records:
record.sale_line_id.write({"order_id": new_order.id})
new_orders |= new_order
return new_orders
@api.model
def _get_adjusted_message(self, product, init_qty, final_qty, uom):
message = Markup(
"""
Product <b>%(product)s</b> adjusted from
<b>%(init_qty)s</b> %(uom)s to <b>%(final_qty)s</b> %(uom)s.
"""
) % {
"product": product,
"init_qty": init_qty,
"final_qty": final_qty,
"uom": uom,
}
return message
def _action_adjust_uom_quantity(self):
"""Adjust the quantity of the sale lines to the maximum allowed by the
UoM."""
mt_note_id = self.env.ref("mail.mt_note").id
adjustable_records = self._get_adjustable_records()
for record in adjustable_records:
record.sale_line_id.order_id.message_post(
body=self._get_adjusted_message(
product=record.product_id.display_name,
init_qty=record.product_uom_qty,
final_qty=record.product_uom_allowed_max_qty,
uom=record.product_uom.name,
),
subtype_id=mt_note_id,
)
record.sale_line_id.product_uom_qty = record.product_uom_allowed_max_qty
if not self - adjustable_records:
return
return (self - adjustable_records)._get_reopen_action()
def _action_adjust_packaging_quantity(self):
"""Adjust the quantity of the sale lines to the maximum allowed by the
packaging."""
mt_note_id = self.env.ref("mail.mt_note").id
adjustable_records = self._get_adjustable_records(packaging=True)
for record in adjustable_records:
record.sale_line_id.order_id.message_post(
body=self._get_adjusted_message(
product=record.product_id.display_name,
init_qty=record.product_packaging_qty,
final_qty=record.product_packaging_allowed_max_qty,
uom=record.product_packaging_id.display_name,
),
subtype_id=mt_note_id,
)
record.sale_line_id.product_packaging_qty = (
record.product_packaging_allowed_max_qty
)
record.sale_line_id.product_uom_qty = (
record.product_packaging_id.qty
* record.product_packaging_allowed_max_qty
)
if not self - adjustable_records:
return
return (self - adjustable_records)._get_reopen_action()

View File

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="sale_order_block_wizard_view" model="ir.ui.view">
<field name="name">Sale Order Block</field>
<field name="model">sale.order.block.wizard</field>
<field name="arch" type="xml">
<form string="Sale Order Block">
<field name="confirmation_allowed" invisible="1" />
<field name="is_uom_adjustable" invisible="1" />
<field name="is_packaging_adjustable" invisible="1" />
<p class="text-muted">
This order cannot be confirmed because it contains products that are not in stock. Please manage the following products before confirming the order.
</p>
<group
name="blocked_lines_group"
staction_create_sale_orderring="Blocked lines"
>
<field name="sale_line_block_ids" nolabel="1" colspan="2">
<list
decoration-danger="product_uom_allowed_max_qty &lt;= 0.0"
create="false"
edit="false"
no_open="true"
>
<field name="wizard_id" column_invisible="1" />
<field name="sale_line_id" column_invisible="1" />
<field name="company_id" column_invisible="1" />
<field name="order_id" optional="hide" />
<field name="product_id" />
<field name="product_uom_qty" optional="show" />
<field name="product_uom_allowed_max_qty" optional="show" />
<field name="product_uom" optional="show" />
<field
name="product_packaging_qty"
optional="show"
invisible="not product_packaging_id"
groups="product.group_stock_packaging"
/>
<field
name="product_packaging_allowed_max_qty"
optional="show"
invisible="not product_packaging_id"
groups="product.group_stock_packaging"
/>
<field
name="product_packaging_id"
optional="show"
invisible="not product_packaging_id"
groups="product.group_stock_packaging"
/>
</list>
</field>
</group>
<footer>
<button
name="action_adjust_uom_quantity"
string="Adjust UoM Quantity"
type="object"
class="btn-primary"
invisible="not is_uom_adjustable or is_packaging_adjustable or not sale_line_block_ids"
/>
<button
name="action_adjust_packaging_quantity"
string="Adjust Packaging Quantity"
type="object"
class="btn-primary"
invisible="not(is_packaging_adjustable and sale_line_block_ids)"
groups="product.group_stock_packaging"
/>
<button
name="action_move_to_new_order"
string="Move to New Order"
type="object"
class="btn-primary"
invisible="is_uom_adjustable or not sale_line_block_ids"
confirm="You are going to move pending lines to a new order"
/>
<button
name="action_move_to_new_order"
string="Move to New Order"
type="object"
class="btn-secondary"
invisible="not(is_uom_adjustable and sale_line_block_ids)"
confirm="You are going to move pending lines to a new order"
/>
<button
name="confirm"
string="Confirm anyway"
type="object"
class="btn-secondary"
invisible="not(confirmation_allowed and sale_line_block_ids)"
confirm="You are going to confirm the Sale Order with not solved lines"
/>
<p class="m-2">or</p>
<button string="Close" class="btn-secondary" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="sale_order_block_wizard_action" model="ir.actions.act_window">
<field name="name">Sale Order Blocked to Confirm</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sale.order.block.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>