From 0a25ecad611e772dd7fac71b837cb0471b5e4e16 Mon Sep 17 00:00:00 2001 From: Tom Blauwendraat Date: Sat, 15 Jun 2024 00:10:47 +0200 Subject: [PATCH 1/5] [ADD] extra test cases for picking validation - serial number product combined with regular product - backorder created on serial number validation --- .../tests/test_inter_company_purchase_sale.py | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/purchase_sale_inter_company/tests/test_inter_company_purchase_sale.py b/purchase_sale_inter_company/tests/test_inter_company_purchase_sale.py index cd183b3a956..379148567da 100644 --- a/purchase_sale_inter_company/tests/test_inter_company_purchase_sale.py +++ b/purchase_sale_inter_company/tests/test_inter_company_purchase_sale.py @@ -354,14 +354,16 @@ def test_sync_picking(self): so_picking_id.move_lines.product_qty, ) - so_picking_id.state = "done" + # Validate sale order, create backorder wizard_data = so_picking_id.with_user(self.user_company_b).button_validate() wizard = ( self.env["stock.backorder.confirmation"] .with_context(**wizard_data.get("context")) .create({}) ) - wizard.process() + wizard.with_user(self.user_company_b).process() + self.assertEqual(so_picking_id.state, "done") + self.assertNotEqual((sale.picking_ids - so_picking_id).state, "done") # Quantities should have been synced self.assertNotEqual(po_picking_id, so_picking_id) @@ -455,7 +457,8 @@ def test_sync_picking_lot(self): self.company_b.sync_picking = True purchase = self._create_purchase_order( - self.partner_company_b, self.stockable_product_serial + self.partner_company_b, + self.stockable_product_serial + self.consumable_product, ) sale = self._approve_po(purchase) @@ -463,14 +466,15 @@ def test_sync_picking_lot(self): po_picking_id = purchase.picking_ids so_picking_id = sale.picking_ids - so_move = so_picking_id.move_lines - so_move.move_line_ids = [ + so_moves = so_picking_id.move_lines + so_moves[1].quantity_done = 2 + so_moves[0].move_line_ids = [ ( 0, 0, { - "location_id": so_move.location_id.id, - "location_dest_id": so_move.location_dest_id.id, + "location_id": so_moves[0].location_id.id, + "location_dest_id": so_moves[0].location_dest_id.id, "product_id": self.stockable_product_serial.id, "product_uom_id": self.stockable_product_serial.uom_id.id, "qty_done": 1, @@ -482,21 +486,8 @@ def test_sync_picking_lot(self): 0, 0, { - "location_id": so_move.location_id.id, - "location_dest_id": so_move.location_dest_id.id, - "product_id": self.stockable_product_serial.id, - "product_uom_id": self.stockable_product_serial.uom_id.id, - "qty_done": 1, - "lot_id": self.serial_2.id, - "picking_id": so_picking_id.id, - }, - ), - ( - 0, - 0, - { - "location_id": so_move.location_id.id, - "location_dest_id": so_move.location_dest_id.id, + "location_id": so_moves[0].location_id.id, + "location_dest_id": so_moves[0].location_dest_id.id, "product_id": self.stockable_product_serial.id, "product_uom_id": self.stockable_product_serial.uom_id.id, "qty_done": 1, @@ -505,9 +496,17 @@ def test_sync_picking_lot(self): }, ), ] - so_picking_id.button_validate() + wizard_data = so_picking_id.with_user(self.user_company_b).button_validate() + wizard = ( + self.env["stock.backorder.confirmation"] + .with_context(**wizard_data.get("context")) + .create({}) + ) + wizard.with_user(self.user_company_b).process() + self.assertEqual(so_picking_id.state, "done") + self.assertNotEqual((sale.picking_ids - so_picking_id).state, "done") - so_lots = so_move.mapped("move_line_ids.lot_id") + so_lots = so_moves.mapped("move_line_ids.lot_id") po_lots = po_picking_id.mapped("move_lines.move_line_ids.lot_id") self.assertEqual( len(so_lots), @@ -518,8 +517,8 @@ def test_sync_picking_lot(self): so_lots, po_lots, msg="The lots of the moves should be different objects" ) self.assertEqual( - so_lots.mapped("name"), - po_lots.mapped("name"), + so_lots.sudo().mapped("name"), + po_lots.sudo().mapped("name"), msg="The lots should have the same name in both moves", ) self.assertIn( @@ -527,6 +526,9 @@ def test_sync_picking_lot(self): po_lots, msg="Serial 333 already existed, a new one shouldn't have been created", ) + # A backorder should have been made for both + self.assertTrue(len(sale.picking_ids) > 1) + self.assertEqual(len(purchase.picking_ids), len(sale.picking_ids)) def test_sync_picking_same_product_multiple_lines(self): """ From 17792b45bd09aa235a1ec6e34aaa236a334349f5 Mon Sep 17 00:00:00 2001 From: Tom Blauwendraat Date: Thu, 13 Jun 2024 17:16:04 +0200 Subject: [PATCH 2/5] [IMP] make picking state syncing configurable By default picking state is forced to Done when SO picking is done, but if picking sync fails, eg if there is a mismatch in SO and PO move lines, user can't correct anything since the picking is already set to Done. This may be acceptable in cases where 100% of the syncs go OK but we make the setting configurable to support situations where manual corrections are needed. --- purchase_sale_inter_company/models/res_company.py | 6 ++++++ purchase_sale_inter_company/models/res_config.py | 2 ++ purchase_sale_inter_company/models/stock_picking.py | 3 ++- purchase_sale_inter_company/views/res_config_view.xml | 6 ++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/purchase_sale_inter_company/models/res_company.py b/purchase_sale_inter_company/models/res_company.py index cab6b7e9f49..025cd16e7e6 100644 --- a/purchase_sale_inter_company/models/res_company.py +++ b/purchase_sale_inter_company/models/res_company.py @@ -46,6 +46,12 @@ class ResCompany(models.Model): string="On sync picking failure", default="raise", help="Pick action to perform on sync picking failure", + sync_picking_state = fields.Boolean( + string="Sync the receipt state with the delivery state", + default=lambda p: p.sync_picking, + help="State of receipt picking syncs with state of the delivery " + "from the source company. Note this disallows user to manually " + "correct or change a picking that did not sync properly.", ) block_po_manual_picking_validation = fields.Boolean( string="Block manual validation of picking in the destination company", diff --git a/purchase_sale_inter_company/models/res_config.py b/purchase_sale_inter_company/models/res_config.py index 3116b653a12..9a27d863b08 100644 --- a/purchase_sale_inter_company/models/res_config.py +++ b/purchase_sale_inter_company/models/res_config.py @@ -49,6 +49,8 @@ class InterCompanyRulesConfig(models.TransientModel): ) sync_picking_failure_action = fields.Selection( related="company_id.sync_picking_failure_action", + sync_picking_state = fields.Boolean( + related="company_id.sync_picking_state", readonly=False, ) block_po_manual_picking_validation = fields.Boolean( diff --git a/purchase_sale_inter_company/models/stock_picking.py b/purchase_sale_inter_company/models/stock_picking.py index 1df7f835fb9..409bd4f6f0c 100644 --- a/purchase_sale_inter_company/models/stock_picking.py +++ b/purchase_sale_inter_company/models/stock_picking.py @@ -21,7 +21,8 @@ def _compute_state(self): res = super()._compute_state() for picking in self: if ( - picking.intercompany_picking_id + picking.company_id.sync_picking_state + and picking.intercompany_picking_id and picking.picking_type_code == "incoming" and picking.state not in ["done", "cancel"] ): diff --git a/purchase_sale_inter_company/views/res_config_view.xml b/purchase_sale_inter_company/views/res_config_view.xml index 46f1f59ef0f..e8601893730 100644 --- a/purchase_sale_inter_company/views/res_config_view.xml +++ b/purchase_sale_inter_company/views/res_config_view.xml @@ -62,6 +62,12 @@ attrs="{'invisible': [('sync_picking_failure_action', '!=', 'notify')], 'required': [('sync_picking_failure_action', '=', 'notify')]}" class="oe_inline" /> + +