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 @@
+