[IMP] stock_picking_batch: ask for qty done and backorder

Previsously picking batch done button automatically run backorder
for picking that need and set a quantity done for picking without it

This commit changes the function done for picking wave in order to
call the needed wizard(set qty_done automatically or asking for a
backorder). Also it override the immediate transfer wizard in order to
pass the picking to backorder found in done function through the process
action.

pick_to_backorder_ids is needed in immediate transfer since we need to
pass the backorders from the done function in stock_picking_batch
through the immediate transfer wizard.
This commit is contained in:
amoyaux
2017-09-26 09:31:26 +02:00
parent 03559629ee
commit 589a38ce33
5 changed files with 291 additions and 5 deletions
@@ -55,7 +55,7 @@ class StockPickingBatch(models.Model):
@api.multi
def done(self):
pickings = self.mapped('picking_ids').filtered(lambda picking: picking.state not in ('cancel', 'done'))
if any(picking.state != 'assigned' for picking in pickings):
if any(picking.state not in ('assigned') for picking in pickings):
raise UserError(_('Some pickings are still waiting for goods. Please check or force their availability before setting this batch to done.'))
for picking in pickings:
picking.message_post(
@@ -64,9 +64,45 @@ class StockPickingBatch(models.Model):
_("Batch Picking"),
picking.batch_id.id,
picking.batch_id.name))
if pickings:
pickings.action_done()
return self.write({'state': 'done'})
picking_to_backorder = self.env['stock.picking']
picking_without_qty_done = self.env['stock.picking']
for picking in pickings:
if all([x.qty_done == 0.0 for x in picking.move_line_ids]):
# If no lots when needed, raise error
picking_type = picking.picking_type_id
if (picking_type.use_create_lots or picking_type.use_existing_lots):
for ml in picking.move_line_ids:
if ml.product_id.tracking != 'none':
raise UserError(_('Some products require lots/serial numbers, so you need to specify those first!'))
# Check if we need to set some qty done.
picking_without_qty_done |= picking
elif picking._check_backorder():
picking_to_backorder |= picking
else:
picking.action_done()
self.write({'state': 'done'})
if picking_without_qty_done:
view = self.env.ref('stock.view_immediate_transfer')
wiz = self.env['stock.immediate.transfer'].create({
'pick_ids': [(4, p.id) for p in picking_without_qty_done],
'pick_to_backorder_ids': [(4, p.id) for p in picking_to_backorder],
})
return {
'name': _('Immediate Transfer?'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'stock.immediate.transfer',
'views': [(view.id, 'form')],
'view_id': view.id,
'target': 'new',
'res_id': wiz.id,
'context': self.env.context,
}
if picking_to_backorder:
return picking_to_backorder.action_generate_backorder_wizard()
return True
def _track_subtype(self, init_values):
if 'state' in init_values:
@@ -80,4 +116,4 @@ class StockPicking(models.Model):
batch_id = fields.Many2one(
'stock.picking.batch', string='Batch Picking', oldname="wave_id",
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
help='Batch associated to this picking')
help='Batch associated to this picking', copy=False)
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_batch_picking
@@ -0,0 +1,225 @@
from odoo.tests.common import TransactionCase
class TestBatchPicking(TransactionCase):
def setUp(self):
""" Create a picking batch with two pickings from stock to customer """
super(TestBatchPicking, self).setUp()
self.stock_location = self.env.ref('stock.stock_location_stock')
self.customer_location = self.env.ref('stock.stock_location_customers')
self.partner_delta_id = self.env['ir.model.data'].xmlid_to_res_id('base.res_partner_4')
self.picking_type_out = self.env['ir.model.data'].xmlid_to_res_id('stock.picking_type_out')
self.productA = self.env['product.product'].create({
'name': 'Product A',
'type': 'product',
'categ_id': self.env.ref('product.product_category_all').id,
})
self.productB = self.env['product.product'].create({
'name': 'Product B',
'type': 'product',
'categ_id': self.env.ref('product.product_category_all').id,
})
self.picking_client_1 = self.env['stock.picking'].create({
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.env['stock.move'].create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 10,
'product_uom': self.productA.uom_id.id,
'picking_id': self.picking_client_1.id,
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
})
self.picking_client_2 = self.env['stock.picking'].create({
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.env['stock.move'].create({
'name': self.productB.name,
'product_id': self.productB.id,
'product_uom_qty': 10,
'product_uom': self.productA.uom_id.id,
'picking_id': self.picking_client_2.id,
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
})
self.batch = self.env['stock.picking.batch'].create({
'name': 'Batch 1',
'picking_ids': [(4, self.picking_client_1.id), (4, self.picking_client_2.id)]
})
def test_simple_batch_with_manual_qty_done(self):
""" Test a simple batch picking with all quantity for picking available.
The user set all the quantity_done on picking manually and no wizard are used.
"""
self.env['stock.quant']._update_available_quantity(self.productA, self.stock_location, 10.0)
self.env['stock.quant']._update_available_quantity(self.productB, self.stock_location, 10.0)
# confirm batch, picking should be assigned
self.batch.confirm_picking()
self.assertEqual(self.picking_client_1.state, 'assigned', 'Picking 1 should be reserved')
self.assertEqual(self.picking_client_2.state, 'assigned', 'Picking 2 should be reserved')
self.picking_client_1.move_lines.quantity_done = 10
self.picking_client_2.move_lines.quantity_done = 10
self.batch.done()
self.assertEqual(self.picking_client_1.state, 'done', 'Picking 1 should be done')
self.assertEqual(self.picking_client_2.state, 'done', 'Picking 2 should be done')
quant_A = self.env['stock.quant']._gather(self.productA, self.stock_location)
quant_B = self.env['stock.quant']._gather(self.productB, self.stock_location)
# ensure that quantity for picking has been moved
self.assertFalse(quant_A)
self.assertFalse(quant_B)
def test_simple_batch_with_wizard(self):
""" Test a simple batch picking with all quantity for picking available.
The user use the wizard in order to complete automatically the quantity_done to
the initial demand (or reserved quantity in this test).
"""
self.env['stock.quant']._update_available_quantity(self.productA, self.stock_location, 10.0)
self.env['stock.quant']._update_available_quantity(self.productB, self.stock_location, 10.0)
# confirm batch, picking should be assigned
self.batch.confirm_picking()
self.assertEqual(self.picking_client_1.state, 'assigned', 'Picking 1 should be reserved')
self.assertEqual(self.picking_client_2.state, 'assigned', 'Picking 2 should be reserved')
# There should be a wizard asking to process picking without quantity done
immediate_transfer_wizard_dict = self.batch.done()
self.assertTrue(immediate_transfer_wizard_dict)
immediate_transfer_wizard = self.env[(immediate_transfer_wizard_dict.get('res_model'))].browse(immediate_transfer_wizard_dict.get('res_id'))
self.assertEqual(len(immediate_transfer_wizard.pick_ids), 2)
immediate_transfer_wizard.process()
self.assertEqual(self.picking_client_1.state, 'done', 'Picking 1 should be done')
self.assertEqual(self.picking_client_2.state, 'done', 'Picking 2 should be done')
quant_A = self.env['stock.quant']._gather(self.productA, self.stock_location)
quant_B = self.env['stock.quant']._gather(self.productB, self.stock_location)
# ensure that quantity for picking has been moved
self.assertFalse(quant_A)
self.assertFalse(quant_B)
def test_batch_with_backorder_wizard(self):
""" Test a simple batch picking with only one quantity fully available.
The user will set by himself the quantity reserved for each picking and
run the picking batch. There should be a wizard asking for a backorder.
"""
self.env['stock.quant']._update_available_quantity(self.productA, self.stock_location, 5.0)
self.env['stock.quant']._update_available_quantity(self.productB, self.stock_location, 10.0)
# confirm batch, picking should be assigned
self.batch.confirm_picking()
self.assertEqual(self.picking_client_1.state, 'assigned', 'Picking 1 should be ready')
self.assertEqual(self.picking_client_2.state, 'assigned', 'Picking 2 should be ready')
self.picking_client_1.move_lines.quantity_done = 5
self.picking_client_2.move_lines.quantity_done = 10
# There should be a wizard asking to process picking without quantity done
back_order_wizard_dict = self.batch.done()
self.assertTrue(back_order_wizard_dict)
back_order_wizard = self.env[(back_order_wizard_dict.get('res_model'))].browse(back_order_wizard_dict.get('res_id'))
self.assertEqual(len(back_order_wizard.pick_ids), 1)
self.assertEqual(self.picking_client_2.state, 'done', 'Picking 2 should be done')
back_order_wizard.process()
self.assertEqual(self.picking_client_1.state, 'done', 'Picking 1 should be done')
self.assertEqual(self.picking_client_1.move_lines.product_uom_qty, 5, 'initial demand should be 5 after picking split')
self.assertTrue(self.env['stock.picking'].search([('backorder_id', '=', self.picking_client_1.id)]), 'no back order created')
quant_A = self.env['stock.quant']._gather(self.productA, self.stock_location)
quant_B = self.env['stock.quant']._gather(self.productB, self.stock_location)
# ensure that quantity for picking has been moved
self.assertFalse(quant_A)
self.assertFalse(quant_B)
def test_batch_with_immediate_transfer_and_backorder_wizard(self):
""" Test a simple batch picking with only one product fully available.
Everything should be automatically. First one backorder in order to set quantity_done
to reserved quantity. After a second wizard asking for a backorder for the quantity that
has not been fully transfered.
"""
self.env['stock.quant']._update_available_quantity(self.productA, self.stock_location, 5.0)
self.env['stock.quant']._update_available_quantity(self.productB, self.stock_location, 10.0)
# confirm batch, picking should be assigned
self.batch.confirm_picking()
self.assertEqual(self.picking_client_1.state, 'assigned', 'Picking 1 should be ready')
self.assertEqual(self.picking_client_2.state, 'assigned', 'Picking 2 should be ready')
# There should be a wizard asking to process picking without quantity done
immediate_transfer_wizard_dict = self.batch.done()
self.assertTrue(immediate_transfer_wizard_dict)
immediate_transfer_wizard = self.env[(immediate_transfer_wizard_dict.get('res_model'))].browse(immediate_transfer_wizard_dict.get('res_id'))
self.assertEqual(len(immediate_transfer_wizard.pick_ids), 2)
back_order_wizard_dict = immediate_transfer_wizard.process()
self.assertTrue(back_order_wizard_dict)
back_order_wizard = self.env[(back_order_wizard_dict.get('res_model'))].browse(back_order_wizard_dict.get('res_id'))
self.assertEqual(len(back_order_wizard.pick_ids), 1)
back_order_wizard.process()
self.assertEqual(self.picking_client_1.state, 'done', 'Picking 1 should be done')
self.assertEqual(self.picking_client_1.move_lines.product_uom_qty, 5, 'initial demand should be 5 after picking split')
self.assertTrue(self.env['stock.picking'].search([('backorder_id', '=', self.picking_client_1.id)]), 'no back order created')
quant_A = self.env['stock.quant']._gather(self.productA, self.stock_location)
quant_B = self.env['stock.quant']._gather(self.productB, self.stock_location)
# ensure that quantity for picking has been moved
self.assertFalse(quant_A)
self.assertFalse(quant_B)
def test_batch_with_immediate_transfer_and_backorder_wizard_with_manual_operations(self):
""" Test a simple batch picking with only one quantity fully available.
The user set the quantity done only for the partially available picking.
The test should run the immediate transfer for the first picking and then
the backorder wizard for the second picking.
"""
self.env['stock.quant']._update_available_quantity(self.productA, self.stock_location, 5.0)
self.env['stock.quant']._update_available_quantity(self.productB, self.stock_location, 10.0)
# confirm batch, picking should be assigned
self.batch.confirm_picking()
self.assertEqual(self.picking_client_1.state, 'assigned', 'Picking 1 should be ready')
self.assertEqual(self.picking_client_2.state, 'assigned', 'Picking 2 should be ready')
self.picking_client_1.move_lines.quantity_done = 5
# There should be a wizard asking to process picking without quantity done
immediate_transfer_wizard_dict = self.batch.done()
self.assertTrue(immediate_transfer_wizard_dict)
immediate_transfer_wizard = self.env[(immediate_transfer_wizard_dict.get('res_model'))].browse(immediate_transfer_wizard_dict.get('res_id'))
self.assertEqual(len(immediate_transfer_wizard.pick_ids), 1)
back_order_wizard_dict = immediate_transfer_wizard.process()
self.assertTrue(back_order_wizard_dict)
back_order_wizard = self.env[(back_order_wizard_dict.get('res_model'))].browse(back_order_wizard_dict.get('res_id'))
self.assertEqual(len(back_order_wizard.pick_ids), 1)
back_order_wizard.process()
self.assertEqual(self.picking_client_1.state, 'done', 'Picking 1 should be done')
self.assertEqual(self.picking_client_1.move_lines.product_uom_qty, 5, 'initial demand should be 5 after picking split')
self.assertTrue(self.env['stock.picking'].search([('backorder_id', '=', self.picking_client_1.id)]), 'no back order created')
quant_A = self.env['stock.quant']._gather(self.productA, self.stock_location)
quant_B = self.env['stock.quant']._gather(self.productB, self.stock_location)
# ensure that quantity for picking has been moved
self.assertFalse(quant_A)
self.assertFalse(quant_B)
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import stock_picking_to_batch
from . import stock_immediate_transfer
@@ -0,0 +1,20 @@
from odoo import models, fields, api
class StockImmediateTransfer(models.TransientModel):
_inherit = 'stock.immediate.transfer'
_description = 'Immediate Transfer'
pick_to_backorder_ids = fields.Many2many('stock.picking', help='Picking to backorder')
@api.multi
def process(self):
backorder_wizard_dict = super(StockImmediateTransfer, self).process()
# If the immediate transfer wizard process all our picking but with some back order maybe needed we want to add the backorder already passed to the wizard.
if backorder_wizard_dict is not None:
backorder_wizard = self.env['stock.backorder.confirmation'].browse(backorder_wizard_dict.get('res_id', False))
backorder_wizard.write({'pick_ids': [(4, p.id) for p in self.pick_to_backorder_ids]})
return backorder_wizard_dict
# If there is no backorder returned by the immediate transfer basic function we still wanted to process those manually given
elif self.pick_to_backorder_ids:
return self.pick_to_backorder_ids.action_generate_backorder_wizard()