402 lines
19 KiB
Python
402 lines
19 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo.exceptions import UserError
|
|
from odoo.tests import Form
|
|
from odoo.fields import Command
|
|
|
|
from .test_common import TestQualityMrpCommon
|
|
|
|
|
|
class TestQualityCheck(TestQualityMrpCommon):
|
|
|
|
def test_00_production_quality_check(self):
|
|
|
|
"""Test quality check on production order and its backorder."""
|
|
|
|
# Create Quality Point for product Laptop Customized with Manufacturing Operation Type.
|
|
self.qality_point_test1 = self.env['quality.point'].create({
|
|
'product_ids': [(4, self.product_id)],
|
|
'picking_type_ids': [(4, self.picking_type_id)],
|
|
})
|
|
|
|
# Check that quality point created.
|
|
assert self.qality_point_test1, "First Quality Point not created for Laptop Customized."
|
|
|
|
# Create Production Order of Laptop Customized to produce 5.0 Unit.
|
|
production_form = Form(self.env['mrp.production'])
|
|
production_form.product_id = self.env['product.product'].browse(self.product_id)
|
|
production_form.product_qty = 5.0
|
|
self.mrp_production_qc_test1 = production_form.save()
|
|
|
|
# Check that Production Order of Laptop Customized to produce 5.0 Unit is created.
|
|
assert self.mrp_production_qc_test1, "Production Order not created."
|
|
|
|
# Perform check availability and produce product.
|
|
self.mrp_production_qc_test1.action_confirm()
|
|
self.mrp_production_qc_test1.action_assign()
|
|
|
|
mo_form = Form(self.mrp_production_qc_test1)
|
|
mo_form.qty_producing = self.mrp_production_qc_test1.product_qty - 1
|
|
mo_form.lot_producing_ids.set(self.lot_product_27_0)
|
|
for move in self.mrp_production_qc_test1.move_raw_ids:
|
|
details_operation_form = Form(move, view=self.env.ref('stock.view_stock_move_operations'))
|
|
with details_operation_form.move_line_ids.edit(0) as ml:
|
|
ml.lot_id = self.lot_product_product_drawer_drawer_0 if ml.product_id == self.product_product_drawer_drawer else self.lot_product_product_drawer_case_0
|
|
details_operation_form.save()
|
|
|
|
self.mrp_production_qc_test1 = mo_form.save()
|
|
self.mrp_production_qc_test1.move_raw_ids[0].picked = True
|
|
# Check Quality Check for Production is created and check it's state is 'none'.
|
|
self.assertEqual(len(self.mrp_production_qc_test1.check_ids), 1)
|
|
self.assertEqual(self.mrp_production_qc_test1.check_ids.quality_state, 'none')
|
|
|
|
# 'Pass' Quality Checks of production order.
|
|
self.mrp_production_qc_test1.check_ids.do_pass()
|
|
|
|
# Set MO Done and create backorder
|
|
action = self.mrp_production_qc_test1.button_mark_done()
|
|
consumption_warning = Form(self.env['mrp.consumption.warning'].with_context(**action['context']))
|
|
action = consumption_warning.save().action_confirm()
|
|
backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
|
|
backorder.save().action_backorder()
|
|
|
|
# Now check state of quality check.
|
|
self.assertEqual(self.mrp_production_qc_test1.check_ids.quality_state, 'pass')
|
|
# Check that the Quality Check was created on the backorder
|
|
self.assertEqual(len(self.mrp_production_qc_test1.production_group_id.production_ids[-1].check_ids), 1)
|
|
|
|
def test_02_quality_check_scrapped(self):
|
|
"""
|
|
Test that when scrapping a manufacturing order, no quality check is created for that move
|
|
"""
|
|
product = self.env['product.product'].create({'name': 'Time'})
|
|
component = self.env['product.product'].create({'name': 'Money'})
|
|
|
|
# Create a quality point for Manufacturing on All Operations (All Operations is set by default)
|
|
qp = self.env['quality.point'].create({'picking_type_ids': [(4, self.picking_type_id)]})
|
|
# Create a Manufacturing order for a product
|
|
mo_form = Form(self.env['mrp.production'])
|
|
mo_form.product_id = product
|
|
mri_form = mo_form.move_raw_ids.new()
|
|
mri_form.product_id = component
|
|
mri_form.product_uom_qty = 1
|
|
mri_form.save()
|
|
mo = mo_form.save()
|
|
mo.action_confirm()
|
|
# Delete the created quality check
|
|
qc = self.env['quality.check'].search([('product_id', '=', product.id), ('point_id', '=', qp.id)])
|
|
qc.unlink()
|
|
|
|
# Scrap the Manufacturing Order
|
|
scrap = self.env['stock.scrap'].with_context(active_model='mrp.production', active_id=mo.id).create({
|
|
'product_id': product.id,
|
|
'scrap_qty': 1.0,
|
|
'product_uom_id': product.uom_id.id,
|
|
'production_id': mo.id
|
|
})
|
|
scrap.do_scrap()
|
|
self.assertEqual(len(self.env['quality.check'].search([('product_id', '=', product.id), ('point_id', '=', qp.id)])), 0, "Quality checks should not be created for scrap moves")
|
|
|
|
def test_03_quality_check_on_operations(self):
|
|
""" Test Quality Check creation of 'operation' type, meaning only one QC will be created per MO.
|
|
"""
|
|
quality_point_operation_type = self.env['quality.point'].create({
|
|
'picking_type_ids': [(4, self.picking_type_id)],
|
|
'measure_on': 'operation',
|
|
'test_type_id': self.env.ref('quality_control.test_type_passfail').id
|
|
})
|
|
|
|
production_form = Form(self.env['mrp.production'])
|
|
production_form.product_id = self.env['product.product'].browse(self.product_id)
|
|
production_form.product_qty = 5.0
|
|
production = production_form.save()
|
|
production.action_confirm()
|
|
|
|
self.assertEqual(len(production.check_ids), 1)
|
|
self.assertEqual(production.check_ids.point_id, quality_point_operation_type)
|
|
self.assertEqual(production.check_ids.production_id, production)
|
|
|
|
# Do the quality checks and create backorder
|
|
production.check_ids.do_pass()
|
|
production.qty_producing = 3.0
|
|
production.lot_producing_ids = self.lot_product_27_0
|
|
for move in production.move_raw_ids:
|
|
details_operation_form = Form(move, view=self.env.ref('stock.view_stock_move_operations'))
|
|
with details_operation_form.move_line_ids.edit(0) as ml:
|
|
ml.lot_id = self.lot_product_product_drawer_drawer_0 if ml.product_id == self.product_product_drawer_drawer else self.lot_product_product_drawer_case_0
|
|
details_operation_form.save()
|
|
production.move_raw_ids[1].picked = True
|
|
action = production.button_mark_done()
|
|
consumption_warning = Form(self.env['mrp.consumption.warning'].with_context(**action['context']))
|
|
action = consumption_warning.save().action_confirm()
|
|
backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
|
|
backorder.save().action_backorder()
|
|
production_backorder = production.production_group_id.production_ids[-1]
|
|
self.assertEqual(len(production_backorder.check_ids), 1)
|
|
self.assertEqual(production_backorder.check_ids.point_id, quality_point_operation_type)
|
|
self.assertEqual(production_backorder.check_ids.production_id, production_backorder)
|
|
|
|
def test_quality_check_serial_backorder(self):
|
|
"""Create a MO for a product tracked by serial number.
|
|
Generate all but one serial numbers and create a back order.
|
|
"""
|
|
# Set up Products
|
|
product_to_build = self.env['product.product'].create({
|
|
'name': 'Young Tom',
|
|
'is_storable': True,
|
|
'tracking': 'serial',
|
|
})
|
|
product_to_use_1 = self.env['product.product'].create({
|
|
'name': 'Botox',
|
|
'is_storable': True,
|
|
})
|
|
product_to_use_2 = self.env['product.product'].create({
|
|
'name': 'Old Tom',
|
|
'is_storable': True,
|
|
})
|
|
bom_1 = self.env['mrp.bom'].create({
|
|
'product_id': product_to_build.id,
|
|
'product_tmpl_id': product_to_build.product_tmpl_id.id,
|
|
'product_qty': 1.0,
|
|
'type': 'normal',
|
|
'bom_line_ids': [
|
|
(0, 0, {'product_id': product_to_use_2.id, 'product_qty': 1}),
|
|
(0, 0, {'product_id': product_to_use_1.id, 'product_qty': 1})
|
|
]})
|
|
|
|
# Create Quality Point for product Laptop Customized with Manufacturing Operation Type.
|
|
self.qality_point_test1 = self.env['quality.point'].create({
|
|
'product_ids': [(4, product_to_build.id)],
|
|
'picking_type_ids': [(4, self.picking_type_id)],
|
|
})
|
|
|
|
# Start manufacturing
|
|
mo_form = Form(self.env['mrp.production'])
|
|
mo_form.product_id = product_to_build
|
|
mo_form.bom_id = bom_1
|
|
mo_form.product_qty = 5
|
|
mo = mo_form.save()
|
|
mo.action_confirm()
|
|
|
|
# Make some stock and reserve
|
|
for product in mo.move_raw_ids.product_id:
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': product.id,
|
|
'inventory_quantity': 100,
|
|
'location_id': mo.location_src_id.id,
|
|
})._apply_inventory()
|
|
mo.action_assign()
|
|
# 'Pass' Quality Checks of production order.
|
|
self.assertEqual(len(mo.check_ids), 1)
|
|
mo.check_ids.do_pass()
|
|
|
|
action = mo.action_generate_serial()
|
|
wizard = Form.from_action(self.env, action)
|
|
wizard.lot_name = 'sn#1'
|
|
wizard.lot_quantity = mo.product_qty - 1
|
|
action = wizard.save().action_generate_serial_numbers()
|
|
wizard = Form.from_action(self.env, action)
|
|
wizard.save().action_apply()
|
|
|
|
# Last MO in sequence is the backorder
|
|
bo = mo.production_group_id.production_ids[-1]
|
|
self.assertEqual(len(bo.check_ids), 1)
|
|
|
|
def test_failure_quality_point_location_on_operation(self):
|
|
""" test the failure location is hidden in case of manufacturing quality point """
|
|
self.env['quality.point'].create({
|
|
'measure_on': 'operation',
|
|
'picking_type_ids': [Command.link(self.picking_type_id)],
|
|
'test_type_id': self.env.ref('quality_control.test_type_passfail').id,
|
|
'failure_location_ids': [Command.link(self.failure_location.id)],
|
|
})
|
|
production_form = Form(self.env['mrp.production'])
|
|
production_form.product_id = self.env['product.product'].browse(self.product_id)
|
|
production_form.product_qty = 1
|
|
production = production_form.save()
|
|
production.action_confirm()
|
|
production.qty_producing = 1
|
|
production.lot_producing_ids = self.lot_product_27_0
|
|
production.move_raw_ids[0].move_line_ids[0].lot_id = self.lot_product_product_drawer_drawer_0
|
|
production.move_raw_ids[1].move_line_ids[0].lot_id = self.lot_product_product_drawer_case_0
|
|
production.move_raw_ids.picked = True
|
|
check_action = production.check_quality()
|
|
quality_wizard = Form(self.env[check_action['res_model']].with_context(**check_action['context']))
|
|
quality_wizard = quality_wizard.save()
|
|
action = quality_wizard.do_fail()
|
|
quality_wizard = Form(self.env[action['res_model']].with_context(**action['context']))
|
|
quality_wizard = quality_wizard.save()
|
|
self.assertEqual(quality_wizard.failure_location_id, self.failure_location)
|
|
quality_wizard.confirm_fail()
|
|
self.assertEqual(production.location_dest_id.id, self.location_dest_id)
|
|
self.assertEqual(production.move_finished_ids.location_dest_id, self.failure_location)
|
|
production.button_mark_done()
|
|
self.assertEqual(production.state, 'done')
|
|
self.assertEqual(production.move_finished_ids.location_dest_id, self.failure_location)
|
|
|
|
def test_failure_quality_point_location(self):
|
|
""" test the failure location is hidden in case of manufacturing quality point """
|
|
quality_point = self.env['quality.point'].create({
|
|
'measure_on': 'product',
|
|
'picking_type_ids': [Command.link(self.picking_type_id)],
|
|
'test_type_id': self.env.ref('quality_control.test_type_passfail').id,
|
|
})
|
|
self.assertTrue(quality_point.show_failure_location)
|
|
quality_point.test_type_id = self.env.ref('quality.test_type_instructions')
|
|
self.assertFalse(quality_point.show_failure_location)
|
|
quality_point.test_type_id = self.env.ref('quality_control.test_type_passfail')
|
|
workcenter = self.env['mrp.workcenter'].create({
|
|
'name': 'Test workcenter',
|
|
})
|
|
|
|
operation = self.env['mrp.routing.workcenter'].create({
|
|
'name': 'Test order',
|
|
'workcenter_id': workcenter.id,
|
|
'bom_id': self.bom.id,
|
|
'time_cycle_manual': 30,
|
|
})
|
|
quality_point.operation_id = operation
|
|
self.assertFalse(quality_point.show_failure_location)
|
|
|
|
def test_production_product_control_point(self):
|
|
"""Test quality control point on production order."""
|
|
|
|
# Create Quality Point for product with Manufacturing Operation Type.
|
|
self.qality_point_test1 = self.env['quality.point'].create({
|
|
'picking_type_ids': [(4, self.picking_type_id)],
|
|
'measure_on': 'product',
|
|
})
|
|
|
|
self.bom.consumption = 'flexible'
|
|
# Create Production Order of 5.0 Unit.
|
|
production_form = Form(self.env['mrp.production'])
|
|
production_form.product_id = self.env['product.product'].browse(self.product_id)
|
|
production_form.product_qty = 5.0
|
|
self.mrp_production_qc_test1 = production_form.save()
|
|
|
|
# Perform check availability and produce product.
|
|
self.mrp_production_qc_test1.action_confirm()
|
|
self.mrp_production_qc_test1.action_assign()
|
|
|
|
mo_form = Form(self.mrp_production_qc_test1)
|
|
mo_form.qty_producing = self.mrp_production_qc_test1.product_qty
|
|
mo_form.lot_producing_ids.set(self.lot_product_27_0)
|
|
for move in self.mrp_production_qc_test1.move_raw_ids:
|
|
details_operation_form = Form(move, view=self.env.ref('stock.view_stock_move_operations'))
|
|
with details_operation_form.move_line_ids.edit(0) as ml:
|
|
ml.lot_id = self.lot_product_product_drawer_drawer_0 if ml.product_id == self.product_product_drawer_drawer else self.lot_product_product_drawer_case_0
|
|
details_operation_form.save()
|
|
|
|
self.mrp_production_qc_test1 = mo_form.save()
|
|
self.mrp_production_qc_test1.move_raw_ids[0].picked = True
|
|
# Check Quality Check for Production is created.
|
|
self.assertEqual(len(self.mrp_production_qc_test1.check_ids), 1)
|
|
|
|
# 'Pass' Quality Checks of production order.
|
|
self.mrp_production_qc_test1.check_ids.do_pass()
|
|
|
|
# Set MO Done.
|
|
self.mrp_production_qc_test1.button_mark_done()
|
|
|
|
# Now check that no new quality check are created.
|
|
self.assertEqual(len(self.mrp_production_qc_test1.check_ids), 1)
|
|
|
|
def test_quantity_control_point_with_production(self):
|
|
"""Test that it's not possible to create a Quantity quality check type with a manufacturing operation type."""
|
|
with self.assertRaises(UserError):
|
|
self.qality_point_test1 = self.env['quality.point'].create({
|
|
'picking_type_ids': [Command.link(self.picking_type_id)],
|
|
'measure_on': 'move_line',
|
|
})
|
|
|
|
def test_manufacture_picking_type_with_product_categ_in_qp(self):
|
|
"""Create a quality point of type measure on'operation' with the manufacturing
|
|
picking type and product category set, and verify that the quality check
|
|
is correctly created.
|
|
"""
|
|
self.bom.product_tmpl_id.categ_id = self.product_category_base
|
|
qp = self.env['quality.point'].create({
|
|
'product_category_ids': [Command.link(self.product_category_base.id)],
|
|
'picking_type_ids': [Command.link(self.picking_type_id)],
|
|
'measure_on': 'operation',
|
|
'test_type_id': self.env.ref('quality_control.test_type_passfail').id,
|
|
})
|
|
production = self.env['mrp.production'].create({
|
|
'product_id': self.product.id,
|
|
'product_qty': 20,
|
|
'move_raw_ids': [
|
|
Command.create({
|
|
'product_id': self.product_3.id,
|
|
'product_uom_qty': 10,
|
|
})
|
|
],
|
|
})
|
|
production.action_confirm()
|
|
self.assertEqual(production.state, 'confirmed')
|
|
self.assertEqual(len(production.check_ids), 1)
|
|
self.assertEqual(production.check_ids.point_id, qp)
|
|
production.check_ids.do_pass()
|
|
self.assertEqual(production.check_ids.quality_state, 'pass')
|
|
|
|
def test_quality_check_failure_sets_lot_line_from_production_lot(self):
|
|
test_picking = self.env['stock.picking.type'].create({
|
|
'name': 'Recepit',
|
|
'code': 'incoming',
|
|
'sequence_code': 'TEST_RECEIPT',
|
|
})
|
|
quality_point = self.env['quality.point'].create({
|
|
'measure_on': 'operation',
|
|
'picking_type_ids': [Command.link(self.picking_type_id), (Command.link(test_picking.id))],
|
|
'test_type_id': self.env.ref('quality_control.test_type_measure').id,
|
|
'product_ids': [Command.link(self.product_id)],
|
|
})
|
|
mo = self.env['mrp.production'].create({
|
|
'product_id': self.product_id,
|
|
'product_qty': 1,
|
|
'lot_producing_ids': self.lot_product_27_0,
|
|
})
|
|
mo.action_confirm()
|
|
quality_point.update({
|
|
'picking_type_ids': [Command.unlink(self.picking_type_id)],
|
|
'measure_on': 'move_line',
|
|
})
|
|
check_action = mo.check_quality()
|
|
quality_wizard = Form(self.env[check_action['res_model']].with_context(**check_action['context']))
|
|
quality_wizard.measure = 1
|
|
action = quality_wizard.save().do_measure()
|
|
failed_check_wizard = Form(self.env[action['res_model']].with_context(**action['context']))
|
|
failed_check_wizard = failed_check_wizard.save()
|
|
self.assertEqual(failed_check_wizard.lot_line_id, mo.lot_producing_ids[:1])
|
|
|
|
def test_merge_mo_unlinks_cancelled_mo_quality_checks(self):
|
|
"""Test quality checks of the source MOs unlinks after a merge."""
|
|
quality_point = self.env['quality.point'].create({
|
|
'product_ids': [Command.link(self.product.id)],
|
|
'picking_type_ids': [Command.link(self.picking_type_id)],
|
|
})
|
|
productions = self.env['mrp.production'].create([{
|
|
'product_id': self.product.id,
|
|
'bom_id': self.bom.id,
|
|
'product_qty': 1,
|
|
'product_uom_id': self.product.uom_id.id,
|
|
'picking_type_id': self.picking_type_id,
|
|
}, {
|
|
'product_id': self.product.id,
|
|
'bom_id': self.bom.id,
|
|
'product_qty': 2,
|
|
'product_uom_id': self.product.uom_id.id,
|
|
'picking_type_id': self.picking_type_id,
|
|
}])
|
|
productions.action_confirm()
|
|
source_checks = productions.check_ids
|
|
self.assertEqual(len(source_checks), 2)
|
|
|
|
action = productions.action_merge()
|
|
merged_production = self.env['mrp.production'].browse(action['res_id'])
|
|
|
|
self.assertEqual(productions.mapped('state'), ['cancel', 'cancel'])
|
|
self.assertFalse(source_checks.exists())
|
|
self.assertEqual(merged_production.product_qty, 3)
|
|
self.assertEqual(merged_production.check_ids.point_id, quality_point)
|