diff --git a/README.md b/README.md index c505822d9a5..17cbc0d29f3 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ addon | version | maintainers | summary [stock_release_channel_show_volume](stock_release_channel_show_volume/) | 16.0.1.1.0 | | Display volumes of stock release channels [stock_release_channel_show_weight](stock_release_channel_show_weight/) | 16.0.1.1.0 | | Display weights of stock release channels [stock_release_channel_warehouse_calendar](stock_release_channel_warehouse_calendar/) | 16.0.1.0.0 | jbaudoux | Glue module between release channel and warehouse calendar -[stock_storage_type](stock_storage_type/) | 16.0.2.0.3 | jbaudoux rousseldenis | Manage packages and locations storage types +[stock_storage_type](stock_storage_type/) | 16.0.2.1.0 | jbaudoux rousseldenis | Manage packages and locations storage types [stock_storage_type_putaway_abc](stock_storage_type_putaway_abc/) | 16.0.1.0.0 | | Advanced storage strategy ABC for WMS [stock_warehouse_flow](stock_warehouse_flow/) | 16.0.1.1.0 | | Configure routing flow for stock moves [stock_warehouse_flow_delivery_refresh](stock_warehouse_flow_delivery_refresh/) | 16.0.1.0.0 | | Allow to refresh delivery flow when carrier changes diff --git a/stock_storage_type/README.rst b/stock_storage_type/README.rst index e679aad239b..9e17c82b3ee 100644 --- a/stock_storage_type/README.rst +++ b/stock_storage_type/README.rst @@ -11,7 +11,7 @@ Stock Storage Type !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:c46ce97684eda7baf75edac00f005ea18baac91d81a4f0df32ba6f49ce1b34ee + !! source digest: sha256:ad18c92cb64972423bce045cb542b198d72561d86c746e5db7e7ccc9962cbec5 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/stock_storage_type/__manifest__.py b/stock_storage_type/__manifest__.py index 03e0ce4b10f..c1208bcbf81 100644 --- a/stock_storage_type/__manifest__.py +++ b/stock_storage_type/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Stock Storage Type", "summary": "Manage packages and locations storage types", - "version": "16.0.2.0.3", + "version": "16.0.2.1.0", "development_status": "Beta", "category": "Warehouse Management", "website": "https://github.com/OCA/wms", @@ -14,6 +14,7 @@ "application": False, "installable": True, "depends": [ + "stock_location_fill_state", "stock_move_line_reserved_quant", "stock_putaway_hook", "stock_quant_package_dimension", diff --git a/stock_storage_type/i18n/de.po b/stock_storage_type/i18n/de.po index f1f6fdef903..60e59921386 100644 --- a/stock_storage_type/i18n/de.po +++ b/stock_storage_type/i18n/de.po @@ -205,6 +205,16 @@ msgstr "" msgid "Execute code" msgstr "" +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "Has Potential Lot Mix Exception" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "Has Potential Product Mix Exception" +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__has_restrictions msgid "Has Restrictions" @@ -292,11 +302,6 @@ msgstr "" msgid "Location" msgstr "" -#. module: stock_storage_type -#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_is_empty -msgid "Location Is Empty" -msgstr "" - #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_lot_ids msgid "Location Will Contain Lot" @@ -530,33 +535,6 @@ msgid "" "heavier: {weight_kg}." msgstr "" -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix lots' but there are other " -"lots in location." -msgstr "" - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix products' but there are " -"other products in location." -msgstr "" - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'only empty' with other quants in " -"location." -msgstr "" - #. module: stock_storage_type #: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu msgid "Storage Location Sequence Conditions" @@ -623,6 +601,22 @@ msgid "" "the category set on the location or on one of its parent." msgstr "" +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different product lots and the location doesnot allow mixed " +"lots." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different products and the location doesnot allow mixed " +"products." +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id msgid "UoM for height" @@ -664,14 +658,6 @@ msgstr "" msgid "code_snippet should return boolean value into `result` variable." msgstr "" -#. module: stock_storage_type -#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_is_empty -msgid "" -"technical field: True if the location is empty and there is no pending " -"incoming products in the location. Computed only if the location needs to " -"check for emptiness (has an \"only empty\" policy)." -msgstr "" - #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_location_ids msgid "technical field: all the leaves locations" diff --git a/stock_storage_type/i18n/es.po b/stock_storage_type/i18n/es.po index de3769f093e..64a9f061bb5 100644 --- a/stock_storage_type/i18n/es.po +++ b/stock_storage_type/i18n/es.po @@ -211,6 +211,16 @@ msgstr "No Mezclar Productos" msgid "Execute code" msgstr "Ejecutar código" +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "Has Potential Lot Mix Exception" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "Has Potential Product Mix Exception" +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__has_restrictions msgid "Has Restrictions" @@ -298,11 +308,6 @@ msgstr "Ubicación de la hoja" msgid "Location" msgstr "Localización" -#. module: stock_storage_type -#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_is_empty -msgid "Location Is Empty" -msgstr "La ubicación está vacía" - #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_lot_ids msgid "Location Will Contain Lot" @@ -537,33 +542,6 @@ msgid "" "heavier: {weight_kg}." msgstr "" -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix lots' but there are other " -"lots in location." -msgstr "" - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix products' but there are " -"other products in location." -msgstr "" - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'only empty' with other quants in " -"location." -msgstr "" - #. module: stock_storage_type #: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu msgid "Storage Location Sequence Conditions" @@ -634,6 +612,22 @@ msgstr "" "Representa la categoría de almacenamiento que se utilizará. Depende de la " "categoría establecida en la ubicación o en uno de sus padres." +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different product lots and the location doesnot allow mixed " +"lots." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different products and the location doesnot allow mixed " +"products." +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id msgid "UoM for height" @@ -675,14 +669,6 @@ msgstr "" msgid "code_snippet should return boolean value into `result` variable." msgstr "code_snippet debe devolver un valor booleano en la variable `result`." -#. module: stock_storage_type -#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_is_empty -msgid "" -"technical field: True if the location is empty and there is no pending " -"incoming products in the location. Computed only if the location needs to " -"check for emptiness (has an \"only empty\" policy)." -msgstr "" - #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_location_ids msgid "technical field: all the leaves locations" @@ -728,6 +714,9 @@ msgstr "" msgid "technical field: the pending outgoing stock.move.lines in the location" msgstr "campo técnico: las líneas de salida pendientes en la ubicación" +#~ msgid "Location Is Empty" +#~ msgstr "La ubicación está vacía" + #, python-format #~ msgid "" #~ " * {location} (WARNING: restrictions are " diff --git a/stock_storage_type/i18n/es_AR.po b/stock_storage_type/i18n/es_AR.po index b9926cb8cc5..e426b755069 100644 --- a/stock_storage_type/i18n/es_AR.po +++ b/stock_storage_type/i18n/es_AR.po @@ -207,6 +207,16 @@ msgstr "No Mezcla Productos" msgid "Execute code" msgstr "Ejecutar código" +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "Has Potential Lot Mix Exception" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "Has Potential Product Mix Exception" +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__has_restrictions msgid "Has Restrictions" @@ -294,11 +304,6 @@ msgstr "Ubicación Hoja" msgid "Location" msgstr "Ubicación" -#. module: stock_storage_type -#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_is_empty -msgid "Location Is Empty" -msgstr "La Ubicación está Vacía" - #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_lot_ids msgid "Location Will Contain Lot" @@ -534,33 +539,6 @@ msgid "" "heavier: {weight_kg}." msgstr "" -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix lots' but there are other " -"lots in location." -msgstr "" - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix products' but there are " -"other products in location." -msgstr "" - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'only empty' with other quants in " -"location." -msgstr "" - #. module: stock_storage_type #: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu msgid "Storage Location Sequence Conditions" @@ -627,6 +605,22 @@ msgid "" "the category set on the location or on one of its parent." msgstr "" +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different product lots and the location doesnot allow mixed " +"lots." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different products and the location doesnot allow mixed " +"products." +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id msgid "UoM for height" @@ -668,14 +662,6 @@ msgstr "" msgid "code_snippet should return boolean value into `result` variable." msgstr "code_snippet debe devolver un valor booleano en la variable `result`." -#. module: stock_storage_type -#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_is_empty -msgid "" -"technical field: True if the location is empty and there is no pending " -"incoming products in the location. Computed only if the location needs to " -"check for emptiness (has an \"only empty\" policy)." -msgstr "" - #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_location_ids msgid "technical field: all the leaves locations" @@ -717,6 +703,9 @@ msgstr "campo técnico: stock.moves de entrada pendientes en la ubicación" msgid "technical field: the pending outgoing stock.move.lines in the location" msgstr "campo técnico: stock.move.lines de salida pendientes en la ubicación" +#~ msgid "Location Is Empty" +#~ msgstr "La Ubicación está Vacía" + #~ msgid "Height is mandatory for packages configured with this storage type." #~ msgstr "" #~ "La Altura es obligatoria para paquetes configurados con este tipo de " diff --git a/stock_storage_type/i18n/fr.po b/stock_storage_type/i18n/fr.po index e962c88b90c..c07754001f6 100644 --- a/stock_storage_type/i18n/fr.po +++ b/stock_storage_type/i18n/fr.po @@ -207,6 +207,16 @@ msgstr "" msgid "Execute code" msgstr "" +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "Has Potential Lot Mix Exception" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "Has Potential Product Mix Exception" +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__has_restrictions msgid "Has Restrictions" @@ -294,11 +304,6 @@ msgstr "" msgid "Location" msgstr "" -#. module: stock_storage_type -#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_is_empty -msgid "Location Is Empty" -msgstr "" - #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_lot_ids msgid "Location Will Contain Lot" @@ -532,33 +537,6 @@ msgid "" "heavier: {weight_kg}." msgstr "" -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix lots' but there are other " -"lots in location." -msgstr "" - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix products' but there are " -"other products in location." -msgstr "" - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'only empty' with other quants in " -"location." -msgstr "" - #. module: stock_storage_type #: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu msgid "Storage Location Sequence Conditions" @@ -625,6 +603,22 @@ msgid "" "the category set on the location or on one of its parent." msgstr "" +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different product lots and the location doesnot allow mixed " +"lots." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different products and the location doesnot allow mixed " +"products." +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id msgid "UoM for height" @@ -666,14 +660,6 @@ msgstr "" msgid "code_snippet should return boolean value into `result` variable." msgstr "" -#. module: stock_storage_type -#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_is_empty -msgid "" -"technical field: True if the location is empty and there is no pending " -"incoming products in the location. Computed only if the location needs to " -"check for emptiness (has an \"only empty\" policy)." -msgstr "" - #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_location_ids msgid "technical field: all the leaves locations" diff --git a/stock_storage_type/i18n/it.po b/stock_storage_type/i18n/it.po index 2d7e271d093..8ba81803ccc 100644 --- a/stock_storage_type/i18n/it.po +++ b/stock_storage_type/i18n/it.po @@ -221,6 +221,16 @@ msgstr "Non mescolare prodotti" msgid "Execute code" msgstr "Esegui codice" +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "Has Potential Lot Mix Exception" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "Has Potential Product Mix Exception" +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__has_restrictions msgid "Has Restrictions" @@ -309,11 +319,6 @@ msgstr "Ubicazione terminale" msgid "Location" msgstr "Ubicazione" -#. module: stock_storage_type -#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_is_empty -msgid "Location Is Empty" -msgstr "Ubicazione vuota" - #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_lot_ids msgid "Location Will Contain Lot" @@ -560,39 +565,6 @@ msgstr "" "La categoria stoccaggio {category} ha un peso massimo di {max_w} ma il collo " "è più pesante: {weight_kg}." -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix lots' but there are other " -"lots in location." -msgstr "" -"La categoria stoccaggio {category} è impostata a 'non mescolare i lotti' ma " -"ci sono altri lotti nell'ubicazione." - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix products' but there are " -"other products in location." -msgstr "" -"La categoria stoccaggio {category} è impostata a 'non mescolare i prodotti' " -"ma ci sono altri prodotti nell'ubicazione." - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'only empty' with other quants in " -"location." -msgstr "" -"La categoria stoccaggio {category} è impostata a 'solo vuote' con altri " -"quanti nell'ubicazione." - #. module: stock_storage_type #: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu msgid "Storage Location Sequence Conditions" @@ -670,6 +642,22 @@ msgstr "" "Questo rappresenta la categoria stoccaggio che verrà utilizzata. Dipende " "dalla categoria impostata nell'ubicazione o in uno dei suoi padri." +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different product lots and the location doesnot allow mixed " +"lots." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different products and the location doesnot allow mixed " +"products." +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id msgid "UoM for height" @@ -703,9 +691,9 @@ msgid "" "away is applied." msgstr "" "Quando i colli con tipo {name} sono depositati, la strategia cerca una " -"ubicazione disponibile nelle seguenti ubicazioni:

