[IMP] delivery, stock(_picking_batch): add "Put In Pack" button

This commit sets up the logic and adds the button for "Put in Pack"
action that already exists in stock.picking. Reuse/extension of logic
from stock.picking reused where possible, including extension of pack
wizard to handle products with different destination.

Note that logic to handle "Delivery Packaging" wizard when "Delivery
Methods" is active was purposely not extended to work correctly in batch
pickings due to complexity of adding in a new module just to handle
conflicting carrier_ids across pickings in the same batch. It is
expected that this use case will not occur except in case of user error.

Additionally, stock.picking implementation of 'put_in_pack' method has
been renamed to 'action_put_in_pack' to have consistent naming (and
support enterprise level code).

Part of "1. Improve Batch Pickings" specification of overall barcode
improvements task.

Task: 1884520
Enterprise PR: odoo/enterprise#12086

Closes: odoo/odoo#55096
This commit is contained in:
Tiffany Chang (tic)
2020-08-20 15:06:08 +00:00
committed by svs-odoo
parent d8cd37fc72
commit 58316d1546
14 changed files with 125 additions and 34 deletions
@@ -65,7 +65,7 @@ class TestPacking(TestPackingCommon):
'location_dest_id': self.customer_location.id
})
picking_ship.action_confirm()
pack_action = picking_ship.put_in_pack()
pack_action = picking_ship.action_put_in_pack()
pack_action_ctx = pack_action['context']
pack_action_model = pack_action['res_model']
@@ -46,7 +46,7 @@ class ChooseDeliveryPackage(models.TransientModel):
}
return {'warning': warning_mess}
def put_in_pack(self):
def action_put_in_pack(self):
picking_move_lines = self.picking_id.move_line_ids
if not self.picking_id.picking_type_id.show_reserved:
picking_move_lines = self.picking_id.move_line_nosuggest_ids
@@ -16,7 +16,7 @@
</div>
</group>
<footer>
<button name="put_in_pack" type="object" string="Save" class="btn-primary"/>
<button name="action_put_in_pack" type="object" string="Save" class="btn-primary"/>
<button string="Discard" special="cancel" class="btn-secondary"/>
</footer>
</form>
+11 -10
View File
@@ -1280,7 +1280,7 @@ class Picking(models.Model):
else:
return {}
def _put_in_pack(self, move_line_ids):
def _put_in_pack(self, move_line_ids, create_package_level=True):
package = False
for pick in self:
move_lines_to_pack = self.env['stock.move.line']
@@ -1312,20 +1312,21 @@ class Picking(models.Model):
ml.write(vals)
new_move_line.write({'product_uom_qty': done_to_keep})
move_lines_to_pack |= new_move_line
package_level = self.env['stock.package_level'].create({
'package_id': package.id,
'picking_id': pick.id,
'location_id': False,
'location_dest_id': move_line_ids.mapped('location_dest_id').id,
'move_line_ids': [(6, 0, move_lines_to_pack.ids)],
'company_id': pick.company_id.id,
})
if create_package_level:
package_level = self.env['stock.package_level'].create({
'package_id': package.id,
'picking_id': pick.id,
'location_id': False,
'location_dest_id': move_line_ids.mapped('location_dest_id').id,
'move_line_ids': [(6, 0, move_lines_to_pack.ids)],
'company_id': pick.company_id.id,
})
move_lines_to_pack.write({
'result_package_id': package.id,
})
return package
def put_in_pack(self):
def action_put_in_pack(self):
self.ensure_one()
if self.state not in ('done', 'cancel'):
picking_move_lines = self.move_line_ids
+5 -5
View File
@@ -4381,13 +4381,13 @@ class StockMove(SavepointCase):
picking.action_assign()
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0)
move1.quantity_done = 1
picking.put_in_pack()
picking.action_put_in_pack()
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0)
self.assertEqual(len(picking.move_line_ids), 2)
unpacked_ml = picking.move_line_ids.filtered(lambda ml: not ml.result_package_id)
self.assertEqual(unpacked_ml.product_qty, 1)
unpacked_ml.qty_done = 1
picking.put_in_pack()
picking.action_put_in_pack()
self.assertEqual(len(picking.move_line_ids), 2)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0)
picking.button_validate()
@@ -4432,7 +4432,7 @@ class StockMove(SavepointCase):
picking.action_assign()
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, self.stock_location), 0)
picking.put_in_pack()
picking.action_put_in_pack()
self.assertEqual(len(picking.move_line_ids), 2)
self.assertEqual(picking.move_line_ids[0].qty_done, 1, "Stock move line should have 1 quantity as a done quantity.")
self.assertEqual(picking.move_line_ids[1].qty_done, 2, "Stock move line should have 2 quantity as a done quantity.")
@@ -4482,9 +4482,9 @@ class StockMove(SavepointCase):
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, self.stock_location), 0)
move1.quantity_done = 1
picking.put_in_pack()
picking.action_put_in_pack()
move2.quantity_done = 2
picking.put_in_pack()
picking.action_put_in_pack()
self.assertEqual(len(picking.move_line_ids), 2)
line1_result_package = picking.move_line_ids[0].result_package_id
line2_result_package = picking.move_line_ids[1].result_package_id
+7 -7
View File
@@ -79,12 +79,12 @@ class TestPacking(TestPackingCommon):
pick_picking.move_line_ids.filtered(lambda ml: ml.product_id == self.productA).qty_done = 1.0
pick_picking.move_line_ids.filtered(lambda ml: ml.product_id == self.productB).qty_done = 2.0
first_pack = pick_picking.put_in_pack()
first_pack = pick_picking.action_put_in_pack()
self.assertEqual(len(pick_picking.package_level_ids), 1, 'Put some products in pack should create a package_level')
self.assertEqual(pick_picking.package_level_ids[0].state, 'new', 'A new pack should be in state "new"')
pick_picking.move_line_ids.filtered(lambda ml: ml.product_id == self.productA and ml.qty_done == 0.0).qty_done = 4.0
pick_picking.move_line_ids.filtered(lambda ml: ml.product_id == self.productB and ml.qty_done == 0.0).qty_done = 3.0
second_pack = pick_picking.put_in_pack()
second_pack = pick_picking.action_put_in_pack()
self.assertEqual(len(pick_picking.move_ids_without_package), 0)
self.assertEqual(len(packing_picking.move_ids_without_package), 2)
pick_picking.button_validate()
@@ -235,7 +235,7 @@ class TestPacking(TestPackingCommon):
picking.action_confirm()
picking.action_assign()
picking.move_line_ids.filtered(lambda ml: ml.product_id == self.productA).qty_done = 5.0
picking.put_in_pack()
picking.action_put_in_pack()
pack1 = self.env['stock.quant.package'].search([])[-1]
picking.write({
'move_line_ids': [(0, 0, {
@@ -261,7 +261,7 @@ class TestPacking(TestPackingCommon):
'state': 'confirmed',
})]
})
wizard_values = picking.put_in_pack()
wizard_values = picking.action_put_in_pack()
wizard = self.env[(wizard_values.get('res_model'))].browse(wizard_values.get('res_id'))
wizard.location_dest_id = shelf2_location.id
wizard.action_done()
@@ -424,7 +424,7 @@ class TestPacking(TestPackingCommon):
with receipt_form.move_line_ids_without_package.edit(1) as move_line:
move_line.qty_done = 1
receipt = receipt_form.save()
receipt.put_in_pack()
receipt.action_put_in_pack()
receipt.button_validate()
receipt_package = receipt.package_level_ids_details[0]
@@ -563,7 +563,7 @@ class TestPacking(TestPackingCommon):
with receipt_form.move_line_ids_without_package.edit(1) as move_line:
move_line.qty_done = 1
receipt = receipt_form.save()
receipt.put_in_pack()
receipt.action_put_in_pack()
receipt.button_validate()
receipt_package = receipt.package_level_ids_details[0]
@@ -665,4 +665,4 @@ class TestPacking(TestPackingCommon):
pick_picking.action_assign()
pick_picking.move_line_ids.qty_done = 3
first_pack = pick_picking.put_in_pack()
first_pack = pick_picking.action_put_in_pack()
+1 -1
View File
@@ -1328,7 +1328,7 @@ class TestStockFlow(TestStockCommon):
# Set the quantity done on the pack operation
move_in.move_line_ids.qty_done = 3.0
# Put in a pack
picking_in.put_in_pack()
picking_in.action_put_in_pack()
# Get the new package
picking_in_package = move_in.move_line_ids.result_package_id
# Validate picking
+3 -3
View File
@@ -333,7 +333,7 @@
<field name="package_level_ids_details"
context="{'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_company_id': company_id}"
attrs="{'readonly': [('state', '=', 'done')], 'invisible': ['|', ('picking_type_entire_packs', '=', False), ('show_operations', '=', False)]}" />
<button class="oe_highlight" name="put_in_pack" type="object" string="Put in Pack" attrs="{'invisible': [('state', 'in', ('draft', 'done', 'cancel'))]}" groups="stock.group_tracking_lot"/>
<button class="oe_highlight" name="action_put_in_pack" type="object" string="Put in Pack" attrs="{'invisible': [('state', 'in', ('draft', 'done', 'cancel'))]}" groups="stock.group_tracking_lot"/>
</page>
<!-- Reservation displayed -->
@@ -344,7 +344,7 @@
attrs="{'readonly': ['|', '|', ('show_operations', '=', False), ('state', '=', 'cancel'), '&amp;', ('state', '=', 'done'), ('is_locked', '=', True)]}"
context="{'tree_view_ref': 'stock.view_stock_move_line_detailed_operation_tree', 'default_picking_id': id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_company_id': company_id}"/>
<field name="package_level_ids_details" context="{'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_company_id': company_id}" attrs="{'readonly': [('state', '=', 'done')], 'invisible': ['|', ('picking_type_entire_packs', '=', False), ('show_operations', '=', False)]}" />
<button class="oe_highlight" name="put_in_pack" type="object" string="Put in Pack" attrs="{'invisible': [('state', 'in', ('draft', 'done', 'cancel'))]}" groups="stock.group_tracking_lot"/>
<button class="oe_highlight" name="action_put_in_pack" type="object" string="Put in Pack" attrs="{'invisible': [('state', 'in', ('draft', 'done', 'cancel'))]}" groups="stock.group_tracking_lot"/>
</page>
<page string="Operations" name="operations">
@@ -420,7 +420,7 @@
</field>
<field name="id" invisible="1"/>
<field name="package_level_ids" context="{'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_company_id': company_id}" attrs="{'readonly': [('state', '=', 'done')], 'invisible': ['|', ('picking_type_entire_packs', '=', False), ('show_operations', '=', True)]}" />
<button class="oe_highlight" name="put_in_pack" type="object" string="Put in Pack" attrs="{'invisible': [('state', 'in', ('draft', 'done', 'cancel'))]}" groups="stock.group_tracking_lot"/>
<button class="oe_highlight" name="action_put_in_pack" type="object" string="Put in Pack" attrs="{'invisible': [('state', 'in', ('draft', 'done', 'cancel'))]}" groups="stock.group_tracking_lot"/>
</page>
<page string="Additional Info" name="extra">
<group>
@@ -24,7 +24,6 @@ class ChooseDestinationLocation(models.TransientModel):
destination.filtered_location = destination.move_line_ids.mapped('location_dest_id')
def action_done(self):
# set the same location on each move line and pass again in _put_in_pack
for line in self.move_line_ids:
line.location_dest_id = self.location_dest_id
return self.picking_id.put_in_pack()
# set the same location on each move line and pass again in action_put_in_pack
self.move_line_ids.location_dest_id = self.location_dest_id
return self.picking_id.action_put_in_pack()
@@ -3,7 +3,7 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_compare, float_is_zero, float_round
class StockPickingBatch(models.Model):
_inherit = ['mail.thread', 'mail.activity.mixin']
@@ -198,6 +198,30 @@ class StockPickingBatch(models.Model):
self.ensure_one()
self.picking_ids.action_assign()
def action_put_in_pack(self):
""" Action to put move lines with 'Done' quantities into a new pack
This method follows same logic to stock.picking.
"""
self.ensure_one()
if self.state not in ('done', 'cancel'):
picking_move_lines = self.move_line_ids
move_line_ids = picking_move_lines.filtered(lambda ml:
float_compare(ml.qty_done, 0.0, precision_rounding=ml.product_uom_id.rounding) > 0
and not ml.result_package_id
)
if not move_line_ids:
move_line_ids = picking_move_lines.filtered(lambda ml: float_compare(ml.product_uom_qty, 0.0,
precision_rounding=ml.product_uom_id.rounding) > 0 and float_compare(ml.qty_done, 0.0,
precision_rounding=ml.product_uom_id.rounding) == 0)
if move_line_ids:
res = self.picking_ids[0]._pre_put_in_pack_hook(move_line_ids)
if not res:
res = self.picking_ids[0]._put_in_pack(move_line_ids, False)
return res
else:
raise UserError(_("Please add 'Done' quantities to the batch picking to create a new pack."))
# -------------------------------------------------------------------------
# Miscellaneous
# -------------------------------------------------------------------------
@@ -325,3 +325,43 @@ class TestBatchPicking(TransactionCase):
# ensure that quantity for picking has been moved
self.assertFalse(sum(quant_A.mapped('quantity')))
self.assertFalse(sum(quant_B.mapped('quantity')))
def test_put_in_pack(self):
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, pickings should not be automatically assigned.
self.batch.action_confirm()
self.assertEqual(self.picking_client_1.state, 'confirmed', 'Picking 1 should be confirmed')
self.assertEqual(self.picking_client_2.state, 'confirmed', 'Picking 2 should be confirmed')
# Ask to assign, so pickings should be assigned now.
self.batch.action_assign()
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')
# only do part of pickings + assign different destinations + try to pack (should get wizard to correct destination)
self.batch.move_line_ids.qty_done = 5
self.batch.move_line_ids[0].location_dest_id = self.stock_location.id
wizard_values = self.batch.action_put_in_pack()
wizard = self.env[(wizard_values.get('res_model'))].browse(wizard_values.get('res_id'))
wizard.location_dest_id = self.customer_location.id
package = wizard.action_done()
# a new package is made and done quantities should be in same package
self.assertTrue(package)
done_qty_move_lines = self.batch.move_line_ids.filtered(lambda ml: ml.qty_done == 5)
self.assertEqual(done_qty_move_lines[0].result_package_id.id, package.id)
self.assertEqual(done_qty_move_lines[1].result_package_id.id, package.id)
# not done quantities should be split into separate lines
self.assertEqual(len(self.batch.move_line_ids), 4)
# confirm w/ backorder
back_order_wizard_dict = self.batch.action_done()
self.assertTrue(back_order_wizard_dict)
back_order_wizard = Form(self.env[(back_order_wizard_dict.get('res_model'))].with_context(back_order_wizard_dict['context'])).save()
self.assertEqual(len(back_order_wizard.pick_ids), 2)
back_order_wizard.process()
# final package location should be correctly set based on wizard
self.assertEqual(package.location_id.id, self.customer_location.id)
@@ -111,6 +111,7 @@
<notebook>
<page string="Detailed Operations" attrs="{'invisible': [('state', '=', 'draft')]}">
<field name="move_line_ids" context="{'tree_view_ref': 'stock_picking_batch.view_move_line_tree'}"/>
<button class="oe_highlight" name="action_put_in_pack" type="object" string="Put in Pack" attrs="{'invisible': [('state', 'in', ('draft', 'done', 'cancel'))]}" groups="stock.group_tracking_lot"/>
</page>
<page string="Operations" attrs="{'invisible': [('state', '=', 'draft')]}">
<field name="move_ids" context="{'tree_view_ref': 'stock_picking_batch.view_picking_move_tree_inherited'}"/>
@@ -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_package_destination
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class ChooseDestinationLocation(models.TransientModel):
_inherit = "stock.package.destination"
def _compute_move_line_ids(self):
destination_without_batch = self.env['stock.package.destination']
for destination in self:
if not destination.picking_id.batch_id:
destination_without_batch |= destination
continue
destination.move_line_ids = destination.picking_id.batch_id.move_line_ids.filtered(lambda l: l.qty_done > 0 and not l.result_package_id)
super(ChooseDestinationLocation, destination_without_batch)._compute_move_line_ids()
def action_done(self):
if self.picking_id.batch_id:
# set the same location on each move line and pass again in action_put_in_pack
self.move_line_ids.location_dest_id = self.location_dest_id
return self.picking_id.batch_id.action_put_in_pack()
else:
return super().action_done()