mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[IMP] stock_picking_product_kit_helper: black, isort
This commit is contained in:
@@ -1,20 +1,15 @@
|
|||||||
# Copyright 2019 Kitti U. - Ecosoft <kittiu@ecosoft.co.th>
|
# Copyright 2019 Kitti U. - Ecosoft <kittiu@ecosoft.co.th>
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
{
|
{
|
||||||
'name': 'Stock Picking Product Kit Helper',
|
"name": "Stock Picking Product Kit Helper",
|
||||||
'summary': 'Set quanity in picking line based on product kit quantity',
|
"summary": "Set quanity in picking line based on product kit quantity",
|
||||||
'version': '12.0.1.0.0',
|
"version": "13.0.1.0.0",
|
||||||
'category': 'Stock',
|
"category": "Stock",
|
||||||
'website': 'https://github.com/OCA/manufacture',
|
"website": "https://github.com/OCA/manufacture",
|
||||||
'author': 'Ecosoft, Odoo Community Association (OCA)',
|
"author": "Ecosoft, Odoo Community Association (OCA)",
|
||||||
'license': 'AGPL-3',
|
"license": "AGPL-3",
|
||||||
'installable': True,
|
"installable": True,
|
||||||
'depends': [
|
"depends": ["sale_mrp"],
|
||||||
'sale_mrp',
|
"data": ["security/ir.model.access.csv", "views/stock_view.xml"],
|
||||||
],
|
"maintainers": ["kittiu"],
|
||||||
'data': [
|
|
||||||
'security/ir.model.access.csv',
|
|
||||||
'views/stock_view.xml',
|
|
||||||
],
|
|
||||||
'maintainers': ['kittiu']
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +1,103 @@
|
|||||||
# Copyright 2019 Kitti U. - Ecosoft <kittiu@ecosoft.co.th>
|
# Copyright 2019 Kitti U. - Ecosoft <kittiu@ecosoft.co.th>
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo import models, fields, api, _
|
from odoo import _, api, fields, models
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
class StockPicking(models.Model):
|
class StockPicking(models.Model):
|
||||||
_inherit = 'stock.picking'
|
_inherit = "stock.picking"
|
||||||
|
|
||||||
product_kit_helper_ids = fields.One2many(
|
product_kit_helper_ids = fields.One2many(
|
||||||
comodel_name='stock.picking.product.kit.helper',
|
comodel_name="stock.picking.product.kit.helper",
|
||||||
string='Product Kit Helper Lines',
|
string="Product Kit Helper Lines",
|
||||||
inverse_name='picking_id',
|
inverse_name="picking_id",
|
||||||
readonly=False,
|
readonly=False,
|
||||||
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
|
states={"done": [("readonly", True)], "cancel": [("readonly", True)]},
|
||||||
)
|
)
|
||||||
has_product_kit = fields.Boolean(
|
has_product_kit = fields.Boolean(
|
||||||
string='Has Product Kit',
|
string="Has Product Kit",
|
||||||
compute='_compute_has_product_kit',
|
compute="_compute_has_product_kit",
|
||||||
help="True if there is at least 1 product kit in the sales order",
|
help="True if there is at least 1 product kit in the sales order",
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _is_product_kit(self, product, company):
|
def _is_product_kit(self, product, company):
|
||||||
BOM = self.env['mrp.bom'].sudo()
|
BOM = self.env["mrp.bom"].sudo()
|
||||||
bom = BOM._bom_find(product=product,
|
bom = BOM._bom_find(product=product, company_id=company.id)
|
||||||
company_id=company.id)
|
return bom and bom.type == "phantom"
|
||||||
return bom and bom.type == 'phantom'
|
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _compute_has_product_kit(self):
|
def _compute_has_product_kit(self):
|
||||||
for picking in self:
|
for picking in self:
|
||||||
if any(self._is_product_kit(line.product_id, line.company_id)
|
if any(
|
||||||
for line in picking.move_lines.mapped('sale_line_id')):
|
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
|
picking.has_product_kit = True
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def show_product_kit(self):
|
def show_product_kit(self):
|
||||||
"""Find move_lines with product kit to create helper line."""
|
"""Find move_lines with product kit to create helper line."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
BOM = self.env['mrp.bom'].sudo()
|
BOM = self.env["mrp.bom"].sudo()
|
||||||
helpers = []
|
helpers = []
|
||||||
for sale_line in self.move_lines.mapped('sale_line_id'):
|
for sale_line in self.move_lines.mapped("sale_line_id"):
|
||||||
bom = BOM._bom_find(product=sale_line.product_id,
|
bom = BOM._bom_find(
|
||||||
company_id=sale_line.company_id.id)
|
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,
|
if bom and bom.type == "phantom": # Create product kit line
|
||||||
'product_id': sale_line.product_id.id,
|
helpers.append(
|
||||||
'product_uom_qty': 0.0,
|
(
|
||||||
}))
|
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.product_kit_helper_ids.unlink()
|
||||||
self.write({'product_kit_helper_ids': helpers})
|
self.write({"product_kit_helper_ids": helpers})
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def action_product_kit_helper(self):
|
def action_product_kit_helper(self):
|
||||||
"""Assign product kit's quantity to stock move."""
|
"""Assign product kit's quantity to stock move."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
if self.state in ('done', 'cancel'):
|
if self.state in ("done", "cancel"):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_('Product Kit Helper is not allowed on current state'))
|
_("Product Kit Helper is not allowed on current state")
|
||||||
|
)
|
||||||
for helper in self.product_kit_helper_ids:
|
for helper in self.product_kit_helper_ids:
|
||||||
helper.action_explode_helper()
|
helper.action_explode_helper()
|
||||||
|
|
||||||
|
|
||||||
class StockPickingProductKitHelper(models.Model):
|
class StockPickingProductKitHelper(models.Model):
|
||||||
_name = 'stock.picking.product.kit.helper'
|
_name = "stock.picking.product.kit.helper"
|
||||||
_description = """
|
_description = """
|
||||||
Product Kit Helper, allow user to specify quantity of product kit,
|
Product Kit Helper, allow user to specify quantity of product kit,
|
||||||
to explode as product quantity in operations tab
|
to explode as product quantity in operations tab
|
||||||
"""
|
"""
|
||||||
|
|
||||||
picking_id = fields.Many2one(
|
picking_id = fields.Many2one(
|
||||||
comodel_name='stock.picking',
|
comodel_name="stock.picking",
|
||||||
string='Picking',
|
string="Picking",
|
||||||
required=True,
|
required=True,
|
||||||
index=True,
|
index=True,
|
||||||
ondelete='cascade',
|
ondelete="cascade",
|
||||||
)
|
)
|
||||||
sale_line_id = fields.Many2one(
|
sale_line_id = fields.Many2one(
|
||||||
comodel_name='sale.order.line',
|
comodel_name="sale.order.line", string="Sales Order Line", required=True
|
||||||
string='Sales Order Line',
|
|
||||||
required=True,
|
|
||||||
)
|
)
|
||||||
product_id = fields.Many2one(
|
product_id = fields.Many2one(
|
||||||
comodel_name='product.product',
|
comodel_name="product.product", string="Product", required=True, readonly=True
|
||||||
string='Product',
|
|
||||||
required=True,
|
|
||||||
readonly=True,
|
|
||||||
)
|
|
||||||
product_uom_qty = fields.Float(
|
|
||||||
string='Quantity',
|
|
||||||
)
|
)
|
||||||
|
product_uom_qty = fields.Float(string="Quantity")
|
||||||
product_uom = fields.Many2one(
|
product_uom = fields.Many2one(
|
||||||
comodel_name='uom.uom',
|
comodel_name="uom.uom",
|
||||||
string='Unit of Measure',
|
string="Unit of Measure",
|
||||||
related='sale_line_id.product_uom',
|
related="sale_line_id.product_uom",
|
||||||
readonly=True,
|
readonly=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,31 +106,36 @@ class StockPickingProductKitHelper(models.Model):
|
|||||||
"""Explodes product kit quantity to detailed product in stock move."""
|
"""Explodes product kit quantity to detailed product in stock move."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
# Mock stock.move, in order to resue stock.move's action_explode
|
# Mock stock.move, in order to resue stock.move's action_explode
|
||||||
StockMove = self.env['stock.move']
|
StockMove = self.env["stock.move"]
|
||||||
mock_loc = self.env['stock.location'].sudo().search([], limit=1)
|
mock_loc = self.env["stock.location"].sudo().search([], limit=1)
|
||||||
mock_pt = self.env['stock.picking.type'].sudo().search([], limit=1)
|
mock_pt = self.env["stock.picking.type"].sudo().search([], limit=1)
|
||||||
mock_stock_move = StockMove.sudo().create({
|
mock_stock_move = StockMove.sudo().create(
|
||||||
'name': '/',
|
{
|
||||||
'product_id': self.product_id.id,
|
"name": "/",
|
||||||
'product_uom': self.product_uom.id,
|
"product_id": self.product_id.id,
|
||||||
'product_uom_qty': self.product_uom_qty,
|
"product_uom": self.product_uom.id,
|
||||||
'picking_type_id': mock_pt.id,
|
"product_uom_qty": self.product_uom_qty,
|
||||||
'location_id': mock_loc.id,
|
"picking_type_id": mock_pt.id,
|
||||||
'location_dest_id': mock_loc.id,
|
"location_id": mock_loc.id,
|
||||||
})
|
"location_dest_id": mock_loc.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
# Reuse explode function and assign quantity_done in stock.move
|
# Reuse explode function and assign quantity_done in stock.move
|
||||||
mock_processed_moves = mock_stock_move.action_explode()
|
mock_processed_moves = mock_stock_move.action_explode()
|
||||||
for mock_move in mock_processed_moves:
|
for mock_move in mock_processed_moves:
|
||||||
stock_move = StockMove.search([
|
stock_move = StockMove.search(
|
||||||
('picking_id', '=', self.picking_id.id),
|
[
|
||||||
('sale_line_id', '=', self.sale_line_id.id),
|
("picking_id", "=", self.picking_id.id),
|
||||||
('product_id', '=', mock_move.product_id.id)])
|
("sale_line_id", "=", self.sale_line_id.id),
|
||||||
|
("product_id", "=", mock_move.product_id.id),
|
||||||
|
]
|
||||||
|
)
|
||||||
if not stock_move:
|
if not stock_move:
|
||||||
continue
|
continue
|
||||||
if len(stock_move) != 1:
|
if len(stock_move) != 1:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_('No matching detailed product %s for product kit %s') %
|
_("No matching detailed product %s for product kit %s")
|
||||||
(mock_move.product_id.display_name,
|
% (mock_move.product_id.display_name, self.product_id.display_name)
|
||||||
self.product_id.display_name))
|
)
|
||||||
stock_move.write({'quantity_done': mock_move.product_uom_qty})
|
stock_move.write({"quantity_done": mock_move.product_uom_qty})
|
||||||
mock_processed_moves.sudo().unlink()
|
mock_processed_moves.sudo().unlink()
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
# Copyright 2019 Kitti U. - Ecosoft <kittiu@ecosoft.co.th>
|
# Copyright 2019 Kitti U. - Ecosoft <kittiu@ecosoft.co.th>
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo.tests import common, Form
|
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
from odoo.tests import Form, common
|
||||||
|
|
||||||
|
|
||||||
class TestStockPickingProductKitHelper(common.TransactionCase):
|
class TestStockPickingProductKitHelper(common.TransactionCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestStockPickingProductKitHelper, self).setUp()
|
super(TestStockPickingProductKitHelper, self).setUp()
|
||||||
self.partner = self.env.ref('base.res_partner_2')
|
self.partner = self.env.ref("base.res_partner_2")
|
||||||
self.table_kit = self.env.ref('mrp.product_product_table_kit')
|
self.table_kit = self.env.ref("mrp.product_product_table_kit")
|
||||||
|
|
||||||
def test_00_sale_product_kit_helper(self):
|
def test_00_sale_product_kit_helper(self):
|
||||||
"""Test sale order with product kit, I expect,
|
"""Test sale order with product kit, I expect,
|
||||||
@@ -19,7 +18,7 @@ class TestStockPickingProductKitHelper(common.TransactionCase):
|
|||||||
- After picking is done, do not allow to use helper
|
- After picking is done, do not allow to use helper
|
||||||
"""
|
"""
|
||||||
# Create sales order of 10 table kit
|
# Create sales order of 10 table kit
|
||||||
order_form = Form(self.env['sale.order'])
|
order_form = Form(self.env["sale.order"])
|
||||||
order_form.partner_id = self.partner
|
order_form.partner_id = self.partner
|
||||||
with order_form.order_line.new() as line:
|
with order_form.order_line.new() as line:
|
||||||
line.product_id = self.table_kit
|
line.product_id = self.table_kit
|
||||||
@@ -27,38 +26,45 @@ class TestStockPickingProductKitHelper(common.TransactionCase):
|
|||||||
order = order_form.save()
|
order = order_form.save()
|
||||||
order.action_confirm()
|
order.action_confirm()
|
||||||
# In the picking, product line is exploded.
|
# In the picking, product line is exploded.
|
||||||
picking = order.mapped('picking_ids')
|
picking = order.mapped("picking_ids")
|
||||||
self.assertEqual(len(picking), 1)
|
self.assertEqual(len(picking), 1)
|
||||||
stock_moves = picking.move_lines
|
stock_moves = picking.move_lines
|
||||||
# 1 SO line exploded to 2 moves
|
# 1 SO line exploded to 2 moves
|
||||||
moves = [{'product': x.product_id.name, 'qty': x.product_uom_qty}
|
moves = [
|
||||||
for x in stock_moves]
|
{"product": x.product_id.name, "qty": x.product_uom_qty}
|
||||||
self.assertEqual(moves,
|
for x in stock_moves
|
||||||
[{'product': 'Wood Panel', 'qty': 10.0},
|
]
|
||||||
{'product': 'Bolt', 'qty': 40.0}])
|
self.assertEqual(
|
||||||
|
moves,
|
||||||
|
[{"product": "Wood Panel", "qty": 10.0}, {"product": "Bolt", "qty": 40.0}],
|
||||||
|
)
|
||||||
self.assertTrue(picking.has_product_kit)
|
self.assertTrue(picking.has_product_kit)
|
||||||
self.assertFalse(picking.product_kit_helper_ids) # Not show yet
|
self.assertFalse(picking.product_kit_helper_ids) # Not show yet
|
||||||
picking.show_product_kit()
|
picking.show_product_kit()
|
||||||
self.assertEqual(len(picking.product_kit_helper_ids), 1)
|
self.assertEqual(len(picking.product_kit_helper_ids), 1)
|
||||||
# Assign product set 4 qty and test that it apply to stock.move
|
# 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.product_kit_helper_ids[0].write({"product_uom_qty": 4.0})
|
||||||
picking.action_product_kit_helper()
|
picking.action_product_kit_helper()
|
||||||
moves = [{'product': x.product_id.name, 'qty': x.quantity_done}
|
moves = [
|
||||||
for x in stock_moves]
|
{"product": x.product_id.name, "qty": x.quantity_done} for x in stock_moves
|
||||||
self.assertEqual(moves,
|
]
|
||||||
[{'product': 'Wood Panel', 'qty': 4.0},
|
self.assertEqual(
|
||||||
{'product': 'Bolt', 'qty': 16.0}])
|
moves,
|
||||||
|
[{"product": "Wood Panel", "qty": 4.0}, {"product": "Bolt", "qty": 16.0}],
|
||||||
|
)
|
||||||
# Assign again to 10 qty
|
# Assign again to 10 qty
|
||||||
picking.product_kit_helper_ids[0].write({'product_uom_qty': 10.0})
|
picking.product_kit_helper_ids[0].write({"product_uom_qty": 10.0})
|
||||||
picking.action_product_kit_helper()
|
picking.action_product_kit_helper()
|
||||||
moves = [{'product': x.product_id.name, 'qty': x.quantity_done}
|
moves = [
|
||||||
for x in stock_moves]
|
{"product": x.product_id.name, "qty": x.quantity_done} for x in stock_moves
|
||||||
self.assertEqual(moves,
|
]
|
||||||
[{'product': 'Wood Panel', 'qty': 10.0},
|
self.assertEqual(
|
||||||
{'product': 'Bolt', 'qty': 40.0}])
|
moves,
|
||||||
|
[{"product": "Wood Panel", "qty": 10.0}, {"product": "Bolt", "qty": 40.0}],
|
||||||
|
)
|
||||||
# Validate Picking
|
# Validate Picking
|
||||||
picking.button_validate()
|
picking.button_validate()
|
||||||
self.assertEqual(picking.state, 'done')
|
self.assertEqual(picking.state, "done")
|
||||||
# After done state, block the helper
|
# After done state, block the helper
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
picking.action_product_kit_helper()
|
picking.action_product_kit_helper()
|
||||||
|
|||||||
Reference in New Issue
Block a user