diff options
| author | amoyaux <arm@odoo.com> | 2017-08-28 09:32:34 +0200 |
|---|---|---|
| committer | amoyaux <arm@odoo.com> | 2017-09-26 09:31:26 +0200 |
| commit | 589a38ce338d4e56f0b5a8d026183316f7896592 (patch) | |
| tree | 6576744a8d65b8c06d7e28dfb6195f57e558157b | |
| parent | 03559629ee3f8ef28e51d8ed659fc7c09b176955 (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.
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() |
