From 932860798fa3b21b13a45ba42c940258832c2b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Thu, 6 Jul 2023 09:33:20 +0200 Subject: [PATCH] [IMP] stock_request: Add option to use stock available TT43413 --- stock_request/i18n/es.po | 20 ++- stock_request/i18n/stock_request.pot | 15 ++- stock_request/models/res_company.py | 3 + stock_request/models/res_config_settings.py | 3 + stock_request/models/stock_request.py | 61 ++++++++- stock_request/tests/test_stock_request.py | 123 ++++++++++++++++++ .../views/res_config_settings_views.xml | 14 ++ 7 files changed, 232 insertions(+), 7 deletions(-) diff --git a/stock_request/i18n/es.po b/stock_request/i18n/es.po index 0538c9692..19e67215f 100644 --- a/stock_request/i18n/es.po +++ b/stock_request/i18n/es.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 11.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-26 16:12+0000\n" -"PO-Revision-Date: 2023-01-26 17:12+0100\n" +"POT-Creation-Date: 2023-07-06 07:32+0000\n" +"PO-Revision-Date: 2023-07-06 09:32+0200\n" "Last-Translator: Víctor Martínez \n" "Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" "Language: es\n" @@ -118,6 +118,11 @@ msgstr "" "De manera predeterminada, solo se permiten ubicaciones internas y de " "tránsito en Solicitud de inventario y pedidos." +#. module: stock_request +#: model_terms:ir.ui.view,arch_db:stock_request.res_config_settings_view_form +msgid "By default, available stock is not used" +msgstr "Por defecto, el stock disponible no se usa" + #. module: stock_request #: model_terms:ir.ui.view,arch_db:stock_request.stock_request_order_form #: model_terms:ir.ui.view,arch_db:stock_request.view_stock_request_form @@ -137,6 +142,13 @@ msgstr "Cancelado" msgid "Category" msgstr "Categoría" +#. module: stock_request +#: model:ir.model.fields,field_description:stock_request.field_res_company__stock_request_check_available_first +#: model:ir.model.fields,field_description:stock_request.field_res_config_settings__stock_request_check_available_first +#: model_terms:ir.ui.view,arch_db:stock_request.res_config_settings_view_form +msgid "Check available stock first" +msgstr "Comprobar stock disponible primero" + #. module: stock_request #: model_terms:ir.actions.act_window,help:stock_request.action_stock_request_form msgid "Click to add a Stock Request." @@ -179,6 +191,9 @@ msgid "" "Conversion between Units of Measure can only occur if they belong to the " "same category. The conversion will be made based on the ratios." msgstr "" +"La conversión entre las unidades de medidas sólo pueden ocurrir si " +"pertenecen a la misma categoría. La conversión se basará en los ratios " +"establecidos." #. module: stock_request #: model:ir.model.fields,field_description:stock_request.field_stock_request__create_uid @@ -531,7 +546,6 @@ msgstr "Nombre" #. module: stock_request #: model:ir.model.constraint,message:stock_request.constraint_stock_request_abstract_name_uniq -#: model:ir.model.constraint,message:stock_request.constraint_stock_request_kanban_name_uniq msgid "Name must be unique" msgstr "El nombre debe ser único" diff --git a/stock_request/i18n/stock_request.pot b/stock_request/i18n/stock_request.pot index 4def688da..ada53b1df 100644 --- a/stock_request/i18n/stock_request.pot +++ b/stock_request/i18n/stock_request.pot @@ -6,6 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-07-06 07:32+0000\n" +"PO-Revision-Date: 2023-07-06 07:32+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -107,6 +109,11 @@ msgid "" "and Orders." msgstr "" +#. module: stock_request +#: model_terms:ir.ui.view,arch_db:stock_request.res_config_settings_view_form +msgid "By default, available stock is not used" +msgstr "" + #. module: stock_request #: model_terms:ir.ui.view,arch_db:stock_request.stock_request_order_form #: model_terms:ir.ui.view,arch_db:stock_request.view_stock_request_form @@ -126,6 +133,13 @@ msgstr "" msgid "Category" msgstr "" +#. module: stock_request +#: model:ir.model.fields,field_description:stock_request.field_res_company__stock_request_check_available_first +#: model:ir.model.fields,field_description:stock_request.field_res_config_settings__stock_request_check_available_first +#: model_terms:ir.ui.view,arch_db:stock_request.res_config_settings_view_form +msgid "Check available stock first" +msgstr "" + #. module: stock_request #: model_terms:ir.actions.act_window,help:stock_request.action_stock_request_form msgid "Click to add a Stock Request." @@ -517,7 +531,6 @@ msgstr "" #. module: stock_request #: model:ir.model.constraint,message:stock_request.constraint_stock_request_abstract_name_uniq -#: model:ir.model.constraint,message:stock_request.constraint_stock_request_kanban_name_uniq msgid "Name must be unique" msgstr "" diff --git a/stock_request/models/res_company.py b/stock_request/models/res_company.py index c38c3d535..7314f3d9d 100644 --- a/stock_request/models/res_company.py +++ b/stock_request/models/res_company.py @@ -12,3 +12,6 @@ class ResCompany(models.Model): stock_request_allow_virtual_loc = fields.Boolean( string="Allow Virtual locations on Stock Requests" ) + stock_request_check_available_first = fields.Boolean( + string="Check available stock first" + ) diff --git a/stock_request/models/res_config_settings.py b/stock_request/models/res_config_settings.py index f711151a8..632e7d46b 100644 --- a/stock_request/models/res_config_settings.py +++ b/stock_request/models/res_config_settings.py @@ -19,6 +19,9 @@ class ResConfigSettings(models.TransientModel): string="Stock Requests Kanban integration" ) + stock_request_check_available_first = fields.Boolean( + related="company_id.stock_request_check_available_first", readonly=False + ) stock_request_allow_virtual_loc = fields.Boolean( related="company_id.stock_request_allow_virtual_loc", readonly=False ) diff --git a/stock_request/models/stock_request.py b/stock_request/models/stock_request.py index 2f4479987..0a21d6424 100644 --- a/stock_request/models/stock_request.py +++ b/stock_request/models/stock_request.py @@ -259,7 +259,7 @@ class StockRequest(models.Model): def _action_confirm(self): self._action_launch_procurement_rule() - self.write({"state": "open"}) + self.filtered(lambda x: x.state != "done").write({"state": "open"}) def action_confirm(self): self._action_confirm() @@ -336,10 +336,50 @@ class StockRequest(models.Model): def _skip_procurement(self): return self.state != "draft" or self.product_id.type not in ("consu", "product") + def _prepare_stock_move(self, qty): + return { + "name": self.product_id.display_name, + "company_id": self.company_id.id, + "product_id": self.product_id.id, + "product_uom_qty": qty, + "product_uom": self.product_id.uom_id.id, + "location_id": self.location_id.id, + "location_dest_id": self.location_id.id, + "state": "draft", + "reference": self.name, + } + + def _prepare_stock_request_allocation(self, move): + return { + "stock_request_id": self.id, + "stock_move_id": move.id, + "requested_product_uom_qty": move.product_uom_qty, + } + + def _action_use_stock_available(self): + """Create a stock move with the necessary data and mark it as done.""" + allocation_model = self.env["stock.request.allocation"] + stock_move_model = self.env["stock.move"].sudo() + precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) + quants = self.env["stock.quant"]._gather(self.product_id, self.location_id) + pending_qty = self.product_uom_qty + for quant in quants.filtered(lambda x: x.available_quantity >= 0): + qty_move = min(pending_qty, quant.available_quantity) + if float_compare(qty_move, 0, precision_digits=precision) > 0: + move = stock_move_model.create(self._prepare_stock_move(qty_move)) + move._action_confirm() + pending_qty -= qty_move + # Create allocation + done move + allocation_model.create(self._prepare_stock_request_allocation(move)) + move.quantity_done = move.product_uom_qty + move._action_done() + def _action_launch_procurement_rule(self): """ - Launch procurement group run method with required/custom - fields genrated by a + Launch procurement group (if not enough stock is available) run method + with required/custom fields genrated by a stock request. procurement group will launch '_run_move', '_run_buy' or '_run_manufacture' depending on the stock request product rule. @@ -358,6 +398,21 @@ class StockRequest(models.Model): if float_compare(qty, request.product_qty, precision_digits=precision) >= 0: continue + # If stock is available we use it and we do not execute rule + if request.company_id.stock_request_check_available_first: + if ( + float_compare( + request.product_id.sudo() + .with_context(location=request.location_id.id) + .qty_available, + request.product_uom_qty, + precision_digits=precision, + ) + >= 0 + ): + request._action_use_stock_available() + continue + values = request._prepare_procurement_values( group_id=request.procurement_group_id ) diff --git a/stock_request/tests/test_stock_request.py b/stock_request/tests/test_stock_request.py index 0a9bf5902..e9eaa06ac 100644 --- a/stock_request/tests/test_stock_request.py +++ b/stock_request/tests/test_stock_request.py @@ -146,6 +146,11 @@ class TestStockRequestBase(TestStockRequest): def setUp(self): super().setUp() + def _create_stock_quant(self, product, location, qty): + self.env["stock.quant"].create( + {"product_id": product.id, "location_id": location.id, "quantity": qty} + ) + def test_defaults(self): vals = { "product_id": self.product.id, @@ -505,6 +510,124 @@ class TestStockRequestBase(TestStockRequest): with self.assertRaises(exceptions.ValidationError): self.request_order.with_user(self.stock_request_user).create(vals) + def test_stock_request_order_available_stock_01(self): + self.main_company.stock_request_check_available_first = True + self._create_stock_quant(self.product, self.warehouse.lot_stock_id, 6) + expected_date = fields.Datetime.now() + vals = { + "company_id": self.main_company.id, + "warehouse_id": self.warehouse.id, + "location_id": self.warehouse.lot_stock_id.id, + "expected_date": expected_date, + "stock_request_ids": [ + ( + 0, + 0, + { + "product_id": self.product.id, + "product_uom_id": self.product.uom_id.id, + "product_uom_qty": 5.0, + "company_id": self.main_company.id, + "warehouse_id": self.warehouse.id, + "location_id": self.warehouse.lot_stock_id.id, + "expected_date": expected_date, + }, + ) + ], + } + order = self.request_order.with_user(self.stock_request_user).create(vals) + order.action_confirm() + self.assertEqual(order.stock_request_ids.state, "done") + self.assertEqual(order.state, "done") + self.assertEqual(len(order.stock_request_ids.move_ids), 1) + quant_stock = self.product.stock_quant_ids.filtered( + lambda x: x.location_id == self.warehouse.lot_stock_id + ) + self.assertEqual(quant_stock.quantity, 6) + + def test_stock_request_order_available_stock_02(self): + self.main_company.stock_request_check_available_first = True + self.location_child_1 = self._create_location( + name="Child 1", + location_id=self.warehouse.lot_stock_id.id, + company_id=self.main_company.id, + ) + self.location_child_2 = self._create_location( + name="Child 2", + location_id=self.warehouse.lot_stock_id.id, + company_id=self.main_company.id, + ) + self._create_stock_quant(self.product, self.location_child_1, 6) + expected_date = fields.Datetime.now() + vals = { + "company_id": self.main_company.id, + "warehouse_id": self.warehouse.id, + "location_id": self.location_child_1.id, + "expected_date": expected_date, + "stock_request_ids": [ + ( + 0, + 0, + { + "product_id": self.product.id, + "product_uom_id": self.product.uom_id.id, + "product_uom_qty": 5.0, + "company_id": self.main_company.id, + "warehouse_id": self.warehouse.id, + "location_id": self.location_child_1.id, + "expected_date": expected_date, + }, + ) + ], + } + order = self.request_order.with_user(self.stock_request_user).create(vals) + order.action_confirm() + self.assertEqual(order.stock_request_ids.state, "done") + self.assertEqual(order.state, "done") + self.assertEqual(len(order.stock_request_ids.move_ids), 1) + + def test_stock_request_order_available_stock_03(self): + self.main_company.stock_request_check_available_first = True + self.location_child_1 = self._create_location( + name="Child 1", + location_id=self.warehouse.lot_stock_id.id, + company_id=self.main_company.id, + ) + self.location_child_2 = self._create_location( + name="Child 2", + location_id=self.warehouse.lot_stock_id.id, + company_id=self.main_company.id, + ) + self._create_stock_quant(self.product, self.location_child_1, 3) + self._create_stock_quant(self.product, self.location_child_2, 2) + expected_date = fields.Datetime.now() + vals = { + "company_id": self.main_company.id, + "warehouse_id": self.warehouse.id, + "location_id": self.warehouse.lot_stock_id.id, + "expected_date": expected_date, + "stock_request_ids": [ + ( + 0, + 0, + { + "product_id": self.product.id, + "product_uom_id": self.product.uom_id.id, + "product_uom_qty": 5.0, + "company_id": self.main_company.id, + "warehouse_id": self.warehouse.id, + "location_id": self.warehouse.lot_stock_id.id, + "expected_date": expected_date, + }, + ) + ], + } + order = self.request_order.with_user(self.stock_request_user).create(vals) + order.action_confirm() + self.assertEqual(order.stock_request_ids.state, "done") + self.assertEqual(order.state, "done") + self.assertEqual(len(order.stock_request_ids.move_ids), 2) + def test_stock_request_validations_01(self): vals = { "product_id": self.product.id, diff --git a/stock_request/views/res_config_settings_views.xml b/stock_request/views/res_config_settings_views.xml index e27bf8d94..c2412a951 100644 --- a/stock_request/views/res_config_settings_views.xml +++ b/stock_request/views/res_config_settings_views.xml @@ -60,6 +60,20 @@ +
+
+ +
+
+
+

Purchases