From 9f5c68498f5604fac97bc836857d7fbfd48eb33b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Wed, 23 Oct 2024 09:54:54 +0200 Subject: [PATCH] [IMP] mrp_subcontracting_skip_no_negative: Show negative stock error of subcontracting component TT50668 --- .../README.rst | 4 + .../i18n/es.po | 43 +++++++ .../mrp_subcontracting_skip_no_negative.pot | 13 +++ .../models/stock_move.py | 40 ++++++- .../readme/CONTRIBUTORS.rst | 4 + .../static/description/index.html | 4 + ...est_mrp_subcontracting_skip_no_negative.py | 106 +++++++++++++----- 7 files changed, 182 insertions(+), 32 deletions(-) create mode 100644 mrp_subcontracting_skip_no_negative/i18n/es.po diff --git a/mrp_subcontracting_skip_no_negative/README.rst b/mrp_subcontracting_skip_no_negative/README.rst index 527110e6e..ea30153d7 100644 --- a/mrp_subcontracting_skip_no_negative/README.rst +++ b/mrp_subcontracting_skip_no_negative/README.rst @@ -76,6 +76,10 @@ Contributors * Aung Ko Ko Lin +* `Tecnativa `_: + + * Víctor Martínez + Maintainers ~~~~~~~~~~~ diff --git a/mrp_subcontracting_skip_no_negative/i18n/es.po b/mrp_subcontracting_skip_no_negative/i18n/es.po new file mode 100644 index 000000000..1aa15554e --- /dev/null +++ b/mrp_subcontracting_skip_no_negative/i18n/es.po @@ -0,0 +1,43 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mrp_subcontracting_skip_no_negative +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-11-05 07:53+0000\n" +"PO-Revision-Date: 2024-11-05 08:53+0100\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: \n" +"X-Generator: Poedit 3.0.1\n" + +#. module: mrp_subcontracting_skip_no_negative +#: model:ir.model,name:mrp_subcontracting_skip_no_negative.model_stock_move +msgid "Stock Move" +msgstr "Movimiento de existencias" + +#. module: mrp_subcontracting_skip_no_negative +#: model:ir.model,name:mrp_subcontracting_skip_no_negative.model_stock_picking +msgid "Transfer" +msgstr "Transferencia" + +#. module: mrp_subcontracting_skip_no_negative +#. odoo-python +#: code:addons/mrp_subcontracting_skip_no_negative/models/stock_move.py:0 +#, python-format +msgid "" +"You cannot validate this stock operation because the stock level of the " +"component product '{name}' would become negative ({qty}) on the stock " +"location '{location}' and negative stock is not allowed for this product and/" +"or location." +msgstr "" +"No se puede validar esta operación de stock porque el nivel de stock del " +"producto componente '{name}' se volvería negativo ({qty}) en la ubicación de " +"stock '{location}' y no se permite stock negativo para este producto y/o " +"ubicación." diff --git a/mrp_subcontracting_skip_no_negative/i18n/mrp_subcontracting_skip_no_negative.pot b/mrp_subcontracting_skip_no_negative/i18n/mrp_subcontracting_skip_no_negative.pot index ce747ae41..b004e9458 100644 --- a/mrp_subcontracting_skip_no_negative/i18n/mrp_subcontracting_skip_no_negative.pot +++ b/mrp_subcontracting_skip_no_negative/i18n/mrp_subcontracting_skip_no_negative.pot @@ -6,6 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-11-05 07:53+0000\n" +"PO-Revision-Date: 2024-11-05 07:53+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -22,3 +24,14 @@ msgstr "" #: model:ir.model,name:mrp_subcontracting_skip_no_negative.model_stock_picking msgid "Transfer" msgstr "" + +#. module: mrp_subcontracting_skip_no_negative +#. odoo-python +#: code:addons/mrp_subcontracting_skip_no_negative/models/stock_move.py:0 +#, python-format +msgid "" +"You cannot validate this stock operation because the stock level of the " +"component product '{name}' would become negative ({qty}) on the stock " +"location '{location}' and negative stock is not allowed for this product " +"and/or location." +msgstr "" diff --git a/mrp_subcontracting_skip_no_negative/models/stock_move.py b/mrp_subcontracting_skip_no_negative/models/stock_move.py index 35a108879..65638222d 100644 --- a/mrp_subcontracting_skip_no_negative/models/stock_move.py +++ b/mrp_subcontracting_skip_no_negative/models/stock_move.py @@ -1,7 +1,10 @@ # Copyright 2023 Quartile Limited +# Copyright 2024 Tecnativa - Víctor Martínez # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import models +from odoo import _, models +from odoo.exceptions import ValidationError +from odoo.tools import config, float_compare class StockMove(models.Model): @@ -31,6 +34,41 @@ class StockMove(models.Model): ): continue moves_with_no_check -= move + # If you have not been able to allocate previously it is because there is + # no stock, therefore it will leave the stock negative, we deduct the + # quantity checking the components and show the corresponding error. + test_condition = ( + config["test_enable"] and self.env.context.get("test_stock_no_negative") + ) or not config["test_enable"] + if not test_condition: + continue + qty_precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) + for p_move in unassigned_productions.move_raw_ids.filtered( + lambda x: x.state != "assigned" + and not x.product_id.allow_negative_stock + and not x.product_id.categ_id.allow_negative_stock + and not x.location_id.allow_negative_stock + ): + product = p_move.product_id.sudo() + location = p_move.location_id + location_qty = product.with_context(location=location.id).free_qty + new_qty = location_qty - p_move.product_uom_qty + if float_compare(new_qty, 0, precision_digits=qty_precision) == -1: + raise ValidationError( + _( + "You cannot validate this stock operation because the " + "stock level of the component product '{name}' would become " + "negative ({qty}) on the stock location '{location}' and " + "negative stock is not allowed for this product and/or " + "location." + ).format( + name=product.display_name, + qty=new_qty, + location=location.complete_name, + ) + ) res = super(StockMove, self - moves_with_no_check)._action_done( cancel_backorder=cancel_backorder ) diff --git a/mrp_subcontracting_skip_no_negative/readme/CONTRIBUTORS.rst b/mrp_subcontracting_skip_no_negative/readme/CONTRIBUTORS.rst index cd4e44ca9..4a81d357e 100644 --- a/mrp_subcontracting_skip_no_negative/readme/CONTRIBUTORS.rst +++ b/mrp_subcontracting_skip_no_negative/readme/CONTRIBUTORS.rst @@ -1,3 +1,7 @@ * `Quartile `__: * Aung Ko Ko Lin + +* `Tecnativa `_: + + * Víctor Martínez diff --git a/mrp_subcontracting_skip_no_negative/static/description/index.html b/mrp_subcontracting_skip_no_negative/static/description/index.html index cde0c7112..42719533a 100644 --- a/mrp_subcontracting_skip_no_negative/static/description/index.html +++ b/mrp_subcontracting_skip_no_negative/static/description/index.html @@ -413,6 +413,10 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
  • Aung Ko Ko Lin
  • +
  • Tecnativa:
      +
    • Víctor Martínez
    • +
    +
  • diff --git a/mrp_subcontracting_skip_no_negative/tests/test_mrp_subcontracting_skip_no_negative.py b/mrp_subcontracting_skip_no_negative/tests/test_mrp_subcontracting_skip_no_negative.py index 46e6aa97c..c6698572c 100644 --- a/mrp_subcontracting_skip_no_negative/tests/test_mrp_subcontracting_skip_no_negative.py +++ b/mrp_subcontracting_skip_no_negative/tests/test_mrp_subcontracting_skip_no_negative.py @@ -1,50 +1,94 @@ # Copyright 2023 Quartile Limited +# Copyright 2024 Tecnativa - Víctor Martínez # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo.exceptions import ValidationError from odoo.tests import Form +from odoo.tools import mute_logger from odoo.addons.mrp_subcontracting.tests.common import TestMrpSubcontractingCommon class TestMrpSubcontractingSkipNoNegative(TestMrpSubcontractingCommon): - def test_mrp_subcontracting_skip_no_negative(self): - picking_form = Form(self.env["stock.picking"]) - picking_form.picking_type_id = self.env.ref("stock.picking_type_in") - picking_form.partner_id = self.subcontractor_partner1 - with picking_form.move_ids_without_package.new() as move: - move.product_id = self.finished - move.product_uom_qty = 1 - subcontracting_receipt = picking_form.save() - subcontracting_receipt = subcontracting_receipt.with_context( - test_stock_no_negative=True + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env( + context=dict( + cls.env.context, + test_stock_no_negative=True, + ) ) - subcontracting_receipt.action_confirm() - self.assertEqual(subcontracting_receipt.state, "assigned") - immediate_wizard = subcontracting_receipt.button_validate() + picking_form = Form(cls.env["stock.picking"]) + picking_form.picking_type_id = cls.env.ref("stock.picking_type_in") + picking_form.partner_id = cls.subcontractor_partner1 + with picking_form.move_ids_without_package.new() as move: + move.product_id = cls.finished + move.product_uom_qty = 1 + cls.subcontracting_receipt = picking_form.save() + + def _create_stock_quant(self, product, qty): + self.env["stock.quant"].create( + { + "product_id": product.id, + "location_id": self.subcontractor_partner1.property_stock_subcontractor.id, + "quantity": qty, + } + ) + + @mute_logger("odoo.models.unlink") + def test_mrp_subcontracting_skip_no_negative_01(self): + self.subcontracting_receipt.action_confirm() + self.assertEqual(self.subcontracting_receipt.state, "assigned") + immediate_wizard = self.subcontracting_receipt.sudo().button_validate() self.assertEqual(immediate_wizard.get("res_model"), "stock.immediate.transfer") immediate_wizard_form = Form( self.env[immediate_wizard["res_model"]].with_context( **immediate_wizard["context"] ) ).save() - with self.assertRaises(ValidationError): + # Component1 error + with self.assertRaises(ValidationError) as e1: immediate_wizard_form.process() - - # Create component stock, and subcontracting receipt should now be successful. - self.env["stock.quant"].create( - { - "product_id": self.comp1.id, - "location_id": self.subcontractor_partner1.property_stock_subcontractor.id, - "quantity": 10, - } - ) - self.env["stock.quant"].create( - { - "product_id": self.comp2.id, - "location_id": self.subcontractor_partner1.property_stock_subcontractor.id, - "quantity": 10, - } - ) + self.assertIn("Component1", str(e1.exception)) + # Create comp1 stock, and try subcontracting receipt process. + self._create_stock_quant(self.comp1, 10) + # Component2 error + with self.assertRaises(ValidationError) as e2: + immediate_wizard_form.process() + self.assertIn("Component2", str(e2.exception)) + # Create comp2 stock, and subcontracting receipt should now be successful. + self._create_stock_quant(self.comp2, 10) immediate_wizard_form.process() - self.assertEqual(subcontracting_receipt.state, "done") + self.assertEqual(self.subcontracting_receipt.state, "done") + + def test_mrp_subcontracting_skip_no_negative_03(self): + self._create_stock_quant(self.comp1, 10) + self._create_stock_quant(self.comp2, 10) + self.subcontracting_receipt.action_confirm() + self.assertEqual(self.subcontracting_receipt.state, "assigned") + immediate_wizard = self.subcontracting_receipt.sudo().button_validate() + self.assertEqual(immediate_wizard.get("res_model"), "stock.immediate.transfer") + immediate_wizard_form = Form( + self.env[immediate_wizard["res_model"]].with_context( + **immediate_wizard["context"] + ) + ).save() + immediate_wizard_form.process() + self.assertEqual(self.subcontracting_receipt.state, "done") + + def test_mrp_subcontracting_skip_no_negative_04(self): + self.subcontractor_partner1.property_stock_subcontractor.allow_negative_stock = ( + True + ) + self.subcontracting_receipt.action_confirm() + self.assertEqual(self.subcontracting_receipt.state, "assigned") + immediate_wizard = self.subcontracting_receipt.sudo().button_validate() + self.assertEqual(immediate_wizard.get("res_model"), "stock.immediate.transfer") + immediate_wizard_form = Form( + self.env[immediate_wizard["res_model"]].with_context( + **immediate_wizard["context"] + ) + ).save() + immediate_wizard_form.process() + self.assertEqual(self.subcontracting_receipt.state, "done")