Skip to content

Commit

Permalink
12.0 create module stock picking completion info
Browse files Browse the repository at this point in the history
  • Loading branch information
grindtildeath committed Sep 2, 2019
1 parent fce3e7c commit 56d5540
Show file tree
Hide file tree
Showing 9 changed files with 375 additions and 0 deletions.
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
20 changes: 20 additions & 0 deletions stock_picking_completion_info/__manifest__.py
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",
],
}
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
59 changes: 59 additions & 0 deletions stock_picking_completion_info/models/stock_picking.py
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'
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,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')
30 changes: 30 additions & 0 deletions stock_picking_completion_info/views/stock_picking.xml
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>

0 comments on commit 56d5540

Please sign in to comment.