summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoramoyaux <arm@odoo.com>2017-08-28 09:32:34 +0200
committeramoyaux <arm@odoo.com>2017-09-26 09:31:26 +0200
commit589a38ce338d4e56f0b5a8d026183316f7896592 (patch)
tree6576744a8d65b8c06d7e28dfb6195f57e558157b
parent03559629ee3f8ef28e51d8ed659fc7c09b176955 (diff)
[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.
-rw-r--r--addons/stock_picking_batch/models/stock_picking_batch.py46
-rw-r--r--addons/stock_picking_batch/tests/__init__.py4
-rw-r--r--addons/stock_picking_batch/tests/test_batch_picking.py225
-rw-r--r--addons/stock_picking_batch/wizard/__init__.py1
-rw-r--r--addons/stock_picking_batch/wizard/stock_immediate_transfer.py20
5 files changed, 291 insertions, 5 deletions
diff --git a/addons/stock_picking_batch/models/stock_picking_batch.py b/addons/stock_picking_batch/models/stock_picking_batch.py
index 2cc4f707f69..5a92147f7ce 100644
--- a/addons/stock_picking_batch/models/stock_picking_batch.py
+++ b/addons/stock_picking_batch/models/stock_picking_batch.py
@@ -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)
diff --git a/addons/stock_picking_batch/tests/__init__.py b/addons/stock_picking_batch/tests/__init__.py
new file mode 100644
index 00000000000..4ca7c88d9f5
--- /dev/null
+++ b/addons/stock_picking_batch/tests/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import test_batch_picking
diff --git a/addons/stock_picking_batch/tests/test_batch_picking.py b/addons/stock_picking_batch/tests/test_batch_picking.py
new file mode 100644
index 00000000000..6a8a1e9d393
--- /dev/null
+++ b/addons/stock_picking_batch/tests/test_batch_picking.py
@@ -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) \ No newline at end of file
diff --git a/addons/stock_picking_batch/wizard/__init__.py b/addons/stock_picking_batch/wizard/__init__.py
index 063ece3795c..92e3d6e014e 100644
--- a/addons/stock_picking_batch/wizard/__init__.py
+++ b/addons/stock_picking_batch/wizard/__init__.py
@@ -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
diff --git a/addons/stock_picking_batch/wizard/stock_immediate_transfer.py b/addons/stock_picking_batch/wizard/stock_immediate_transfer.py
new file mode 100644
index 00000000000..57de52d59af
--- /dev/null
+++ b/addons/stock_picking_batch/wizard/stock_immediate_transfer.py
@@ -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()