[12.0][ADD] stock_picking_product_kit_helper

This commit is contained in:
Kitti U
2019-06-17 08:17:04 +07:00
committed by clementmbr
parent 959396c05a
commit 57454ef1f8
13 changed files with 367 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
# Copyright 2019 Kitti U. - Ecosoft <kittiu@ecosoft.co.th>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import models

View File

@@ -0,0 +1,20 @@
# Copyright 2019 Kitti U. - Ecosoft <kittiu@ecosoft.co.th>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
'name': 'Stock Picking Product Kit Helper',
'summary': 'Set quanity in picking line based on product kit quantity',
'version': '12.0.1.0.0',
'category': 'Stock',
'website': 'https://github.com/OCA/manufacture',
'author': 'Ecosoft, Odoo Community Association (OCA)',
'license': 'AGPL-3',
'installable': True,
'depends': [
'sale_mrp',
],
'data': [
'security/ir.model.access.csv',
'views/stock_view.xml',
],
'maintainers': ['kittiu']
}

View File

@@ -0,0 +1,4 @@
# Copyright 2019 Kitti U. - Ecosoft <kittiu@ecosoft.co.th>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import stock_picking

View File

@@ -0,0 +1,133 @@
# Copyright 2019 Kitti U. - Ecosoft <kittiu@ecosoft.co.th>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class StockPicking(models.Model):
_inherit = 'stock.picking'
product_kit_helper_ids = fields.One2many(
comodel_name='stock.picking.product.kit.helper',
string='Product Kit Helper Lines',
inverse_name='picking_id',
readonly=False,
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
)
has_product_kit = fields.Boolean(
string='Has Product Kit',
compute='_compute_has_product_kit',
help="True if there is at least 1 product kit in the sales order",
)
@api.model
def _is_product_kit(self, product, company):
BOM = self.env['mrp.bom'].sudo()
bom = BOM._bom_find(product=product,
company_id=company.id)
return bom and bom.type == 'phantom'
@api.multi
def _compute_has_product_kit(self):
for picking in self:
if any(self._is_product_kit(line.product_id, line.company_id)
for line in picking.move_lines.mapped('sale_line_id')):
picking.has_product_kit = True
@api.multi
def show_product_kit(self):
"""Find move_lines with product kit to create helper line."""
self.ensure_one()
BOM = self.env['mrp.bom'].sudo()
helpers = []
for sale_line in self.move_lines.mapped('sale_line_id'):
bom = BOM._bom_find(product=sale_line.product_id,
company_id=sale_line.company_id.id)
if bom and bom.type == 'phantom': # Create product kit line
helpers.append((0, 0, {'sale_line_id': sale_line.id,
'product_id': sale_line.product_id.id,
'product_uom_qty': 0.0,
}))
self.product_kit_helper_ids.unlink()
self.write({'product_kit_helper_ids': helpers})
@api.multi
def action_product_kit_helper(self):
"""Assign product kit's quantity to stock move."""
self.ensure_one()
if self.state in ('done', 'cancel'):
raise ValidationError(
_('Product Kit Helper is not allowed on current state'))
for helper in self.product_kit_helper_ids:
helper.action_explode_helper()
class StockPickingProductKitHelper(models.Model):
_name = 'stock.picking.product.kit.helper'
_description = """
Product Kit Helper, allow user to specify quantity of product kit,
to explode as product quantity in operations tab
"""
picking_id = fields.Many2one(
comodel_name='stock.picking',
string='Picking',
required=True,
index=True,
ondelete='cascade',
)
sale_line_id = fields.Many2one(
comodel_name='sale.order.line',
string='Sales Order Line',
required=True,
)
product_id = fields.Many2one(
comodel_name='product.product',
string='Product',
required=True,
readonly=True,
)
product_uom_qty = fields.Float(
string='Quantity',
)
product_uom = fields.Many2one(
comodel_name='uom.uom',
string='Unit of Measure',
related='sale_line_id.product_uom',
readonly=True,
)
@api.multi
def action_explode_helper(self):
"""Explodes product kit quantity to detailed product in stock move."""
self.ensure_one()
# Mock stock.move, in order to resue stock.move's action_explode
StockMove = self.env['stock.move']
mock_loc = self.env['stock.location'].sudo().search([], limit=1)
mock_pt = self.env['stock.picking.type'].sudo().search([], limit=1)
mock_stock_move = StockMove.sudo().create({
'name': '/',
'product_id': self.product_id.id,
'product_uom': self.product_uom.id,
'product_uom_qty': self.product_uom_qty,
'picking_type_id': mock_pt.id,
'location_id': mock_loc.id,
'location_dest_id': mock_loc.id,
})
# Reuse explode function and assign quantity_done in stock.move
mock_processed_moves = mock_stock_move.action_explode()
for mock_move in mock_processed_moves:
stock_move = StockMove.search([
('picking_id', '=', self.picking_id.id),
('sale_line_id', '=', self.sale_line_id.id),
('product_id', '=', mock_move.product_id.id)])
if not stock_move:
continue
if len(stock_move) != 1:
raise ValidationError(
_('No matching detailed product %s for product kit %s') %
(mock_move.product_id.display_name,
self.product_id.display_name))
stock_move.write({'quantity_done': mock_move.product_uom_qty})
mock_processed_moves.sudo().unlink()

View File

