From cdfcf6d88fff0d366534f1145b8c200595df1498 Mon Sep 17 00:00:00 2001 From: Michael Tietz Date: Tue, 9 Jul 2024 17:43:17 +0200 Subject: [PATCH] [IMP] shipment_advice: Auto close incoming advice If a move is done or cancelled the related incoming shipment advice will be set to done if all planned moves are done or cancelled Co-authored-by: vnikolayev1 --- shipment_advice/models/res_company.py | 9 ++++ shipment_advice/models/res_config_settings.py | 4 ++ shipment_advice/models/shipment_advice.py | 33 ++++++++++++- shipment_advice/models/stock_move.py | 11 +++++ shipment_advice/readme/CONTRIBUTORS.rst | 1 + shipment_advice/tests/__init__.py | 1 + shipment_advice/tests/common.py | 16 ++++++- .../tests/test_shipment_advice_auto_close.py | 46 +++++++++++++++++++ shipment_advice/views/res_config_settings.xml | 14 ++++++ 9 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 shipment_advice/tests/test_shipment_advice_auto_close.py diff --git a/shipment_advice/models/res_company.py b/shipment_advice/models/res_company.py index df8c1ad04..132e21c0d 100644 --- a/shipment_advice/models/res_company.py +++ b/shipment_advice/models/res_company.py @@ -1,4 +1,5 @@ # Copyright 2021 Camptocamp SA +# Copyright 2024 Michael Tietz (MT Software) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo import fields, models @@ -23,3 +24,11 @@ class ResCompany(models.Model): "deliveries will be shipped by several trucks." ), ) + shipment_advice_auto_close_incoming = fields.Boolean( + string="Shipment Advice: Auto Close Incoming Advices", + help=( + "This flag indicates if an incoming shipment advice " + "will be automatically set to done " + "if all related moves are done or cancelled" + ), + ) diff --git a/shipment_advice/models/res_config_settings.py b/shipment_advice/models/res_config_settings.py index 1bb6a2417..67bf99a8d 100644 --- a/shipment_advice/models/res_config_settings.py +++ b/shipment_advice/models/res_config_settings.py @@ -1,4 +1,5 @@ # Copyright 2021 Camptocamp SA +# Copyright 2024 Michael Tietz (MT Software) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo import fields, models @@ -10,3 +11,6 @@ class ResConfigSettings(models.TransientModel): shipment_advice_outgoing_backorder_policy = fields.Selection( related="company_id.shipment_advice_outgoing_backorder_policy", readonly=False ) + shipment_advice_auto_close_incoming = fields.Boolean( + related="company_id.shipment_advice_auto_close_incoming", readonly=False + ) diff --git a/shipment_advice/models/shipment_advice.py b/shipment_advice/models/shipment_advice.py index bfa8f368e..ca4b7f7cc 100644 --- a/shipment_advice/models/shipment_advice.py +++ b/shipment_advice/models/shipment_advice.py @@ -1,4 +1,5 @@ # Copyright 2021 Camptocamp SA +# Copyright 2024 Michael Tietz (MT Software) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo import _, api, fields, models @@ -381,6 +382,8 @@ def _lock_records(self, records): def action_done(self): wiz_model = self.env["stock.backorder.confirmation"] + shipment_advice_ids_to_validate = [] + self = self.with_context(shipment_advice_ignore_auto_close=True) for shipment in self: if shipment.state != "in_progress": raise UserError( @@ -432,10 +435,36 @@ def action_done(self): lambda m: m.state not in ("cancel", "done") and not m.quantity_done ) moves_to_unplan.shipment_advice_id = False - shipment.departure_date = fields.Datetime.now() - shipment.state = "done" + shipment_advice_ids_to_validate.append(shipment.id) + if shipment_advice_ids_to_validate: + self.browse(shipment_advice_ids_to_validate)._action_done() return True + def _action_done(self): + self.write({"departure_date": fields.Datetime.now(), "state": "done"}) + + def _is_fully_done(self): + self.ensure_one() + if any(move.state not in ["cancel", "done"] for move in self.planned_move_ids): + return False + return True + + def auto_close_incoming_shipment_advices(self): + """Set incoming shipment advices to done when they all moves done or cancelled""" + if self.env.context.get("shipment_advice_ignore_auto_close"): + return + shipment_ids_to_close = [] + for shipment_advice in self: + if ( + shipment_advice.shipment_type != "incoming" + or not shipment_advice.company_id.shipment_advice_auto_close_incoming + or not shipment_advice._is_fully_done() + ): + continue + shipment_ids_to_close.append(shipment_advice.id) + if shipment_ids_to_close: + self.browse(shipment_ids_to_close)._action_done() + def action_cancel(self): for shipment in self: if shipment.state not in ("confirmed", "in_progress"): diff --git a/shipment_advice/models/stock_move.py b/shipment_advice/models/stock_move.py index 0fe033d64..942a6ce67 100644 --- a/shipment_advice/models/stock_move.py +++ b/shipment_advice/models/stock_move.py @@ -1,4 +1,5 @@ # Copyright 2021 Camptocamp SA +# Copyright 2024 Michael Tietz (MT Software) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo import fields, models @@ -23,3 +24,13 @@ def _prepare_merge_moves_distinct_fields(self): # Avoid having stock move assign to different shipment merged together res.append("shipment_advice_id") return res + + def _action_done(self, cancel_backorder=False): + res = super()._action_done(cancel_backorder=cancel_backorder) + res.shipment_advice_id.auto_close_incoming_shipment_advices() + return res + + def _action_cancel(self): + res = super()._action_cancel() + self.shipment_advice_id.auto_close_incoming_shipment_advices() + return res diff --git a/shipment_advice/readme/CONTRIBUTORS.rst b/shipment_advice/readme/CONTRIBUTORS.rst index 09f87c8c8..67ff68bce 100644 --- a/shipment_advice/readme/CONTRIBUTORS.rst +++ b/shipment_advice/readme/CONTRIBUTORS.rst @@ -3,6 +3,7 @@ * Simone Orsi * `Trobz `_: * Dung Tran +* Michael Tietz (MT Software) Design ~~~~~~ diff --git a/shipment_advice/tests/__init__.py b/shipment_advice/tests/__init__.py index 197236b61..6152d14fa 100644 --- a/shipment_advice/tests/__init__.py +++ b/shipment_advice/tests/__init__.py @@ -5,3 +5,4 @@ from . import test_shipment_advice_picking_values from . import test_shipment_advice_unload from . import test_shipment_advice_stock_user +from . import test_shipment_advice_auto_close diff --git a/shipment_advice/tests/common.py b/shipment_advice/tests/common.py index 81bd7eec6..50376737a 100644 --- a/shipment_advice/tests/common.py +++ b/shipment_advice/tests/common.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo import fields -from odoo.tests.common import SavepointCase, new_test_user +from odoo.tests.common import Form, SavepointCase, new_test_user class Common(SavepointCase): @@ -202,3 +202,17 @@ def _unload_records_from_shipment(cls, shipment_advice, records): wiz = wiz_model.create({}) wiz.action_unload() return wiz + + @classmethod + def _validate_picking(cls, picking, qty_done=None): + picking.ensure_one() + for ml in picking.move_line_ids: + ml.qty_done = qty_done or ml.product_uom_qty + action_data = picking.button_validate() + if action_data is True: + return cls.env["stock.picking"] + backorder_wizard = Form( + cls.env["stock.backorder.confirmation"].with_context(action_data["context"]) + ).save() + backorder_wizard.process() + return cls.env["stock.picking"].search([("backorder_id", "=", picking.id)]) diff --git a/shipment_advice/tests/test_shipment_advice_auto_close.py b/shipment_advice/tests/test_shipment_advice_auto_close.py new file mode 100644 index 000000000..bdb46f7bc --- /dev/null +++ b/shipment_advice/tests/test_shipment_advice_auto_close.py @@ -0,0 +1,46 @@ +# Copyright 2024 Michael Tietz (MT Software) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from .common import Common + + +class TestShipmentAdviceAutoClose(Common): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.shipment_advice_in.company_id.shipment_advice_auto_close_incoming = True + cls.picking1 = cls.move_product_in1.picking_id + group = cls.env["procurement.group"].create({}) + cls.move_product_in21 = cls._create_move( + cls.picking_type_in, cls.product_in, 5, group + ) + cls.picking2 = cls.move_product_in21.picking_id + cls.pickings = cls.picking1 | cls.picking2 + cls._plan_records_in_shipment(cls.shipment_advice_in, cls.pickings) + cls._in_progress_shipment_advice(cls.shipment_advice_in) + + def test_auto_close_incoming_on_done(self): + self._validate_picking(self.picking1) + self.assertEqual(self.shipment_advice_in.state, "in_progress") + self._validate_picking(self.picking2) + self.assertEqual(self.shipment_advice_in.state, "done") + + def test_auto_close_incoming_on_cancel(self): + self._validate_picking(self.picking1) + self.assertEqual(self.shipment_advice_in.state, "in_progress") + self.picking2.action_cancel() + self.assertEqual(self.shipment_advice_in.state, "done") + + def test_no_auto_close_on_outgoing(self): + picking = self.move_product_out1.picking_id + self._plan_records_in_shipment(self.shipment_advice_out, picking) + self._in_progress_shipment_advice(self.shipment_advice_out) + self._validate_picking(picking) + self.assertEqual(picking.state, "done") + self.assertEqual(self.shipment_advice_out.state, "in_progress") + + def test_no_auto_close_context(self): + pickings = self.pickings.with_context(shipment_advice_ignore_auto_close=True) + for picking in pickings: + self._validate_picking(picking) + self.assertEqual(self.shipment_advice_in.state, "in_progress") diff --git a/shipment_advice/views/res_config_settings.xml b/shipment_advice/views/res_config_settings.xml index 2b747f386..702549837 100644 --- a/shipment_advice/views/res_config_settings.xml +++ b/shipment_advice/views/res_config_settings.xml @@ -1,5 +1,6 @@ @@ -27,6 +28,19 @@ +
+
+ +
+
+
+