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>
|
||||
# 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']
|
||||
"name": "Stock Picking Product Kit Helper",
|
||||
"summary": "Set quanity in picking line based on product kit quantity",
|
||||
"version": "13.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"],
|
||||
}
|
||||
|
||||
@@ -1,100 +1,103 @@
|
||||
# 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 import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
_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',
|
||||
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)]},
|
||||
states={"done": [("readonly", True)], "cancel": [("readonly", True)]},
|
||||
)
|
||||
has_product_kit = fields.Boolean(
|
||||
string='Has Product Kit',
|
||||
compute='_compute_has_product_kit',
|
||||
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'
|
||||
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')):
|
||||
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()
|
||||
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,
|
||||
}))
|
||||
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})
|
||||
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'):
|
||||
if self.state in ("done", "cancel"):
|
||||
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:
|
||||
helper.action_explode_helper()
|
||||
|
||||
|
||||
class StockPickingProductKitHelper(models.Model):
|
||||
_name = 'stock.picking.product.kit.helper'
|
||||
_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',
|
||||
comodel_name="stock.picking",
|
||||
string="Picking",
|
||||
required=True,
|
||||
index=True,
|
||||
ondelete='cascade',
|
||||
ondelete="cascade",
|
||||
)
|
||||
sale_line_id = fields.Many2one(
|
||||
comodel_name='sale.order.line',
|
||||
string='Sales Order Line',
|
||||
required=True,
|
||||
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',
|
||||
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',
|
||||
comodel_name="uom.uom",
|
||||
string="Unit of Measure",
|
||||
related="sale_line_id.product_uom",
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
@@ -103,31 +106,36 @@ class StockPickingProductKitHelper(models.Model):
|
||||
"""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,
|
||||
})
|
||||
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)])
|
||||
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})
|
||||
_("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()
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
# 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
|
||||
from odoo.tests import Form, common
|
||||
|
||||
|
||||
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')
|
||||
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,
|
||||
@@ -19,7 +18,7 @@ class TestStockPickingProductKitHelper(common.TransactionCase):
|
||||
- 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 = 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
|
||||
@@ -27,38 +26,45 @@ class TestStockPickingProductKitHelper(common.TransactionCase):
|
||||
order = order_form.save()
|
||||
order.action_confirm()
|
||||
# In the picking, product line is exploded.
|
||||
picking = order.mapped('picking_ids')
|
||||
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}])
|
||||
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.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}])
|
||||
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.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}])
|
||||
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')
|
||||
self.assertEqual(picking.state, "done")
|
||||
# After done state, block the helper
|
||||
with self.assertRaises(ValidationError):
|
||||
picking.action_product_kit_helper()
|
||||
|
||||
Reference in New Issue
Block a user