Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WMS][12.0] Add stock_picking_completion_info - alpha version #693

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions stock_picking_completion_info/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
21 changes: 21 additions & 0 deletions stock_picking_completion_info/__manifest__.py
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",
grindtildeath marked this conversation as resolved.
Show resolved Hide resolved
"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",
],
}
1 change: 1 addition & 0 deletions stock_picking_completion_info/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import stock_picking
76 changes: 76 additions & 0 deletions stock_picking_completion_info/models/stock_picking.py
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'
)
],
grindtildeath marked this conversation as resolved.
Show resolved Hide resolved
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'
grindtildeath marked this conversation as resolved.
Show resolved Hide resolved
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'
1 change: 1 addition & 0 deletions stock_picking_completion_info/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Akim Juillerat <[email protected]>
9 changes: 9 additions & 0 deletions stock_picking_completion_info/readme/DESCRIPTION.rst
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.
1 change: 1 addition & 0 deletions stock_picking_completion_info/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_stock_picking_completion_info
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')
Loading