-
-
Notifications
You must be signed in to change notification settings - Fork 719
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
12.0 create module stock picking completion info
- Loading branch information
1 parent
fce3e7c
commit 56d5540
Showing
9 changed files
with
375 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,20 @@ | ||
# Copyright 2019 Camptocamp SA | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) | ||
{ | ||
"name": "Stock Picking Completion Info", | ||
"summary": "Display completion information according to next pickings", | ||
"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,59 @@ | ||
# 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 order pick at processing and at' | ||
' completion' | ||
) | ||
|
||
|
||
class StockPicking(models.Model): | ||
|
||
_inherit = 'stock.picking' | ||
|
||
completion_info = fields.Selection( | ||
[ | ||
('no', 'No'), | ||
('last_picking', 'Completion of this picking allows next pickings ' | ||
'to be processed.'), | ||
('next_picking_ready', 'Next pickings are ready 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( | ||
m.state in ('done', 'cancel') for m in depending_moves | ||
): | ||
picking.completion_info = 'next_picking_ready' | ||
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 |
253 changes: 253 additions & 0 deletions
253
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,253 @@ | ||
# 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, 'last_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, 'last_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, 'last_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, 'last_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') |
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,30 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<odoo> | ||
<record id="view_picking_type_form_inherit" model="ir.ui.view"> | ||
<field name="name">Operation Types inherit</field> | ||
<field name="inherit_id" ref="stock.view_picking_type_form" /> | ||
<field name="model">stock.picking.type</field> | ||
<field name="arch" type="xml"> | ||
<field name="show_reserved" position="after"> | ||
<field name="display_completion_info" /> | ||
</field> | ||
</field> | ||
</record> | ||
|
||
<record id="view_picking_form_inherit" model="ir.ui.view"> | ||
<field name="name">stock.picking.form.inherit</field> | ||
<field name="inherit_id" ref="stock.view_picking_form" /> | ||
<field name="model">stock.picking</field> | ||
<field name="arch" type="xml"> | ||
<xpath expr="//h1" position="after"> | ||
<label for="completion_info" invisible="1" /> | ||
<div class="alert alert-success" attrs="{'invisible': [('completion_info', '!=', 'next_picking_ready')]}" role="alert"> | ||
<field name="completion_info" nolabel="1" /> | ||
</div> | ||
<div class="alert alert-warning" attrs="{'invisible': [('completion_info', '!=', 'last_picking')]}" role="alert"> | ||
<field name="completion_info" nolabel="1" /> | ||
</div> | ||
</xpath> | ||
</field> | ||
</record> | ||
</odoo> |