From c7861ab37acebef4ac8761ef412711c8e4d65018 Mon Sep 17 00:00:00 2001 From: Tom Blauwendraat Date: Tue, 18 Jun 2024 15:44:32 +0200 Subject: [PATCH] [FIX] purchase_sale_inter_company: support 'no backorder' When an incomplete sale order is delivered and "No backorder" is chosen, SO moves split in two while PO stays the same. Aggregating writes per each PO move makes sure qty does not get overwritten. Usually second qty is zero so the destination move becomes quantity zero and the picking was even cancelled. --- .../models/stock_picking.py | 12 ++-- .../tests/test_inter_company_purchase_sale.py | 62 +++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/purchase_sale_inter_company/models/stock_picking.py b/purchase_sale_inter_company/models/stock_picking.py index 8ad85c5c085..5d3cd07952b 100644 --- a/purchase_sale_inter_company/models/stock_picking.py +++ b/purchase_sale_inter_company/models/stock_picking.py @@ -129,6 +129,7 @@ def _sync_receipt_with_delivery(self, dest_company, sale_order): raise UserError(_("PO does not exist or has no receipts")) if self.intercompany_picking_id: dest_picking = self.intercompany_picking_id.with_user(intercompany_user.id) + dest_move_qty_update_dict = {} for move in self.move_ids_without_package.sudo(): # To identify the correct move to write to, # use both the SO-PO link and the intercompany_picking_id link @@ -143,11 +144,12 @@ def _sync_receipt_with_delivery(self, dest_company, sale_order): "qty_done": line.qty_done, } ) - dest_move.write( - { - "quantity_done": move.quantity_done, - } - ) + dest_move_qty_update_dict.setdefault(dest_move, 0.0) + dest_move_qty_update_dict[dest_move] += move.quantity_done + # "No backorder" case splits SO moves in two while PO stays the same. + # Aggregating writes per each PO move makes sure qty does not get overwritten + for dest_move, qty_done in dest_move_qty_update_dict.items(): + dest_move.quantity_done = qty_done dest_picking.sudo().with_context( cancel_backorder=bool( self.env.context.get("picking_ids_not_to_backorder") 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 e74d53de261..d0f07071406 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 @@ -377,6 +377,68 @@ def test_sync_picking(self): self.assertTrue(len(sale.picking_ids) > 1) self.assertEqual(len(purchase.picking_ids), len(sale.picking_ids)) + def test_sync_picking_no_backorder(self): + self.company_a.sync_picking = True + self.company_b.sync_picking = True + + purchase = self._create_purchase_order( + self.partner_company_b, self.consumable_product + ) + sale = self._approve_po(purchase) + + self.assertTrue(purchase.picking_ids) + self.assertTrue(sale.picking_ids) + + po_picking_id = purchase.picking_ids + so_picking_id = sale.picking_ids + + # check po_picking state + self.assertEqual(po_picking_id.state, "waiting") + + # validate the SO picking + so_picking_id.move_lines.quantity_done = 2 + + self.assertNotEqual(po_picking_id, so_picking_id) + self.assertNotEqual( + po_picking_id.move_lines.quantity_done, + so_picking_id.move_lines.quantity_done, + ) + self.assertEqual( + po_picking_id.move_lines.product_qty, + so_picking_id.move_lines.product_qty, + ) + + # No 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.with_user(self.user_company_b).process_cancel_backorder() + self.assertEqual(so_picking_id.state, "done") + self.assertEqual(po_picking_id.state, "done") + + # Quantity done should be the same on both sides, per product + self.assertNotEqual(po_picking_id, so_picking_id) + for product in so_picking_id.move_lines.mapped("product_id"): + self.assertEqual( + sum( + so_picking_id.move_lines.filtered( + lambda l: l.product_id == product + ).mapped("quantity_done") + ), + sum( + po_picking_id.move_lines.filtered( + lambda l: l.product_id == product + ).mapped("quantity_done") + ), + ) + + # No backorder should have been made for both + self.assertEqual(len(sale.picking_ids), 1) + self.assertEqual(len(purchase.picking_ids), len(sale.picking_ids)) + def test_sync_picking_lot(self): """ Test that the lot is synchronized on the moves