Odoo18-Base/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py
2025-03-10 11:12:23 +07:00

482 lines
21 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import Command
from odoo.exceptions import UserError
from odoo.tests import Form
from odoo.addons.mrp_subcontracting.tests.common import TestMrpSubcontractingCommon
class MrpSubcontractingPurchaseTest(TestMrpSubcontractingCommon):
def setUp(self):
super().setUp()
self.finished2, self.comp3 = self.env['product.product'].create([{
'name': 'SuperProduct',
'type': 'product',
}, {
'name': 'Component',
'type': 'consu',
}])
self.vendor = self.env['res.partner'].create({
'name': 'Vendor',
'company_id': self.env.ref('base.main_company').id,
})
self.bom_finished2 = self.env['mrp.bom'].create({
'product_tmpl_id': self.finished2.product_tmpl_id.id,
'type': 'subcontract',
'subcontractor_ids': [(6, 0, self.subcontractor_partner1.ids)],
'bom_line_ids': [(0, 0, {
'product_id': self.comp3.id,
'product_qty': 1,
})],
})
def test_count_smart_buttons(self):
resupply_sub_on_order_route = self.env['stock.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
(self.comp1 + self.comp2).write({'route_ids': [Command.link(resupply_sub_on_order_route.id)]})
# I create a draft Purchase Order for first in move for 10 kg at 50 euro
po = self.env['purchase.order'].create({
'partner_id': self.subcontractor_partner1.id,
'order_line': [Command.create({
'name': 'finished',
'product_id': self.finished.id,
'product_qty': 1.0,
'product_uom': self.finished.uom_id.id,
'price_unit': 50.0}
)],
})
po.button_confirm()
self.assertEqual(po.subcontracting_resupply_picking_count, 1)
action1 = po.action_view_subcontracting_resupply()
picking = self.env[action1['res_model']].browse(action1['res_id'])
self.assertEqual(picking.subcontracting_source_purchase_count, 1)
action2 = picking.action_view_subcontracting_source_purchase()
po_action2 = self.env[action2['res_model']].browse(action2['res_id'])
self.assertEqual(po_action2, po)
def test_decrease_qty(self):
""" Tests when a PO for a subcontracted product has its qty decreased after confirmation
"""
product_qty = 5.0
po = self.env['purchase.order'].create({
'partner_id': self.subcontractor_partner1.id,
'order_line': [Command.create({
'name': 'finished',
'product_id': self.finished.id,
'product_qty': product_qty,
'product_uom': self.finished.uom_id.id,
'price_unit': 50.0}
)],
})
po.button_confirm()
receipt = po.picking_ids
sub_mo = receipt._get_subcontract_production()
self.assertEqual(len(receipt), 1, "A receipt should have been created")
self.assertEqual(receipt.move_ids.product_qty, product_qty, "Qty of subcontracted product to receive is incorrect")
self.assertEqual(len(sub_mo), 1, "A subcontracting MO should have been created")
self.assertEqual(sub_mo.product_qty, product_qty, "Qty of subcontracted product to produce is incorrect")
# create a neg qty to proprogate to receipt
lower_qty = product_qty - 1.0
po.order_line.product_qty = lower_qty
sub_mos = receipt._get_subcontract_production()
self.assertEqual(receipt.move_ids.product_qty, lower_qty, "Qty of subcontracted product to receive should update (not validated yet)")
self.assertEqual(len(sub_mos), 1, "Original subcontract MO should have absorbed qty change")
self.assertEqual(sub_mo.product_qty, lower_qty, "Qty of subcontract MO should update (none validated yet)")
# increase qty again
po.order_line.product_qty = product_qty
sub_mos = receipt._get_subcontract_production()
self.assertEqual(sum(receipt.move_ids.mapped('product_qty')), product_qty, "Qty of subcontracted product to receive should update (not validated yet)")
self.assertEqual(len(sub_mos), 1, "The subcontracted mo should have been updated")
# check that a neg qty can't proprogate once receipt is done
for move in receipt.move_ids:
move.move_line_ids.qty_done = move.product_qty
receipt.button_validate()
self.assertEqual(receipt.state, 'done')
self.assertEqual(sub_mos.state, 'done')
with self.assertRaises(UserError):
po.order_line.product_qty = lower_qty
def test_purchase_and_return01(self):
"""
The user buys 10 x a subcontracted product P. He receives the 10
products and then does a return with 3 x P. The test ensures that the
final received quantity is correctly computed
"""
po = self.env['purchase.order'].create({
'partner_id': self.subcontractor_partner1.id,
'order_line': [(0, 0, {
'name': self.finished2.name,
'product_id': self.finished2.id,
'product_uom_qty': 10,
'product_uom': self.finished2.uom_id.id,
'price_unit': 1,
})],
})
po.button_confirm()
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom_finished2.id)])
self.assertTrue(mo)
receipt = po.picking_ids
receipt.move_ids.quantity_done = 10
receipt.button_validate()
return_form = Form(self.env['stock.return.picking'].with_context(active_id=receipt.id, active_model='stock.picking'))
return_wizard = return_form.save()
return_wizard.product_return_moves.quantity = 3
return_wizard.product_return_moves.to_refund = True
return_id, _ = return_wizard._create_returns()
return_picking = self.env['stock.picking'].browse(return_id)
return_picking.move_ids.quantity_done = 3
return_picking.button_validate()
self.assertEqual(self.finished2.qty_available, 7.0)
self.assertEqual(po.order_line.qty_received, 7.0)
def test_purchase_and_return02(self):
"""
The user buys 10 x a subcontracted product P. He receives the 10
products and then does a return with 3 x P (with the flag to_refund
disabled and the subcontracting location as return location). The test
ensures that the final received quantity is correctly computed
"""
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
self.env.user.write({'groups_id': [(4, grp_multi_loc.id)]})
po = self.env['purchase.order'].create({
'partner_id': self.subcontractor_partner1.id,
'order_line': [(0, 0, {
'name': self.finished2.name,
'product_id': self.finished2.id,
'product_uom_qty': 10,
'product_uom': self.finished2.uom_id.id,
'price_unit': 1,
})],
})
po.button_confirm()
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom_finished2.id)])
self.assertTrue(mo)
receipt = po.picking_ids
receipt.move_ids.quantity_done = 10
receipt.button_validate()
return_form = Form(self.env['stock.return.picking'].with_context(active_id=receipt.id, active_model='stock.picking'))
return_form.location_id = self.env.company.subcontracting_location_id
return_wizard = return_form.save()
return_wizard.product_return_moves.quantity = 3
return_wizard.product_return_moves.to_refund = False
return_id, _ = return_wizard._create_returns()
return_picking = self.env['stock.picking'].browse(return_id)
return_picking.move_ids.quantity_done = 3
return_picking.button_validate()
self.assertEqual(self.finished2.qty_available, 7.0)
self.assertEqual(po.order_line.qty_received, 10.0)
def test_orderpoint_warehouse_not_required(self):
"""
The user creates a subcontracted bom for the product,
then we create a po for the subcontracted bom we are gonna get
orderpoints for the components without warehouse.Notice this is
when our subcontracting location is also a replenish location.
The test ensure that we can get those orderpoints without warehouse.
"""
# Create a second warehouse to check which one will be used
self.env['stock.warehouse'].create({'name': 'Second WH', 'code': 'WH02'})
product = self.env['product.product'].create({
'name': 'Product',
'detailed_type': 'product',
})
component = self.env['product.product'].create({
'name': 'Component',
'detailed_type': 'product',
})
subcontractor = self.env['res.partner'].create({
'name': 'Subcontractor',
'property_stock_subcontractor': self.env.company.subcontracting_location_id.id,
})
self.env.company.subcontracting_location_id.replenish_location = True
self.env['mrp.bom'].create({
'product_tmpl_id': product.product_tmpl_id.id,
'product_qty': 1,
'product_uom_id': product.uom_id.id,
'type': 'subcontract',
'subcontractor_ids': [(subcontractor.id)],
'bom_line_ids': [(0, 0, {
'product_id': component.id,
'product_qty': 1,
'product_uom_id': component.uom_id.id,
})],
})
po = self.env['purchase.order'].create({
'partner_id': subcontractor.id,
'order_line': [(0, 0, {
'product_id': product.id,
'product_qty': 1,
'product_uom': product.uom_id.id,
'name': product.name,
'price_unit': 1,
})],
})
po.button_confirm()
self.env['stock.warehouse.orderpoint']._get_orderpoint_action()
orderpoint = self.env['stock.warehouse.orderpoint'].search([('product_id', '=', component.id)])
self.assertTrue(orderpoint)
self.assertEqual(orderpoint.warehouse_id, self.warehouse)
def test_purchase_and_return03(self):
"""
With 2 steps receipt and an input location child of Physical Location (instead of WH)
The user buys 10 x a subcontracted product P. He receives the 10
products and then does a return with 3 x P. The test ensures that the
final received quantity is correctly computed
"""
# Set 2 steps receipt
self.warehouse.write({"reception_steps": "two_steps"})
# Set 'Input' parent location to 'Physical locations'
physical_locations = self.env.ref("stock.stock_location_locations")
input_location = self.warehouse.wh_input_stock_loc_id
input_location.write({"location_id": physical_locations.id})
# Create Purchase
po = self.env['purchase.order'].create({
'partner_id': self.subcontractor_partner1.id,
'order_line': [(0, 0, {
'name': self.finished2.name,
'product_id': self.finished2.id,
'product_uom_qty': 10,
'product_uom': self.finished2.uom_id.id,
'price_unit': 1,
})],
})
po.button_confirm()
# Receive Products
receipt = po.picking_ids
receipt.move_ids.quantity_done = 10
receipt.button_validate()
self.assertEqual(po.order_line.qty_received, 10.0)
# Return Products
return_form = Form(self.env['stock.return.picking'].with_context(active_id=receipt.id, active_model='stock.picking'))
return_wizard = return_form.save()
return_wizard.product_return_moves.quantity = 3
return_wizard.product_return_moves.to_refund = True
return_id, _ = return_wizard._create_returns()
return_picking = self.env['stock.picking'].browse(return_id)
return_picking.move_ids.quantity_done = 3
return_picking.button_validate()
self.assertEqual(po.order_line.qty_received, 7.0)
def test_return_and_decrease_pol_qty(self):
"""
Buy and receive 10 subcontracted products. Return one. Then adapt the
demand on the PO to 9.
"""
po = self.env['purchase.order'].create({
'partner_id': self.subcontractor_partner1.id,
'order_line': [(0, 0, {
'name': self.finished2.name,
'product_id': self.finished2.id,
'product_qty': 10,
'product_uom': self.finished2.uom_id.id,
'price_unit': 1,
})],
})
po.button_confirm()
receipt = po.picking_ids
receipt.move_ids.quantity_done = 10
receipt.button_validate()
return_form = Form(self.env['stock.return.picking'].with_context(active_id=receipt.id, active_model='stock.picking'))
wizard = return_form.save()
wizard.product_return_moves.quantity = 1.0
return_picking_id, _pick_type_id = wizard._create_returns()
return_picking = self.env['stock.picking'].browse(return_picking_id)
return_picking.move_ids.quantity_done = 1.0
return_picking.button_validate()
pol = po.order_line
pol.product_qty = 9.0
stock_location_id = self.warehouse.lot_stock_id
subco_location_id = self.env.company.subcontracting_location_id
self.assertEqual(pol.qty_received, 9.0)
self.assertEqual(pol.product_qty, 9.0)
self.assertEqual(len(po.picking_ids), 2)
self.assertRecordValues(po.picking_ids.move_ids, [
{'location_dest_id': stock_location_id.id, 'quantity_done': 10.0, 'state': 'done'},
{'location_dest_id': subco_location_id.id, 'quantity_done': 1.0, 'state': 'done'},
])
def test_resupply_order_buy_mto(self):
""" Test a subcontract component can has resupply on order + buy + mto route"""
mto_route = self.env.ref('stock.route_warehouse0_mto')
mto_route.active = True
resupply_sub_on_order_route = self.env['stock.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
(self.comp1 + self.comp2).write({
'route_ids': [
Command.link(resupply_sub_on_order_route.id),
Command.link(self.env.ref('purchase_stock.route_warehouse0_buy').id),
Command.link(mto_route.id)],
'seller_ids': [Command.create({
'partner_id': self.vendor.id,
})],
})
po = self.env['purchase.order'].create({
'partner_id': self.subcontractor_partner1.id,
'order_line': [Command.create({
'name': 'finished',
'product_id': self.finished.id,
'product_qty': 1.0,
'product_uom': self.finished.uom_id.id,
'price_unit': 50.0}
)],
})
po.button_confirm()
ressuply_pick = self.env['stock.picking'].search([('location_dest_id', '=', self.env.company.subcontracting_location_id.id)])
self.assertEqual(len(ressuply_pick.move_ids), 2)
self.assertEqual(ressuply_pick.move_ids.mapped('product_id'), self.comp1 | self.comp2)
# should have create a purchase order for the components
comp_po = self.env['purchase.order'].search([('partner_id', '=', self.vendor.id)])
self.assertEqual(len(comp_po.order_line), 2)
self.assertEqual(comp_po.order_line.mapped('product_id'), self.comp1 | self.comp2)
# confirm the po should create stock moves linked to the resupply
comp_po.button_confirm()
comp_receipt = comp_po.picking_ids
self.assertEqual(comp_receipt.move_ids.move_dest_ids, ressuply_pick.move_ids)
# validate the comp receipt should reserve the resupply
self.assertEqual(ressuply_pick.state, 'waiting')
comp_receipt.move_ids.quantity_done = 1
comp_receipt.button_validate()
self.assertEqual(ressuply_pick.state, 'assigned')
def test_update_qty_purchased_with_subcontracted_product(self):
"""
Test That we can update the quantity of a purchase order line with a subcontracted product
"""
mto_route = self.env.ref('stock.route_warehouse0_mto')
buy_route = self.env['stock.route'].search([('name', '=', 'Buy')])
mto_route.active = True
self.finished.route_ids = mto_route.ids + buy_route.ids
seller = self.env['product.supplierinfo'].create({
'partner_id': self.vendor.id,
'price': 12.0,
'delay': 0
})
self.finished.seller_ids = [(6, 0, [seller.id])]
mo = self.env['mrp.production'].create({
'product_id': self.finished2.id,
'product_qty': 3.0,
'move_raw_ids': [(0, 0, {
'product_id': self.finished.id,
'product_uom_qty': 3.0,
'product_uom': self.finished.uom_id.id,
})]
})
mo.action_confirm()
po = self.env['purchase.order.line'].search([('product_id', '=', self.finished.id)]).order_id
po.button_confirm()
self.assertEqual(len(po.picking_ids), 1)
picking = po.picking_ids
picking.move_ids.quantity_done = 2.0
# When we validate the picking manually, we create a backorder.
backorder_wizard_dict = picking.button_validate()
backorder_wizard = Form(self.env[backorder_wizard_dict['res_model']].with_context(backorder_wizard_dict['context'])).save()
backorder_wizard.process()
self.assertEqual(len(po.picking_ids), 2)
picking.backorder_ids.action_cancel()
self.assertEqual(picking.backorder_ids.state, 'cancel')
po.order_line.product_qty = 2.0
self.assertEqual(po.order_line.product_qty, 2.0)
def test_location_after_dest_location_update_backorder_production(self):
"""
Buy 2 subcontracted products.
Receive 1 product after changing the destination location.
Create a backorder.
Receive the last one.
Check the locations.
"""
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
self.env.user.write({'groups_id': [Command.link(grp_multi_loc.id)]})
subcontract_loc = self.env.company.subcontracting_location_id
production_loc = self.finished.property_stock_production
final_loc = self.env['stock.location'].create({
'name': 'Final location',
'location_id': self.env.ref('stock.warehouse0').lot_stock_id.id,
})
# buy 2 subcontracted products
po = self.env['purchase.order'].create({
'partner_id': self.subcontractor_partner1.id,
'order_line': [Command.create({
'name': self.finished.name,
'product_id': self.finished.id,
'product_qty': 2.0,
'product_uom': self.finished.uom_id.id,
'price_unit': 1.0,
})],
})
po.button_confirm()
receipt = po.picking_ids
# receive 1 subcontracted product
receipt.move_ids.quantity_done = 1
receipt_form = Form(receipt)
# change the destination location
with self.assertLogs(level="WARNING"):
receipt_form.location_dest_id = final_loc
receipt_form.save()
# change the destination location on the move line too
receipt.move_line_ids.location_dest_id = final_loc
# create the backorder
backorder_wizard_dict = receipt.button_validate()
backorder_wizard = Form(self.env[backorder_wizard_dict['res_model']].with_context(backorder_wizard_dict['context'])).save()
backorder_wizard.process()
backorder = receipt.backorder_ids
# test the stock quantities after receiving 1 product
stock_quants = self.env['stock.quant'].search([('product_id', '=', self.finished.id)])
self.assertEqual(len(stock_quants), 3)
self.assertEqual(stock_quants.filtered(lambda q: q.location_id == final_loc).quantity, 1.0)
self.assertEqual(stock_quants.filtered(lambda q: q.location_id == subcontract_loc).quantity, 0.0)
self.assertEqual(stock_quants.filtered(lambda q: q.location_id == production_loc).quantity, -1.0)
# receive the last subcontracted product
backorder.move_ids.quantity_done = 1
backorder.button_validate()
# test the final stock quantities
stock_quants = self.env['stock.quant'].search([('product_id', '=', self.finished.id)])
self.assertEqual(len(stock_quants), 3)
self.assertEqual(stock_quants.filtered(lambda q: q.location_id == final_loc).quantity, 2.0)
self.assertEqual(stock_quants.filtered(lambda q: q.location_id == subcontract_loc).quantity, 0.0)
self.assertEqual(stock_quants.filtered(lambda q: q.location_id == production_loc).quantity, -2.0)