@@ -0,0 +1 @@
* Kitti Upariphutthiphong <kittiu@ecosoft.co.th>

View File

@@ -0,0 +1,6 @@
When sales order contain product kits (product with BOM of type kit),
the delivery order (stock.move) created by it will be exploded to multiple product lines.
Normally, to partially deliver, user will calculate manually the quantity of each product lines to delivery.
This module add new tab "Product Kit Help" to help in calculate quantity in product line with ease.

View File

@@ -0,0 +1,9 @@
When origin sales order of the underlining delivery order contains at least 1 product kit,
the tab "Product Kit Helper" will appear.
To use the helper, go to the "Product Kit Helper" tab,
#. Click => Show Product Kit
#. Edit and fill in required quantity
#. Click Assign Operation Quantity
#. Check result in Operations tab

View File

@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_stock_picking_product_kit_helper_user,stock.picking.product.kit.helper user,model_stock_picking_product_kit_helper,stock.group_stock_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_stock_picking_product_kit_helper_user stock.picking.product.kit.helper user model_stock_picking_product_kit_helper stock.group_stock_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,4 @@
# Copyright 2019 Kitti U. - Ecosoft <kittiu@ecosoft.co.th>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import test_stock_picking_product_kit_helper

View File

@@ -0,0 +1,64 @@
# Copyright 2019 Kitti U. - Ecosoft <kittiu@ecosoft.co.th>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tests import common, Form
from odoo.exceptions import ValidationError
class TestStockPickingProductKitHelper(common.TransactionCase):
def setUp(self):
super(TestStockPickingProductKitHelper, self).setUp()
self.partner = self.env.ref('base.res_partner_2')
self.table_kit = self.env.ref('mrp.product_product_table_kit')
def test_00_sale_product_kit_helper(self):
"""Test sale order with product kit, I expect,
- Product is exploded on picking
- Use helper, will assign quantity to stock.move correctly
- After picking is done, do not allow to use helper
"""
# Create sales order of 10 table kit
order_form = Form(self.env['sale.order'])
order_form.partner_id = self.partner
with order_form.order_line.new() as line:
line.product_id = self.table_kit
line.product_uom_qty = 10
order = order_form.save()
order.action_confirm()
# In the picking, product line is exploded.
picking = order.mapped('picking_ids')
self.assertEqual(len(picking), 1)
stock_moves = picking.move_lines
# 1 SO line exploded to 2 moves
moves = [{'product': x.product_id.name, 'qty': x.product_uom_qty}
for x in stock_moves]
self.assertEqual(moves,
[{'product': 'Wood Panel', 'qty': 10.0},
{'product': 'Bolt', 'qty': 40.0}])
self.assertTrue(picking.has_product_kit)
self.assertFalse(picking.product_kit_helper_ids) # Not show yet
picking.show_product_kit()
self.assertEqual(len(picking.product_kit_helper_ids), 1)
# Assign product set 4 qty and test that it apply to stock.move
picking.product_kit_helper_ids[0].write({'product_uom_qty': 4.0})
picking.action_product_kit_helper()
moves = [{'product': x.product_id.name, 'qty': x.quantity_done}
for x in stock_moves]
self.assertEqual(moves,
[{'product': 'Wood Panel', 'qty': 4.0},
{'product': 'Bolt', 'qty': 16.0}])
# Assign again to 10 qty
picking.product_kit_helper_ids[0].write({'product_uom_qty': 10.0})
picking.action_product_kit_helper()
moves = [{'product': x.product_id.name, 'qty': x.quantity_done}
for x in stock_moves]
self.assertEqual(moves,
[{'product': 'Wood Panel', 'qty': 10.0},
{'product': 'Bolt', 'qty': 40.0}])
# Validate Picking
picking.button_validate()
self.assertEqual(picking.state, 'done')
# After done state, block the helper
with self.assertRaises(ValidationError):
picking.action_product_kit_helper()

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2019 Kitti U. - Ecosoft <carlos.dauden@tecnativa.com>
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_picking_form" model="ir.ui.view">
<field name="name">stock.picking.form</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<xpath expr="/form/sheet/notebook/page[@name='extra']" position="before">
<page string="Kit Helper" attrs="{'invisible': [('has_product_kit', '=', False)]}">
<p class="oe_grey">
To deliver partial product kits, you can use this tab to help calculate
quantity and auto fill in "Done" column in Operations tab.<br/>
<ol>
<li>Click => Show Product Kit</li>
<li>Edit and fill in required quantity</li>
<li>Click Assign Operation Quantity</li>
<li>Check result in Operations tab</li>
</ol>
</p>
<field name="has_product_kit" invisible="1"/>
<button name="show_product_kit" type="object" string="⇒ Show Product Kit" class="oe_link"
attrs="{'invisible': ['|', ('product_kit_helper_ids', '!=', []), ('state', 'in', ['done', 'cancel'])]}"/>
<button name="action_product_kit_helper" type="object" string="Assign Operation Quantity"
class="oe_highlight" attrs="{'invisible': ['|', ('product_kit_helper_ids', '=', []), ('state', 'in', ['done', 'cancel'])]}"/>
<field name="product_kit_helper_ids" attrs="{'invisible': [('product_kit_helper_ids', '=', [])]}">
<tree editable="bottom" create="0" delete="0">
<field name="product_id"/>
<field name="product_uom_qty"/>
<field name="product_uom" groups="uom.group_uom"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
</odoo>