-
-
Notifications
You must be signed in to change notification settings - Fork 729
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by dreispt
- Loading branch information
Showing
9 changed files
with
398 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Copyright 2019 Camptocamp SA | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) | ||
{ | ||
"name": "Stock Picking Completion Info", | ||
"summary": "Display on current document completion information according " | ||
"to next operations", | ||
"version": "12.0.1.0.0", | ||
"development_status": "Alpha", | ||
"category": "Warehouse Management", | ||
"website": "https://github.com/OCA/stock-logistics-warehouse", | ||
"author": "Camptocamp, Odoo Community Association (OCA)", | ||
"license": "AGPL-3", | ||
"application": False, | ||
"installable": True, | ||
"depends": [ | ||
"stock", | ||
], | ||
"data": [ | ||
"views/stock_picking.xml", | ||
], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import stock_picking |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# Copyright 2019 Camptocamp SA | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) | ||
from odoo import api, models, fields | ||
|
||
|
||
class PickingType(models.Model): | ||
|
||
_inherit = 'stock.picking.type' | ||
|
||
display_completion_info = fields.Boolean( | ||
help='Inform operator of a completed operation at processing and at' | ||
' completion' | ||
) | ||
|
||
|
||
class StockPicking(models.Model): | ||
|
||
_inherit = 'stock.picking' | ||
|
||
completion_info = fields.Selection( | ||
[ | ||
('no', 'No'), | ||
( | ||
'last_picking', | ||
'Last picking: Completion of this operation allows next ' | ||
'operations to be processed.', | ||
), | ||
( | ||
'next_picking_ready', | ||
'Next operations are ready to be processed.', | ||
), | ||
( | ||
'full_order_picking', | ||
'Full order picking: You are processing a full order picking ' | ||
'that will allow next operation to be processed' | ||
) | ||
], | ||
compute='_compute_completion_info', | ||
) | ||
|
||
@api.depends( | ||
'picking_type_id.display_completion_info', | ||
'move_lines.move_dest_ids.move_orig_ids.state', | ||
) | ||
def _compute_completion_info(self): | ||
for picking in self: | ||
if ( | ||
picking.state == 'draft' | ||
or not picking.picking_type_id.display_completion_info | ||
): | ||
picking.completion_info = 'no' | ||
continue | ||
# Depending moves are all the origin moves linked to the | ||
# destination pickings' moves | ||
depending_moves = picking.move_lines.mapped( | ||
'move_dest_ids.picking_id.move_lines.move_orig_ids' | ||
) | ||
# If all the depending moves are done or canceled then next picking | ||
# is ready to be processed | ||
if all(m.state in ('done', 'cancel') for m in depending_moves): | ||
picking.completion_info = 'next_picking_ready' | ||
continue | ||
# If all the depending moves are the moves on the actual picking | ||
# then it's a full order and next picking is ready to be processed | ||
if depending_moves == picking.move_lines: | ||
picking.completion_info = 'full_order_picking' | ||
continue | ||
# If there aren't any depending move from another picking that is | ||
# not done, then actual picking is the last to process | ||
other_depending_moves = ( | ||
depending_moves - picking.move_lines | ||
).filtered(lambda m: m.state not in ('done', 'cancel')) | ||
if not other_depending_moves: | ||
picking.completion_info = 'last_picking' | ||
continue | ||
picking.completion_info = 'no' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* Akim Juillerat <[email protected]> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
This module adds completion information on stock picking. | ||
|
||
If activated on the picking type, completion information is computed according | ||
to the next chained pickings related to the stock moves of the actual picking. | ||
|
||
In other words, if all the previous moves linked to the destination pickings | ||
moves are done, the completion of the actual picking allows the destination | ||
pickings to be processed. In such case, a ribbon will appear on the stock | ||
picking form view, to inform the stock operator. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import test_stock_picking_completion_info |
255 changes: 255 additions & 0 deletions
255
stock_picking_completion_info/tests/test_stock_picking_completion_info.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
# Copyright 2019 Camptocamp SA | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) | ||
from odoo.tests import SavepointCase | ||
|
||
|
||
class TestStockPickingCompletionInfo(SavepointCase): | ||
|
||
@classmethod | ||
def setUpClass(cls): | ||
super().setUpClass() | ||
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) | ||
cls.partner_delta = cls.env.ref('base.res_partner_4') | ||
cls.warehouse = cls.env.ref('stock.warehouse0') | ||
cls.warehouse.write({ | ||
'outgoing_shipments': 'pick_pack_ship', | ||
}) | ||
cls.customers_location = cls.env.ref('stock.stock_location_customers') | ||
cls.output_location = cls.env.ref('stock.stock_location_output') | ||
cls.packing_location = cls.env.ref('stock.location_pack_zone') | ||
cls.stock_shelf_location = cls.env.ref( | ||
'stock.stock_location_components' | ||
) | ||
cls.stock_shelf_2_location = cls.env.ref('stock.stock_location_14') | ||
|
||
cls.out_type = cls.warehouse.out_type_id | ||
cls.pack_type = cls.warehouse.pack_type_id | ||
cls.pick_type = cls.warehouse.pick_type_id | ||
cls.pick_type.write({'display_completion_info': True}) | ||
|
||
cls.product_1 = cls.env['product.product'].create({ | ||
'name': 'Product 1', 'type': 'product', | ||
}) | ||
cls.product_2 = cls.env['product.product'].create({ | ||
'name': 'Product 2', 'type': 'product', | ||
}) | ||
|
||
def _init_inventory(self, same_location=True): | ||
# Product 1 on shelf 1 | ||
# Product 2 on shelf 2 | ||
inventory = self.env['stock.inventory'].create({ | ||
'name': 'Test init', | ||
'filter': 'partial', | ||
}) | ||
inventory.action_start() | ||
if not same_location: | ||
product_location_list = [ | ||
(self.product_1, self.stock_shelf_location), | ||
(self.product_2, self.stock_shelf_2_location), | ||
] | ||
else: | ||
product_location_list = [ | ||
(self.product_1, self.stock_shelf_location), | ||
(self.product_2, self.stock_shelf_location), | ||
] | ||
lines_vals = list() | ||
for product, location in product_location_list: | ||
lines_vals.append((0, 0, { | ||
'product_id': product.id, | ||
'product_uom_id': product.uom_id.id, | ||
'product_qty': 10.0, | ||
'location_id': location.id, | ||
})) | ||
inventory.write({ | ||
'line_ids': lines_vals | ||
}) | ||
inventory.action_validate() | ||
|
||
def _create_pickings(self, same_pick_location=True): | ||
# Create delivery order | ||
ship_order = self.env['stock.picking'].create({ | ||
'partner_id': self.partner_delta.id, | ||
'location_id': self.output_location.id, | ||
'location_dest_id': self.customers_location.id, | ||
'picking_type_id': self.out_type.id, | ||
}) | ||
pack_order = self.env['stock.picking'].create({ | ||
'partner_id': self.partner_delta.id, | ||
'location_id': self.packing_location.id, | ||
'location_dest_id': self.output_location.id, | ||
'picking_type_id': self.pack_type.id, | ||
}) | ||
pick_order = self.env['stock.picking'].create({ | ||
'partner_id': self.partner_delta.id, | ||
'location_id': self.stock_shelf_location.id, | ||
'location_dest_id': self.packing_location.id, | ||
'picking_type_id': self.pick_type.id, | ||
}) | ||
if same_pick_location: | ||
return ship_order, pack_order, pick_order | ||
pick_order_2 = self.env['stock.picking'].create({ | ||
'partner_id': self.partner_delta.id, | ||
'location_id': self.stock_shelf_2_location.id, | ||
'location_dest_id': self.packing_location.id, | ||
'picking_type_id': self.pick_type.id, | ||
}) | ||
return ship_order, pack_order, pick_order, pick_order_2 | ||
|
||
def _create_move(self, picking, product, state='waiting', | ||
procure_method='make_to_order', move_dest=None): | ||
move_vals = { | ||
'name': product.name, | ||
'product_id': product.id, | ||
'product_uom_qty': 2.0, | ||
'product_uom': product.uom_id.id, | ||
'picking_id': picking.id, | ||
'location_id': picking.location_id.id, | ||
'location_dest_id': picking.location_dest_id.id, | ||
'state': state, | ||
'procure_method': procure_method, | ||
} | ||
if move_dest: | ||
move_vals['move_dest_ids'] = [(4, move_dest.id, False)] | ||
return self.env['stock.move'].create(move_vals) | ||
|
||
def test_picking_all_at_once(self): | ||
self._init_inventory() | ||
ship_order, pack_order, pick_order = self._create_pickings() | ||
ship_move_1 = self._create_move(ship_order, self.product_1) | ||
pack_move_1 = self._create_move( | ||
pack_order, self.product_1, move_dest=ship_move_1 | ||
) | ||
pick_move_1 = self._create_move( | ||
pick_order, self.product_1, state='confirmed', | ||
procure_method='make_to_stock', move_dest=pack_move_1 | ||
) | ||
ship_move_2 = self._create_move(ship_order, self.product_2) | ||
pack_move_2 = self._create_move( | ||
pack_order, self.product_2, move_dest=ship_move_2 | ||
) | ||
pick_move_2 = self._create_move( | ||
pick_order, self.product_2, state='confirmed', | ||
procure_method='make_to_stock', move_dest=pack_move_2 | ||
) | ||
self.assertEqual(pick_move_1.state, 'confirmed') | ||
self.assertEqual(pick_move_2.state, 'confirmed') | ||
self.assertEqual(pick_order.state, 'confirmed') | ||
self.assertEqual(pick_order.completion_info, 'full_order_picking') | ||
pick_order.action_assign() | ||
self.assertEqual(pick_move_1.state, 'assigned') | ||
self.assertEqual(pick_move_2.state, 'assigned') | ||
self.assertEqual(pick_order.state, 'assigned') | ||
self.assertEqual(pick_order.completion_info, 'full_order_picking') | ||
wiz = self.env['stock.immediate.transfer'].create( | ||
{'pick_ids': [(4, pick_order.id)]} | ||
) | ||
wiz.process() | ||
self.assertEqual(pick_move_1.state, 'done') | ||
self.assertEqual(pick_move_2.state, 'done') | ||
self.assertEqual(pick_order.state, 'done') | ||
self.assertEqual(pick_order.completion_info, 'next_picking_ready') | ||
|
||
def test_picking_from_different_locations(self): | ||
self._init_inventory(same_location=False) | ||
ship_order, pack_order, pick_order_1, pick_order_2 = \ | ||
self._create_pickings(same_pick_location=False) | ||
ship_move_1 = self._create_move(ship_order, self.product_1) | ||
pack_move_1 = self._create_move( | ||
pack_order, self.product_1, move_dest=ship_move_1 | ||
) | ||
pick_move_1 = self._create_move( | ||
pick_order_1, self.product_1, state='confirmed', | ||
procure_method='make_to_stock', move_dest=pack_move_1 | ||
) | ||
ship_move_2 = self._create_move(ship_order, self.product_2) | ||
pack_move_2 = self._create_move( | ||
pack_order, self.product_2, move_dest=ship_move_2 | ||
) | ||
pick_move_2 = self._create_move( | ||
pick_order_2, self.product_2, state='confirmed', | ||
procure_method='make_to_stock', move_dest=pack_move_2 | ||
) | ||
self.assertEqual(pick_move_1.state, 'confirmed') | ||
self.assertEqual(pick_move_2.state, 'confirmed') | ||
self.assertEqual(pick_order_1.state, 'confirmed') | ||
self.assertEqual(pick_order_1.completion_info, 'no') | ||
self.assertEqual(pick_order_2.state, 'confirmed') | ||
self.assertEqual(pick_order_2.completion_info, 'no') | ||
pick_order_1.action_assign() | ||
self.assertEqual(pick_move_1.state, 'assigned') | ||
self.assertEqual(pick_order_1.state, 'assigned') | ||
self.assertEqual(pick_order_1.completion_info, 'no') | ||
pick_order_2.action_assign() | ||
self.assertEqual(pick_move_2.state, 'assigned') | ||
self.assertEqual(pick_order_2.state, 'assigned') | ||
self.assertEqual(pick_order_2.completion_info, 'no') | ||
wiz = self.env['stock.immediate.transfer'].create( | ||
{'pick_ids': [(4, pick_order_1.id)]} | ||
) | ||
wiz.process() | ||
self.assertEqual(pick_move_1.state, 'done') | ||
self.assertEqual(pick_order_1.state, 'done') | ||
self.assertEqual(pick_order_1.completion_info, 'no') | ||
self.assertNotEqual(pick_move_2.state, 'done') | ||
self.assertNotEqual(pick_order_2.state, 'done') | ||
self.assertEqual(pick_order_2.completion_info, 'last_picking') | ||
wiz = self.env['stock.immediate.transfer'].create( | ||
{'pick_ids': [(4, pick_order_2.id)]}) | ||
wiz.process() | ||
self.assertEqual(pick_move_2.state, 'done') | ||
self.assertEqual(pick_order_2.state, 'done') | ||
self.assertEqual(pick_order_2.completion_info, 'next_picking_ready') | ||
self.assertEqual(pick_order_1.completion_info, 'next_picking_ready') | ||
|
||
def test_picking_with_backorder(self): | ||
self._init_inventory() | ||
ship_order, pack_order, pick_order = self._create_pickings() | ||
ship_move_1 = self._create_move(ship_order, self.product_1) | ||
pack_move_1 = self._create_move( | ||
pack_order, self.product_1, move_dest=ship_move_1 | ||
) | ||
pick_move_1 = self._create_move( | ||
pick_order, self.product_1, state='confirmed', | ||
procure_method='make_to_stock', move_dest=pack_move_1 | ||
) | ||
ship_move_2 = self._create_move(ship_order, self.product_2) | ||
pack_move_2 = self._create_move( | ||
pack_order, self.product_2, move_dest=ship_move_2 | ||
) | ||
pick_move_2 = self._create_move( | ||
pick_order, self.product_2, state='confirmed', | ||
procure_method='make_to_stock', move_dest=pack_move_2 | ||
) | ||
self.assertEqual(pick_move_1.state, 'confirmed') | ||
self.assertEqual(pick_move_2.state, 'confirmed') | ||
self.assertEqual(pick_order.state, 'confirmed') | ||
self.assertEqual(pick_order.completion_info, 'full_order_picking') | ||
pick_order.action_assign() | ||
self.assertEqual(pick_move_1.state, 'assigned') | ||
self.assertEqual(pick_move_2.state, 'assigned') | ||
self.assertEqual(pick_order.state, 'assigned') | ||
self.assertEqual(pick_order.completion_info, 'full_order_picking') | ||
# Process partially to create backorder | ||
pick_move_1.move_line_ids.qty_done = 1.0 | ||
pick_move_2.move_line_ids.qty_done = \ | ||
pick_move_2.move_line_ids.product_uom_qty | ||
pick_order.action_done() | ||
pick_backorder = self.env['stock.picking'].search( | ||
[('backorder_id', '=', pick_order.id)] | ||
) | ||
pick_backorder_move = pick_backorder.move_lines | ||
self.assertEqual(pick_move_1.state, 'done') | ||
self.assertEqual(pick_move_2.state, 'done') | ||
self.assertEqual(pick_order.state, 'done') | ||
self.assertEqual(pick_backorder_move.state, 'assigned') | ||
self.assertEqual(pick_backorder.state, 'assigned') | ||
self.assertEqual(pick_order.completion_info, 'no') | ||
self.assertEqual(pick_backorder.completion_info, 'last_picking') | ||
# Process backorder | ||
pick_backorder_move.move_line_ids.qty_done = \ | ||
pick_backorder_move.move_line_ids.product_uom_qty | ||
pick_backorder.action_done() | ||
self.assertEqual(pick_backorder_move.state, 'done') | ||
self.assertEqual(pick_backorder.state, 'done') | ||
self.assertEqual(pick_order.completion_info, 'next_picking_ready') | ||
self.assertEqual(pick_backorder.completion_info, 'next_picking_ready') |
Oops, something went wrong.