{message}
<" -"br/>Nota: questo accade finché queste ubicazioni sono figlie della " -"ubicazione destinazione del movimento di magazzino o finché queste " +"ubicazione disponibile nelle seguenti ubicazioni:

{message}

Nota: questo accade finché queste ubicazioni sono figlie " +"della ubicazione destinazione del movimento di magazzino o finché queste " "ubicazioni sono figlie dell'ubicazione di destinazione dopo che è stato " "eseguito il deposito (prodotto o categoria)." @@ -717,17 +705,6 @@ msgstr "" msgid "code_snippet should return boolean value into `result` variable." msgstr "code_snippet deve restituire un buleano nella variabile `result`." -#. module: stock_storage_type -#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_is_empty -msgid "" -"technical field: True if the location is empty and there is no pending " -"incoming products in the location. Computed only if the location needs to " -"check for emptiness (has an \"only empty\" policy)." -msgstr "" -"Campo tecnico: Vero se l'ubicazione è vuota e non ci sono prodotti in arrivo " -"in sospeso. Calcolato solo se l'ubicazione deve verificare se vuota (ha una " -"politica \"solo vuote\")." - #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_location_ids msgid "technical field: all the leaves locations" @@ -772,6 +749,42 @@ msgstr "campo tecnico: i stock.moves in attesa nell'ubicazione" msgid "technical field: the pending outgoing stock.move.lines in the location" msgstr "campo tecnico: le stock.move.lines in uscita in atesa nell'ubicazione" +#~ msgid "Location Is Empty" +#~ msgstr "Ubicazione vuota" + +#, python-format +#~ msgid "" +#~ "Storage Category {category} is flagged 'do not mix lots' but there are " +#~ "other lots in location." +#~ msgstr "" +#~ "La categoria stoccaggio {category} è impostata a 'non mescolare i lotti' " +#~ "ma ci sono altri lotti nell'ubicazione." + +#, python-format +#~ msgid "" +#~ "Storage Category {category} is flagged 'do not mix products' but there " +#~ "are other products in location." +#~ msgstr "" +#~ "La categoria stoccaggio {category} è impostata a 'non mescolare i " +#~ "prodotti' ma ci sono altri prodotti nell'ubicazione." + +#, python-format +#~ msgid "" +#~ "Storage Category {category} is flagged 'only empty' with other quants in " +#~ "location." +#~ msgstr "" +#~ "La categoria stoccaggio {category} è impostata a 'solo vuote' con altri " +#~ "quanti nell'ubicazione." + +#~ msgid "" +#~ "technical field: True if the location is empty and there is no pending " +#~ "incoming products in the location. Computed only if the location needs " +#~ "to check for emptiness (has an \"only empty\" policy)." +#~ msgstr "" +#~ "Campo tecnico: Vero se l'ubicazione è vuota e non ci sono prodotti in " +#~ "arrivo in sospeso. Calcolato solo se l'ubicazione deve verificare se " +#~ "vuota (ha una politica \"solo vuote\")." + #, python-format #~ msgid "" #~ " * {location} (WARNING: restrictions are " diff --git a/stock_storage_type/i18n/stock_storage_type.pot b/stock_storage_type/i18n/stock_storage_type.pot index 09502e30c7a..f25b3571d5a 100644 --- a/stock_storage_type/i18n/stock_storage_type.pot +++ b/stock_storage_type/i18n/stock_storage_type.pot @@ -202,6 +202,16 @@ msgstr "" msgid "Execute code" msgstr "" +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "Has Potential Lot Mix Exception" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "Has Potential Product Mix Exception" +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__has_restrictions msgid "Has Restrictions" @@ -289,11 +299,6 @@ msgstr "" msgid "Location" msgstr "" -#. module: stock_storage_type -#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_is_empty -msgid "Location Is Empty" -msgstr "" - #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_lot_ids msgid "Location Will Contain Lot" @@ -526,33 +531,6 @@ msgid "" " heavier: {weight_kg}." msgstr "" -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix lots' but there are other" -" lots in location." -msgstr "" - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix products' but there are " -"other products in location." -msgstr "" - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'only empty' with other quants in " -"location." -msgstr "" - #. module: stock_storage_type #: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu msgid "Storage Location Sequence Conditions" @@ -616,6 +594,22 @@ msgid "" " the category set on the location or on one of its parent." msgstr "" +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different product lots and the location doesnot allow mixed " +"lots." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different products and the location doesnot allow mixed " +"products." +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id msgid "UoM for height" @@ -657,14 +651,6 @@ msgstr "" msgid "code_snippet should return boolean value into `result` variable." msgstr "" -#. module: stock_storage_type -#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_is_empty -msgid "" -"technical field: True if the location is empty and there is no pending " -"incoming products in the location. Computed only if the location needs to " -"check for emptiness (has an \"only empty\" policy)." -msgstr "" - #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_location_ids msgid "technical field: all the leaves locations" diff --git a/stock_storage_type/models/stock_location.py b/stock_storage_type/models/stock_location.py index 72d2de071cd..ba477e927d7 100644 --- a/stock_storage_type/models/stock_location.py +++ b/stock_storage_type/models/stock_location.py @@ -7,7 +7,7 @@ from odoo import api, fields, models from odoo.fields import Command -from odoo.tools import float_compare, index_exists +from odoo.tools import float_compare, groupby, index_exists _logger = logging.getLogger(__name__) OUT_MOVE_LINE_DOMAIN = [ @@ -58,15 +58,7 @@ class StockLocation(models.Model): "location_id", string="Storage locations sequences", ) - location_is_empty = fields.Boolean( - compute="_compute_location_is_empty", - store=True, - help="technical field: True if the location is empty " - "and there is no pending incoming products in the location. " - " Computed only if the location needs to check for emptiness " - '(has an "only empty" policy).', - recursive=True, - ) + # TODO: Maybe renaming these fields as there are already such fields # in core but without domains. Something like 'pending_in_move_ids' in_move_ids = fields.One2many( @@ -142,6 +134,23 @@ class StockLocation(models.Model): compute="_compute_only_empty", store=True, recursive=True ) + has_potential_product_mix_exception = fields.Boolean( + compute="_compute_has_potential_product_mix_exception", + store=True, + index=True, + help="This will represent a situation where several moves are pointing" + "to the location for different products and the location does" + "not allow mixed products.", + ) + has_potential_lot_mix_exception = fields.Boolean( + compute="_compute_has_potential_lot_mix_exception", + store=True, + index=True, + help="This will represent a situation where several moves are pointing" + "to the location for different product lots and the location does" + "not allow mixed lots.", + ) + def init(self): # pylint: disable=missing-return super().init() if not index_exists(self._cr, "stock_move_line_location_state_index"): @@ -155,6 +164,38 @@ def init(self): # pylint: disable=missing-return """ ) + @api.depends("do_not_mix_lots", "location_will_contain_lot_ids", "fill_state") + def _compute_has_potential_lot_mix_exception(self): + locations_with_exception = self.browse() + locations_without_exception = self.browse() + for location in self: + if ( + location.fill_state not in ("empty", "being_emptied") + and location._should_compute_will_contain_lot_ids() + and len(location.location_will_contain_lot_ids) > 1 + ): + locations_with_exception |= location + else: + locations_without_exception |= location + locations_with_exception.has_potential_lot_mix_exception = True + locations_without_exception.has_potential_lot_mix_exception = False + + @api.depends("do_not_mix_lots", "location_will_contain_product_ids", "fill_state") + def _compute_has_potential_product_mix_exception(self): + locations_with_exception = self.browse() + locations_without_exception = self.browse() + for location in self: + if ( + location.fill_state not in ("empty", "being_emptied") + and location._should_compute_will_contain_product_ids() + and len(location.location_will_contain_product_ids) > 1 + ): + locations_with_exception |= location + else: + locations_without_exception |= location + locations_with_exception.has_potential_product_mix_exception = True + locations_without_exception.has_potential_product_mix_exception = False + @api.depends( "usage", "computed_storage_category_id.allow_new_product", @@ -263,13 +304,11 @@ def _should_compute_will_contain_product_ids(self): def _should_compute_will_contain_lot_ids(self): return self.do_not_mix_lots - def _should_compute_location_is_empty(self): - return self.only_empty - @api.depends( "quant_ids.quantity", "in_move_ids", "in_move_line_ids", + "out_move_line_ids", "do_not_mix_products", ) def _compute_location_will_contain_product_ids(self): @@ -278,18 +317,53 @@ def _compute_location_will_contain_product_ids(self): no_product = self.env["product.product"].browse() rec.location_will_contain_product_ids = no_product else: + non_fully_reserved_quants = rec.quant_ids.filtered( + lambda q: float_compare( + q.quantity, 0, precision_rounding=q.product_uom_id.rounding + ) + > 0 + and float_compare( + q.reserved_quantity, + q.quantity, + precision_rounding=q.product_uom_id.rounding, + ) + < 0 + ) + # Products that are obviously in the location products = ( - rec.mapped("quant_ids") - .filtered(lambda q: q.quantity > 0) - .product_id - | rec.mapped("in_move_ids.product_id") + non_fully_reserved_quants.product_id | rec.mapped("in_move_line_ids.product_id") + | rec.mapped("in_move_ids.product_id") ) + # For fully reserved quants, ensure the product is not being emptied + remaining_quants = rec.quant_ids.filtered( + lambda q, products=products: q.product_id not in products + ) + if remaining_quants: + for product, quants_by_product in groupby( + remaining_quants, lambda q: q.product_id + ): + quantity = sum(map(lambda q: q.quantity, quants_by_product)) + picked_quantity = sum( + ml.qty_done + for ml in rec.out_move_line_ids + if ml.product_id == product + ) + if ( + float_compare( + quantity, + picked_quantity, + precision_rounding=product.uom_id.rounding, + ) + > 0 + ): + products |= product rec.location_will_contain_product_ids = products @api.depends( "quant_ids.quantity", "in_move_line_ids", + "out_move_line_ids", "do_not_mix_lots", ) def _compute_location_will_contain_lot_ids(self): @@ -298,59 +372,47 @@ def _compute_location_will_contain_lot_ids(self): no_lot = self.env["stock.lot"].browse() rec.location_will_contain_lot_ids = no_lot else: - lots = rec.mapped("quant_ids").filtered( - lambda q: q.quantity > 0 - ).lot_id | rec.mapped("in_move_line_ids.lot_id") + non_fully_reserved_quants = rec.quant_ids.filtered( + lambda q: float_compare( + q.quantity, 0, precision_rounding=q.product_uom_id.rounding + ) + > 0 + and float_compare( + q.reserved_quantity, + q.quantity, + precision_rounding=q.product_uom_id.rounding, + ) + < 0 + ) + # Lots that are obviously in the location + lots = non_fully_reserved_quants.lot_id | rec.mapped( + "in_move_line_ids.lot_id" + ) + # For fully reserved quants, ensure the lot is not being emptied + remaining_quants = rec.quant_ids.filtered( + lambda q, lots=lots: q.lot_id and q.lot_id not in lots + ) + if remaining_quants: + for lot, quants_by_product in groupby( + remaining_quants, lambda q: q.lot_id + ): + quantity = sum(map(lambda q: q.quantity, quants_by_product)) + picked_quantity = sum( + ml.qty_done + for ml in rec.out_move_line_ids + if ml.lot_id == lot + ) + if ( + float_compare( + quantity, + picked_quantity, + precision_rounding=lot.product_id.uom_id.rounding, + ) + > 0 + ): + lots |= lot rec.location_will_contain_lot_ids = lots - @api.depends( - "quant_ids.quantity", - "out_move_line_ids.qty_done", - "in_move_ids", - "in_move_line_ids", - "only_empty", - ) - def _compute_location_is_empty(self): - # No restriction should apply on customer/supplier/... - # locations and we don't need to compute is empty - # if there is no limit on the location - only_empty_locations = self.filtered( - lambda l: not l._should_compute_location_is_empty() - ) - only_empty_locations.update({"location_is_empty": True}) - records = self - only_empty_locations - if not records: - return - location_domain = [("location_id", "in", records.ids)] - out_qty_by_location = {} - qty_by_location = {} - for group in self.env["stock.move.line"].read_group( - OUT_MOVE_LINE_DOMAIN + location_domain, - fields=["qty_done:sum"], - groupby=["location_id"], - ): - location_id = group["location_id"][0] - out_qty_by_location[location_id] = group["qty_done"] - for group in self.env["stock.quant"].read_group( - location_domain, fields=["quantity:sum"], groupby=["location_id"] - ): - location_id = group["location_id"][0] - qty_by_location[location_id] = group["quantity"] - for rec in records: - # we do want to keep a write here even if the value is the same - # to enforce concurrent transaction safety: 2 moves taking - # quantities in a location have to be executed sequentially - # or the location could remain "not empty" - if ( - qty_by_location.get(rec.id, 0.0) - out_qty_by_location.get(rec.id, 0.0) - > 0 - or rec.in_move_ids - or rec.in_move_line_ids - ): - rec.location_is_empty = False - else: - rec.location_is_empty = True - # method provided by "stock_putaway_hook" def _putaway_strategy_finalizer( self, @@ -635,18 +697,17 @@ def _order_allowed_locations(self, valid_locations): valid_no_mix = valid_locations.filtered("do_not_mix_products") loc_ordered_by_qty = [] if valid_no_mix: - StockQuant = self.env["stock.quant"] - domain_quant = [("location_id", "in", valid_no_mix.ids)] - loc_ordered_by_qty = [ - item["location_id"][0] - for item in StockQuant.read_group( - domain_quant, - ["location_id", "quantity"], - ["location_id"], - orderby="quantity", + for location, items in groupby( + valid_no_mix.quant_ids.sorted("quantity"), + lambda quant: quant.location_id, + ): + loc_ordered_by_qty.extend( + [ + location.id + for item in items + if (float_compare(item["quantity"], 0, precision_digits=2) > 0) + ] ) - if (float_compare(item["quantity"], 0, precision_digits=2) > 0) - ] valid_location_ids = set(valid_locations.ids) - set(loc_ordered_by_qty) ordered_valid_location_ids = loc_ordered_by_qty + [ id_ for id_ in self.ids if id_ in valid_location_ids diff --git a/stock_storage_type/models/stock_quant.py b/stock_storage_type/models/stock_quant.py index effebd99c38..d1acacad287 100644 --- a/stock_storage_type/models/stock_quant.py +++ b/stock_storage_type/models/stock_quant.py @@ -30,58 +30,14 @@ def _check_storage_capacities(self): "Location {location}" ).format(storage=package_type.name, location=location.name) ) + package = quant.package_id package_weight_kg = ( - quant.package_id.pack_weight_in_kg - or quant.package_id.estimated_pack_weight_kg + package.pack_weight_in_kg or package.estimated_pack_weight_kg ) - package_quants = quant.package_id.mapped("quant_ids") - package_products = package_quants.mapped("product_id") - package_lots = package_quants.mapped("lot_id") - other_quants_in_location = self.search( - [ - ("location_id", "=", location.id), - ("id", "not in", package_quants.ids), - ("quantity", ">", 0), - ] - ) - products_in_location = other_quants_in_location.mapped("product_id") - lots_in_location = other_quants_in_location.mapped("lot_id") error = None category = location.computed_storage_category_id - allow_new_product = category.get_allow_new_product( - product=quant.product_id, - package_type=package_type, - package=quant.package_id, - quants=quant, - ) - # Check content constraints - if allow_new_product == "empty" and other_quants_in_location: - error = _( - "Storage Category {category} is flagged " - "'only empty' with other quants in location." - ).format(category=category.display_name) - elif allow_new_product == "same" and ( - len(package_products) > 1 - or len(products_in_location) >= 1 - and package_products != products_in_location - ): - error = _( - "Storage Category {category} is flagged 'do not mix" - " products' but there are other products in " - "location." - ).format(category=category.display_name) - elif allow_new_product == "same_lot" and ( - len(package_lots) > 1 - or len(lots_in_location) >= 1 - and package_lots != lots_in_location - ): - error = _( - "Storage Category {category} is flagged 'do not mix" - " lots' but there are other lots in " - "location." - ).format(category=category.display_name) # Check size constraint - elif ( + if ( category.max_height_in_m and quant.package_id.height_in_m > category.max_height_in_m ): @@ -94,7 +50,7 @@ def _check_storage_capacities(self): max_h=category.max_height_in_m, height=quant.package_id.height_in_m, ) - elif ( + if ( category.max_weight_in_kg and package_weight_kg > category.max_weight_in_kg ): @@ -107,8 +63,6 @@ def _check_storage_capacities(self): max_w=category.max_weight_in_kg, weight_kg=package_weight_kg, ) - # If we get here, it means there is a storage category - # allowing the package into the location if error: raise ValidationError( _( @@ -116,7 +70,7 @@ def _check_storage_capacities(self): " because there isn't any rules that allows" " package type {type} into it:\n\n{error}" ).format( - package=quant.package_id.name, + package=package.name, location=location.complete_name, type=package_type.name, error=error, diff --git a/stock_storage_type/models/stock_storage_category.py b/stock_storage_type/models/stock_storage_category.py index 0196e1cc1a8..36ab6044f0a 100644 --- a/stock_storage_type/models/stock_storage_category.py +++ b/stock_storage_type/models/stock_storage_category.py @@ -1,6 +1,7 @@ # Copyright 2022 ACSONE SA # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) from odoo import api, fields, models +from odoo.osv.expression import AND, OR class StockStorageCategory(models.Model): @@ -128,21 +129,53 @@ def _compute_has_restrictions(self): ] ) + def _get_product_lot_location_domain(self, lots): + """ + Helper to get product lots domain + """ + lot_domain = OR( + [ + [ + ("location_will_contain_lot_ids", "in", lots.ids), + ], + [ + ("location_will_contain_lot_ids", "=", False), + ], + ] + ) + + location_domain = OR( + [lot_domain, [("fill_state", "in", ("empty", "being_emptied"))]] + ) + + return location_domain + def _get_product_location_domain(self, products): """ Helper to get products location domain """ - return [ - "|", - # Ideally, we would like a domain which is a strict comparison: - # if we do not mix products, we should be able to filter on == - # product.id. Here, if we can create a move for product B and - # set it's destination in a location already used by product A, - # then all the new moves for product B will be allowed in the - # location. - ("location_will_contain_product_ids", "in", products.ids), - ("location_will_contain_product_ids", "=", False), - ] + # Ideally, we would like a domain which is a strict comparison: + # if we do not mix products, we should be able to filter on == + # product.id. Here, if we can create a move for product B and + # set it's destination in a location already used by product A, + # then all the new moves for product B will be allowed in the + # location. + + # Take only locations that has no potential different products + # in it. + product_domain = OR( + [ + [ + ("has_potential_product_mix_exception", "=", False), + ("location_will_contain_product_ids", "in", products.ids), + ], + [("location_will_contain_product_ids", "=", False)], + ] + ) + location_domain = OR( + [product_domain, [("fill_state", "in", ("empty", "being_emptied"))]] + ) + return location_domain def _domain_location_storage_category( self, candidate_locations, quants, products, package_type @@ -164,19 +197,56 @@ def _domain_location_storage_category( quants=quants, ) if allow_new_product == "empty": - location_domain.append(("location_is_empty", "=", True)) + # We should include the destination location of the current + # stock move line to avoid excluding it if already selected + # Indeed, if the current move line point to the last void location, + # calling the putaway apply will recompute the destination location + # to the related stock.move destination as the rules consider + # there is no more room available (which is not true). + exclude_sml_ids = self.env.context.get("exclude_sml_ids") + if exclude_sml_ids: + lines_locations = ( + self.env["stock.move.line"].browse(exclude_sml_ids).location_dest_id + ) + if lines_locations: + location_domain = AND( + [ + location_domain, + OR( + [ + [ + ( + "fill_state", + "in", + ("filled", "being_filled"), + ), + ("id", "in", lines_locations.ids), + ], + [("fill_state", "in", ("empty", "being_emptied"))], + ] + ), + ] + ) + else: + location_domain = AND( + [ + location_domain, + [("fill_state", "in", ("empty", "being_emptied"))], + ] + ) elif allow_new_product == "same": - location_domain += self._get_product_location_domain(products) + location_domain = AND( + [location_domain, self._get_product_location_domain(products)] + ) elif allow_new_product == "same_lot": lots = quants.mapped("lot_id") # As same lot should filter also on same product - location_domain += self._get_product_location_domain(products) - location_domain += [ - "|", - # same comment as for the products - ("location_will_contain_lot_ids", "in", lots.ids), - ("location_will_contain_lot_ids", "=", False), - ] + location_domain = AND( + [location_domain, self._get_product_location_domain(products)] + ) + location_domain = AND( + [location_domain, self._get_product_lot_location_domain(lots)] + ) return location_domain def get_allow_new_product( diff --git a/stock_storage_type/static/description/index.html b/stock_storage_type/static/description/index.html index 4cdf66d916d..814131b2264 100644 --- a/stock_storage_type/static/description/index.html +++ b/stock_storage_type/static/description/index.html @@ -372,7 +372,7 @@

Stock Storage Type

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:c46ce97684eda7baf75edac00f005ea18baac91d81a4f0df32ba6f49ce1b34ee +!! source digest: sha256:ad18c92cb64972423bce045cb542b198d72561d86c746e5db7e7ccc9962cbec5 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/wms Translate me on Weblate Try me on Runboat

This module extends package types Odoo feature in order to better manage stock diff --git a/stock_storage_type/tests/__init__.py b/stock_storage_type/tests/__init__.py index ba7aa132776..bdd58279b1c 100644 --- a/stock_storage_type/tests/__init__.py +++ b/stock_storage_type/tests/__init__.py @@ -1,7 +1,6 @@ from . import ( test_auto_assign_storage_type, test_package_height_required, - test_package_type_message, test_stock_location, test_storage_type, test_storage_type_move, diff --git a/stock_storage_type/tests/test_package_type_message.py b/stock_storage_type/tests/test_package_type_message.py deleted file mode 100644 index 7a77d490d86..00000000000 --- a/stock_storage_type/tests/test_package_type_message.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2022 ACSONE SA -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -from odoo.tests import TransactionCase - - -class TestStorageType(TransactionCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) - - cls.stock_location = cls.env.ref("stock.stock_location_stock") - cls.pallets_location_storage_type = cls.env.ref( - "stock_storage_type.location_storage_type_pallets" - ) - cls.pallets_uk_location_storage_type = cls.env.ref( - "stock_storage_type.location_storage_type_pallets_uk" - ) - cls.cardboxes_location_storage_type = cls.env.ref( - "stock_storage_type.location_storage_type_cardboxes" - ) - cls.cardboxes_stock = cls.env.ref("stock_storage_type.stock_location_cardboxes") - cls.cardboxes_bin_1 = cls.env.ref( - "stock_storage_type.stock_location_cardboxes_bin_1" - ) - cls.cardboxes_bin_2 = cls.env.ref( - "stock_storage_type.stock_location_cardboxes_bin_2" - ) - cls.cardboxes_bin_3 = cls.env.ref( - "stock_storage_type.stock_location_cardboxes_bin_3" - ) - cls.cardboxes_bin_4 = cls.env.ref( - "stock_storage_type.stock_location_cardboxes_bin_4" - ) diff --git a/stock_storage_type/tests/test_stock_location.py b/stock_storage_type/tests/test_stock_location.py index 6381ee18195..7bdbd0df43e 100644 --- a/stock_storage_type/tests/test_stock_location.py +++ b/stock_storage_type/tests/test_stock_location.py @@ -267,28 +267,3 @@ def test_will_contain_product_lot_ids_quantity(self): self.assertTrue(quant) self.assertEqual(0.0, quant.quantity) self.assertFalse(location.location_will_contain_lot_ids) - - def test_location_is_empty_non_internal(self): - location = self.env.ref("stock.stock_location_customers") - # we always consider an non-internal location empty, the put-away - # rules do not apply and we can add as many quants as we want - self.assertTrue(location.location_is_empty) - self._update_qty_in_location(location, self.product, 10) - self.assertTrue(location.location_is_empty) - - def test_location_is_empty(self): - location = self.pallets_reserve_bin_1_location - self.assertTrue(location.only_empty) - self.assertTrue(location.location_is_empty) - self._update_qty_in_location(location, self.product, 10) - self.assertFalse(location.location_is_empty) - - # When the location has no "only_empty" rule, we don't - # care about if it is empty or not, we keep it as True so we - # can always put things inside. Not computing it prevents - # useless race conditions on concurrent writes. - category = location.computed_storage_category_id - category.allow_new_product_ids.filtered( - lambda rule: rule.allow_new_product == "empty" - ).allow_new_product = "mixed" - self.assertTrue(location.location_is_empty) diff --git a/stock_storage_type/tests/test_storage_type_move.py b/stock_storage_type/tests/test_storage_type_move.py index 1915122a3a6..28e20fadac1 100644 --- a/stock_storage_type/tests/test_storage_type_move.py +++ b/stock_storage_type/tests/test_storage_type_move.py @@ -1,6 +1,5 @@ # Copyright 2020 Camptocamp SA # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -from odoo.exceptions import ValidationError from odoo.tools.safe_eval import const_eval from .common import TestStorageTypeCommon @@ -375,14 +374,6 @@ def _levels_for(packages): _get_possible_locations(pack_level), ) - # Set the quantities done in order to avoid immediate transfer wizard - for move_line in pack_level.move_line_ids: - move_line.qty_done = move_line.reserved_qty - - second_level.location_dest_id = third_level.location_dest_id - with self.assertRaises(ValidationError): - int_picking.button_validate() - def test_stock_move_no_package(self): """ Create a stock move for a product with lot restriction diff --git a/stock_storage_type/tests/test_storage_type_putaway_strategy.py b/stock_storage_type/tests/test_storage_type_putaway_strategy.py index a2e2f82b120..8a3da2bd688 100644 --- a/stock_storage_type/tests/test_storage_type_putaway_strategy.py +++ b/stock_storage_type/tests/test_storage_type_putaway_strategy.py @@ -143,6 +143,13 @@ def test_storage_strategy_only_empty_ordered_locations_pallets(self): self.pallets_bin_1_location | self.pallets_bin_3_location, ) + # Try to re-apply the putaways to check the same destinations are selected + int_picking.move_line_ids._apply_putaway_strategy() + self.assertEqual( + int_picking.move_line_ids.mapped("location_dest_id"), + self.pallets_bin_1_location | self.pallets_bin_3_location, + ) + def test_storage_strategy_max_weight_ordered_locations_pallets(self): """Test pallet max weight constraint on a location. @@ -829,3 +836,293 @@ def test_storage_strategy_with_view(self): "the move line's destination must stay in Stock as we have" " a 'none' strategy on it and it is in the sequence", ) + + def test_storage_strategy_same_ordered_locations_pallets_reapply(self): + """ + Check if location is well recomputed after filling it with another move + and after emptying other ones that are after in the ordering + + - The location is first computed on the last free one + - The location is filled in with another move + - The move's location destination is recomputed + """ + # Set pallets location type as only empty and remove specific pallet condition + self.pallets_location_storage_type.storage_category_id.write( + {"allow_new_product": "same"} + ) + self.pallets_location_storage_type.storage_category_id.allow_new_product_ids = ( + False + ) + # Set another product in bin 2 and bin 3 + self.env["stock.quant"]._update_available_quantity( + self.product2, self.pallets_bin_2_location, 1.0 + ) + self.env["stock.quant"]._update_available_quantity( + self.product3, self.pallets_bin_3_location, 1.0 + ) + # Create picking + in_picking = self.env["stock.picking"].create( + { + "picking_type_id": self.receipts_picking_type.id, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "move_ids": [ + ( + 0, + 0, + { + "name": self.product.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product.id, + "product_uom_qty": 96.0, + "product_uom": self.product.uom_id.id, + }, + ) + ], + } + ) + # Mark as todo + in_picking.action_confirm() + # Put in pack + in_picking.move_line_ids.qty_done = 48.0 + first_package = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + first_package.product_packaging_id = self.product_pallet_product_packaging + # Put in pack again + ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: not ml.result_package_id + ) + ml_without_package.qty_done = 48.0 + second_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + second_pack.product_packaging_id = self.product_pallet_product_packaging + + # Validate picking + in_picking.button_validate() + # Assign internal picking + int_picking = in_picking.move_ids.move_dest_ids.picking_id + int_picking.action_assign() + self.assertEqual(int_picking.location_dest_id, self.stock_location) + self.assertEqual( + int_picking.move_ids.mapped("location_dest_id"), self.stock_location + ) + # First move line goes into pallets bin 1 + # Second move line goes into pallets bin 3 as bin 1 is planned for + # first move line and bin 2 is already used + self.assertEqual( + int_picking.move_line_ids[0].mapped("location_dest_id"), + self.pallets_bin_1_location, + ) + + self.env["stock.quant"].with_context(inventory_mode=True).create( + { + "product_id": self.product3.id, + "location_id": self.pallets_bin_1_location.id, + "inventory_quantity": 10.0, + } + )._apply_inventory() + + # Void the bin 3 + quant = self.env["stock.quant"].search( + [ + ("product_id", "=", self.product3.id), + ("location_id", "=", self.pallets_bin_3_location.id), + ] + ) + quant.location_id = self.env.ref("stock.stock_location_customers") + + # Try to re-apply the putaways to check the good destination is selected + int_picking.move_line_ids._apply_putaway_strategy() + self.assertNotEqual( + int_picking.move_line_ids[0].mapped("location_dest_id"), + self.pallets_bin_1_location, + ) + self.assertEqual( + int_picking.move_line_ids[0].mapped("location_dest_id"), + self.pallets_bin_3_location, + ) + + self.env["stock.quant"].with_context(inventory_mode=True).create( + { + "product_id": self.product.id, + "location_id": self.pallets_bin_3_location.id, + "inventory_quantity": 10.0, + } + )._apply_inventory() + + int_picking.move_line_ids._apply_putaway_strategy() + self.assertEqual( + int_picking.move_line_ids[0].mapped("location_dest_id"), + self.pallets_bin_3_location, + ) + + def test_storage_strategy_same_lot_emptying(self): + """ + Ensure that if a location is being emptied, it becomes available + for a new movement. + + So: + - Fill in all carboxes location bins. + - Create an OUT move with quantity done (location will be 'being emptied') + - Do the reception + - Check the internal move will go to the BIN 1 + + """ + self.product_other = self.env.ref("product.product_product_10") + self.cardboxes_location_storage_type.storage_category_id.write( + {"allow_new_product": "same_lot"} + ) + # Fill in all locations + self.env["stock.quant"]._update_available_quantity( + self.product_other, + self.cardboxes_bin_1_location, + 1.0, + ) + self.env["stock.quant"]._update_available_quantity( + self.product_other, + self.cardboxes_bin_2_location, + 1.0, + ) + self.env["stock.quant"]._update_available_quantity( + self.product_other, + self.cardboxes_bin_3_location, + 1.0, + ) + self.env["stock.quant"]._update_available_quantity( + self.product_other, + self.cardboxes_bin_4_location, + 1.0, + ) + + # Create picking + in_picking = self.env["stock.picking"].create( + { + "picking_type_id": self.receipts_picking_type.id, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "move_ids": [ + ( + 0, + 0, + { + "name": self.product.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product.id, + "product_uom_qty": 8.0, + "product_uom": self.product.uom_id.id, + "picking_type_id": self.receipts_picking_type.id, + }, + ), + ( + 0, + 0, + { + "name": self.product_lot.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product_lot.id, + "product_uom_qty": 10.0, + "product_uom": self.product_lot.uom_id.id, + "picking_type_id": self.receipts_picking_type.id, + }, + ), + ], + } + ) + # Mark as todo + in_picking.action_confirm() + # Put in pack product + in_picking.move_line_ids.filtered( + lambda ml: ml.product_id == self.product + ).qty_done = 4.0 + product_first_package = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + product_first_package.product_packaging_id = ( + self.product_cardbox_product_packaging + ) + # Put in pack product again + product_ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: not ml.result_package_id and ml.product_id == self.product + ) + product_ml_without_package.qty_done = 4.0 + product_second_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + product_second_pack.product_packaging_id = ( + self.product_cardbox_product_packaging + ) + + # Put in pack product lot + product_lot_ml = in_picking.move_line_ids.filtered( + lambda ml: not ml.result_package_id and ml.product_id == self.product_lot + ) + product_lot_ml.write({"qty_done": 5.0, "lot_name": "A0001"}) + product_lot_first_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + product_lot_first_pack.product_packaging_id = ( + self.product_lot_cardbox_product_packaging + ) + # Put in pack product lot again + product_lot_ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: not ml.result_package_id and ml.product_id == self.product_lot + ) + product_lot_ml_without_package.write({"qty_done": 5.0, "lot_name": "A0002"}) + product_lot_second_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + product_lot_second_pack.product_packaging_id = ( + self.product_lot_cardbox_product_packaging + ) + + # Create a move to pick a bin location, so that location fill state + # will be 'being emptied'. + customers = self.env.ref("stock.stock_location_customers") + pick_picking = self.env["stock.picking"].create( + { + "picking_type_id": self.internal_picking_type.id, + "location_id": self.cardboxes_bin_1_location.id, + "location_dest_id": customers.id, + "move_ids": [ + ( + 0, + 0, + { + "name": self.product.name, + "location_id": self.cardboxes_bin_1_location.id, + "location_dest_id": customers.id, + "product_id": self.product_other.id, + "product_uom_qty": 8.0, + "product_uom": self.product_other.uom_id.id, + "picking_type_id": self.internal_picking_type.id, + }, + ), + ], + } + ) + + pick_picking.action_assign() + self.assertEqual( + pick_picking.move_line_ids.location_id, + self.cardboxes_bin_1_location, + ) + + pick_picking.move_line_ids.qty_done = 8.0 + + self.assertEqual("being_emptied", self.cardboxes_bin_1_location.fill_state) + + # Validate picking + in_picking.button_validate() + + # Assign internal picking + int_picking = in_picking.move_ids.mapped("move_dest_ids.picking_id") + int_picking.action_assign() + self.assertEqual(int_picking.location_dest_id, self.stock_location) + self.assertEqual( + int_picking.move_ids.mapped("location_dest_id"), self.stock_location + ) + product_mls = int_picking.move_line_ids.filtered( + lambda ml: ml.product_id == self.product + ) + self.assertEqual( + product_mls.mapped("location_dest_id"), self.cardboxes_bin_1_location + )