From ca381bd3972810b8192515099e66f2be983ce6a0 Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Thu, 13 Apr 2023 20:41:35 +0200 Subject: [PATCH] prod_lot_seq: Fix sequence incrementation opening detailed operations When opening the detailed operations view of a stock move, Odoo is calling stock.production.lot._get_next_serial to set stock.move.next_serial field anytime the product is tracked by serial and the move is assigned. If we use this module with product or global policy, the respective sequence will therefore be called and incremented anytime this view is open, even if the picking is not creating lots (e.g. delivery orders or internal transfers) To avoid incrementing the sequence unnecessarily, we only allow to get the next sequence number the first time this view is opened and if the move has to create new serial numbers. Otherwise, we force the value to be set to stock.move.next_serial field to an empty string if no serial has to be created through this move, or to the next_serial value assigned on the first opening of the view. --- product_lot_sequence/models/__init__.py | 1 + product_lot_sequence/models/stock_move.py | 23 +++++++++ .../models/stock_production_lot.py | 2 + .../tests/test_product_lot_sequence.py | 49 +++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 product_lot_sequence/models/stock_move.py diff --git a/product_lot_sequence/models/__init__.py b/product_lot_sequence/models/__init__.py index ec4ec034fcb8..9bfdbf927d8a 100644 --- a/product_lot_sequence/models/__init__.py +++ b/product_lot_sequence/models/__init__.py @@ -1,2 +1,3 @@ from . import product from . import stock_production_lot +from . import stock_move diff --git a/product_lot_sequence/models/stock_move.py b/product_lot_sequence/models/stock_move.py new file mode 100644 index 000000000000..c95ef7e1dc68 --- /dev/null +++ b/product_lot_sequence/models/stock_move.py @@ -0,0 +1,23 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import models + + +class StockMove(models.Model): + _inherit = "stock.move" + + def action_show_details(self): + """Avoid calling and incrementing the sequence if not needed or already done""" + seq_policy = self.env["stock.production.lot"]._get_sequence_policy() + if seq_policy in ("product", "global"): + # If move is not supposed to assign serial pass empty string for next serial + if not self.display_assign_serial: + return super( + StockMove, self.with_context(force_next_serial="") + ).action_show_details() + # If the sequence was already called once, avoid calling it another time + elif self.next_serial: + return super( + StockMove, self.with_context(force_next_serial=self.next_serial) + ).action_show_details() + return super().action_show_details() diff --git a/product_lot_sequence/models/stock_production_lot.py b/product_lot_sequence/models/stock_production_lot.py index 42cf12fd4088..0b07712befae 100644 --- a/product_lot_sequence/models/stock_production_lot.py +++ b/product_lot_sequence/models/stock_production_lot.py @@ -56,6 +56,8 @@ def create(self, vals_list): @api.model def _get_next_serial(self, company, product): + if "force_next_serial" in self.env.context: + return self.env.context.get("force_next_serial") seq_policy = self._get_sequence_policy() if seq_policy == "product": seq = product.product_tmpl_id.lot_sequence_id diff --git a/product_lot_sequence/tests/test_product_lot_sequence.py b/product_lot_sequence/tests/test_product_lot_sequence.py index 9dfe55a969d2..6c6d8a4de084 100644 --- a/product_lot_sequence/tests/test_product_lot_sequence.py +++ b/product_lot_sequence/tests/test_product_lot_sequence.py @@ -9,6 +9,22 @@ def setUp(self): super(TestProductLotSequence, self).setUp() self.product_product = self.env["product.product"] self.stock_production_lot = self.env["stock.production.lot"] + self.receipt_type = self.env.ref("stock.picking_type_in") + self.delivery_type = self.env.ref("stock.picking_type_out") + + def _create_picking(self, picking_type, move_vals_list): + picking_form = Form(self.env["stock.picking"]) + picking_form.picking_type_id = picking_type + for move_vals in move_vals_list: + with picking_form.move_ids_without_package.new() as move_form: + move_form.product_id = move_vals.get("product_id") + move_form.product_uom_qty = move_vals.get("product_uom_qty", 1.0) + move_form.product_uom = move_vals.get( + "product_uom", self.env.ref("uom.product_uom_unit") + ) + picking = picking_form.save() + picking.action_confirm() + return picking def test_product_sequence(self): self.assertEqual(self.stock_production_lot._get_sequence_policy(), "product") @@ -88,3 +104,36 @@ def test_lot_onchange_product_id_global(self): lot_form.product_id = product lot = lot_form.save() self.assertEqual(lot.name, next_sequence_number) + + def test_open_detailed_operations(self): + self.env["ir.config_parameter"].set_param( + "product_lot_sequence.policy", "global" + ) + seq = self.env["ir.sequence"].search([("code", "=", "stock.lot.serial")]) + first_next_sequence_number = seq.get_next_char(seq.number_next_actual) + product = self.product_product.create( + {"name": "Test global", "tracking": "serial"} + ) + delivery_picking = self._create_picking( + self.delivery_type, [{"product_id": product}] + ) + delivery_move = delivery_picking.move_lines + self.assertFalse(delivery_move.next_serial) + delivery_move.action_show_details() + self.assertFalse(delivery_move.next_serial) + self.assertEqual( + seq.get_next_char(seq.number_next_actual), first_next_sequence_number + ) + receipt_picking = self._create_picking( + self.receipt_type, [{"product_id": product}] + ) + receipt_move = receipt_picking.move_lines + self.assertFalse(receipt_move.next_serial) + receipt_move.action_show_details() + self.assertEqual(receipt_move.next_serial, first_next_sequence_number) + new_next_sequence_number = seq.get_next_char(seq.number_next_actual) + self.assertNotEqual(new_next_sequence_number, first_next_sequence_number) + receipt_move.action_show_details() + self.assertEqual( + new_next_sequence_number, seq.get_next_char(seq.number_next_actual) + )