diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index 6731c0fe05d..06628269a97 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -88,6 +88,13 @@ class PickingType(models.Model): company_id = fields.Many2one( 'res.company', 'Company', required=True, default=lambda s: s.env.company.id, index=True) + create_backorder = fields.Selection( + [('ask', 'Ask'), ('always', 'Always'), ('never', 'Never')], + 'Create Backorder', required=True, default='ask', + help="When validating a transfer:\n" + " * Ask: users are asked to choose if they want to make a backorder for remaining products\n" + " * Always: a backorder is automatically created for the remaining products\n" + " * Never: remaining products are cancelled") @api.model_create_multi def create(self, vals_list): @@ -1010,12 +1017,10 @@ class Picking(models.Model): return res # Call `_action_done`. + pickings_not_to_backorder = self.filtered(lambda p: p.picking_type_id.create_backorder == 'never') if self.env.context.get('picking_ids_not_to_backorder'): - pickings_not_to_backorder = self.browse(self.env.context['picking_ids_not_to_backorder']) - pickings_to_backorder = self - pickings_not_to_backorder - else: - pickings_not_to_backorder = self.env['stock.picking'] - pickings_to_backorder = self + pickings_not_to_backorder |= self.browse(self.env.context['picking_ids_not_to_backorder']) + pickings_to_backorder = self - pickings_not_to_backorder pickings_not_to_backorder.with_context(cancel_backorder=True)._action_done() pickings_to_backorder.with_context(cancel_backorder=False)._action_done() @@ -1106,6 +1111,8 @@ class Picking(models.Model): prec = self.env["decimal.precision"].precision_get("Product Unit of Measure") backorder_pickings = self.browse() for picking in self: + if picking.picking_type_id.create_backorder != 'ask': + continue quantity_todo = {} quantity_done = {} for move in picking.move_ids.filtered(lambda m: m.state != "cancel"): diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py index b299c19bc94..be93963fbff 100644 --- a/addons/stock/tests/test_stock_flow.py +++ b/addons/stock/tests/test_stock_flow.py @@ -1670,7 +1670,7 @@ class TestStockFlow(TestStockCommon): backorder_wizard.process_cancel_backorder() # Checking that no backorders were attached to the picking - self.assertFalse(picking.backorder_id) + self.assertFalse(picking.backorder_ids) # Checking that the original move is still in the same picking self.assertEqual(move_a.picking_id.id, picking.id) @@ -1690,6 +1690,73 @@ class TestStockFlow(TestStockCommon): # Checking that the canceled move is in the original picking self.assertIn(move_canceled.id, picking.move_ids.mapped('id')) + def test_backorder_setting(self): + """Create 3 pickings with each has different backorder settings on its + picking type. Check the backorder behavior for each picking. + """ + picking_type_ask = self.env['stock.picking.type'].browse(self.picking_type_in) + picking_type_ask.create_backorder = 'ask' + picking_type_always = picking_type_ask.copy({ + 'sequence_code': 'always', + 'create_backorder': 'always', + }) + picking_type_never = picking_type_ask.copy({ + 'sequence_code': 'never', + 'create_backorder': 'never', + }) + + picking_ask = self.PickingObj.create({ + 'picking_type_id': picking_type_ask.id, + 'location_id': self.supplier_location, + 'location_dest_id': self.stock_location}) + self.MoveObj.create({ + 'name': self.productA.name, + 'product_id': self.productA.id, + 'product_uom_qty': 10, + 'product_uom': self.productA.uom_id.id, + 'picking_id': picking_ask.id, + 'location_id': self.supplier_location, + 'location_dest_id': self.stock_location}) + picking_always = self.PickingObj.create({ + 'picking_type_id': picking_type_always.id, + 'location_id': self.supplier_location, + 'location_dest_id': self.stock_location}) + self.MoveObj.create({ + 'name': self.productA.name, + 'product_id': self.productA.id, + 'product_uom_qty': 10, + 'product_uom': self.productA.uom_id.id, + 'picking_id': picking_always.id, + 'location_id': self.supplier_location, + 'location_dest_id': self.stock_location}) + picking_never = self.PickingObj.create({ + 'picking_type_id': picking_type_never.id, + 'location_id': self.supplier_location, + 'location_dest_id': self.stock_location}) + self.MoveObj.create({ + 'name': self.productA.name, + 'product_id': self.productA.id, + 'product_uom_qty': 10, + 'product_uom': self.productA.uom_id.id, + 'picking_id': picking_never.id, + 'location_id': self.supplier_location, + 'location_dest_id': self.stock_location}) + + pickings = picking_ask | picking_always | picking_never + pickings.action_confirm() + # Only 4 items are processed in each picking + pickings.move_ids.move_line_ids.qty_done = 4 + res_dict = pickings.button_validate() + backorder_wizard = Form(self.env['stock.backorder.confirmation'].with_context(res_dict['context'])).save() + + self.assertEqual(backorder_wizard.pick_ids, picking_ask, "Only ask backorder for picking with setting 'set'") + backorder_wizard.process_cancel_backorder() + + self.assertEqual(pickings.mapped('state'), ['done', 'done', 'done']) + self.assertFalse(picking_ask.backorder_ids) + self.assertTrue(picking_always.backorder_ids, "Picking with setting 'always' should have backorder created") + self.assertFalse(picking_never.backorder_ids, "Picking with setting 'never' should have no backorder created") + def test_transit_multi_companies(self): """ Ensure that inter company rules set the correct company on picking and their moves. diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml index 01f32e8548b..6cd8755297f 100644 --- a/addons/stock/views/stock_picking_views.xml +++ b/addons/stock/views/stock_picking_views.xml @@ -56,6 +56,7 @@ +