# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import datetime as dt, time from datetime import timedelta as td from json import loads from odoo import SUPERUSER_ID, Command from odoo.fields import Date from odoo.tests import Form, tagged, freeze_time from odoo.tests.common import TransactionCase from odoo.tools import format_date from odoo.tools.date_utils import add from odoo.exceptions import UserError, ValidationError @tagged('post_install', '-at_install') @freeze_time("2021-01-14 09:12:15") class TestReorderingRule(TransactionCase): @classmethod def setUpClass(cls): super(TestReorderingRule, cls).setUpClass() cls.partner = cls.env['res.partner'].create({ 'name': 'Smith' }) # create product and set the vendor product_form = Form(cls.env['product.product']) product_form.name = 'Product A' product_form.is_storable = True product_form.description = 'Internal Notes' with product_form.seller_ids.new() as seller: seller.partner_id = cls.partner product_form.route_ids.add(cls.env.ref('purchase_stock.route_warehouse0_buy')) cls.product_01 = product_form.save() def test_reordering_rule_1(self): """ - Receive products in 2 steps - The product has a reordering rule - Manually create and confirm a PO => the forecast should be updated - Cancel the PO => the forecast should be updated - Create a picking that automatically generates another PO - On the po generated, the source document should be the name of the reordering rule - Increase the quantity on the RFQ, the extra quantity should follow the push rules - Increase the quantity on the PO, the extra quantity should follow the push rules - There should be one move supplier -> input and two moves input -> stock """ warehouse_1 = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) warehouse_1.write({'reception_steps': 'two_steps'}) warehouse_2 = self.env['stock.warehouse'].create({'name': 'WH 2', 'code': 'WH2', 'company_id': self.env.company.id, 'partner_id': self.env.company.partner_id.id, 'reception_steps': 'one_step'}) # Create and set specific buyer for partner buyer_id = self.env['res.users'].create({ 'login': 'buyer1', 'name': 'Buyer1', 'email': 'buyer1@example.com', }) self.partner.buyer_id = buyer_id.id # create reordering rule orderpoint_form = Form(self.env['stock.warehouse.orderpoint']) orderpoint_form.warehouse_id = warehouse_1 orderpoint_form.location_id = warehouse_1.lot_stock_id orderpoint_form.product_id = self.product_01 orderpoint_form.product_min_qty = 0.000 orderpoint_form.product_max_qty = 0.000 order_point = orderpoint_form.save() # Manually create a PO, and check orderpoint forecast manual_po = self.env['purchase.order'].create({ 'name': 'Manual PO', 'partner_id': self.partner.id, 'order_line': [Command.create({ 'product_id': self.product_01.id, 'product_qty': 10, })], }) manual_po.button_confirm() self.assertEqual(order_point.qty_forecast, 10) manual_po.button_cancel() self.assertEqual(order_point.qty_forecast, 0) # Create Delivery Order of 10 product picking_form = Form(self.env['stock.picking']) picking_form.partner_id = self.partner picking_form.picking_type_id = self.env.ref('stock.picking_type_out') with picking_form.move_ids_without_package.new() as move: move.product_id = self.product_01 move.product_uom_qty = 10.0 customer_picking = picking_form.save() customer_picking.action_confirm() # Run scheduler self.env['procurement.group'].run_scheduler() # Check purchase order created or not purchase_order = self.env['purchase.order'].search([('partner_id', '=', self.partner.id), ('state', '!=', 'cancel')]) self.assertTrue(purchase_order, 'No purchase order created.') # Check the picking type on the purchase order purchase_order.picking_type_id = warehouse_2.in_type_id with self.assertRaises(UserError): purchase_order.button_confirm() purchase_order.picking_type_id = warehouse_1.in_type_id # On the po generated, the source document should be the name of the reordering rule self.assertEqual(order_point.name, purchase_order.origin, 'Source document on purchase order should be the name of the reordering rule.') self.assertEqual(purchase_order.order_line.product_qty, 10) self.assertEqual(purchase_order.order_line.name, 'Product A') self.assertEqual(purchase_order.user_id, buyer_id) # Increase the quantity on the RFQ before confirming it purchase_order.order_line.product_qty = 12 purchase_order.button_confirm() self.assertEqual(purchase_order.picking_ids.move_ids.filtered(lambda m: m.product_id == self.product_01).product_qty, 12) purchase_order.picking_ids.button_validate() next_picking = purchase_order.picking_ids.move_ids.move_dest_ids.picking_id self.assertEqual(len(next_picking), 1) self.assertEqual(next_picking.move_ids.filtered(lambda m: m.product_id == self.product_01).product_qty, 12) # Increase the quantity on the PO purchase_order.order_line.product_qty = 15 receipt1, receipt2 = purchase_order.picking_ids self.assertEqual(receipt1.move_ids.product_qty, 12) self.assertEqual(receipt2.move_ids.product_qty, 3) purchase_order.picking_ids[1].button_validate() self.assertEqual(next_picking.move_ids.product_qty, 15) def test_reordering_rule_2(self): """ - Receive products in 1 steps - The product has two reordering rules, each one applying in a sublocation - Processing the purchase order should fulfill the two sublocations - Increase the quantity on the RFQ for one of the POL, the extra quantity will go to the original subloc since we don't know where to push it (no move dest) - Increase the quantity on the PO, the extra quantity should follow the push rules and thus go to stock """ # Required for `warehouse_id` to be visible in the view self.env.user.groups_id += self.env.ref('stock.group_stock_multi_locations') warehouse_1 = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) subloc_1 = self.env['stock.location'].create({'name': 'subloc_1', 'location_id': warehouse_1.lot_stock_id.id}) subloc_2 = self.env['stock.location'].create({'name': 'subloc_2', 'location_id': warehouse_1.lot_stock_id.id}) # create reordering rules orderpoint_form = Form(self.env['stock.warehouse.orderpoint']) orderpoint_form.warehouse_id = warehouse_1 orderpoint_form.location_id = subloc_1 orderpoint_form.product_id = self.product_01 orderpoint_form.product_min_qty = 0.000 orderpoint_form.product_max_qty = 0.000 order_point_1 = orderpoint_form.save() orderpoint_form = Form(self.env['stock.warehouse.orderpoint']) orderpoint_form.warehouse_id = warehouse_1 orderpoint_form.location_id = subloc_2 orderpoint_form.product_id = self.product_01 orderpoint_form.product_min_qty = 0.000 orderpoint_form.product_max_qty = 0.000 order_point_2 = orderpoint_form.save() # Create Delivery Order of 10 product picking_form = Form(self.env['stock.picking']) picking_form.partner_id = self.partner picking_form.picking_type_id = self.env.ref('stock.picking_type_out') with picking_form.move_ids_without_package.new() as move: move.product_id = self.product_01 move.product_uom_qty = 10.0 with picking_form.move_ids_without_package.new() as move: move.product_id = self.product_01 move.product_uom_qty = 10.0 customer_picking = picking_form.save() customer_picking.move_ids[0].location_id = subloc_1.id customer_picking.move_ids[1].location_id = subloc_2.id # picking confirm customer_picking.action_confirm() self.assertEqual(self.product_01.with_context(location=subloc_1.id).virtual_available, -10) self.assertEqual(self.product_01.with_context(location=subloc_2.id).virtual_available, -10) # Run scheduler self.env['procurement.group'].run_scheduler() # Check purchase order created or not purchase_order = self.env['purchase.order'].search([('partner_id', '=', self.partner.id)]) self.assertTrue(purchase_order, 'No purchase order created.') self.assertEqual(len(purchase_order.order_line), 2, 'Not enough purchase order lines created.') # increment the qty of the first po line purchase_order.order_line.filtered(lambda pol: pol.orderpoint_id == order_point_1).product_qty = 15 purchase_order.button_confirm() self.assertEqual(self.product_01.with_context(location=subloc_1.id).virtual_available, 5) self.assertEqual(self.product_01.with_context(location=subloc_2.id).virtual_available, 0) # increment the qty of the second po line purchase_order.order_line.filtered(lambda pol: pol.orderpoint_id == order_point_2).product_qty = 15 self.assertEqual(self.product_01.with_context(location=subloc_1.id).virtual_available, 5) self.assertEqual(self.product_01.with_context(location=subloc_2.id).virtual_available, 5) self.assertEqual(self.product_01.with_context(location=warehouse_1.lot_stock_id.id).virtual_available, 10) # 5 on the subloc_2, 5 on subloc_1 def test_reordering_rule_3(self): """ trigger a reordering rule with a route to a location without warehouse """ warehouse_1 = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) outside_loc = self.env['stock.location'].create({ 'name': 'outside', 'usage': 'internal', 'location_id': self.env.ref('stock.stock_location_locations').id, }) route = self.env['stock.route'].create({ 'name': 'resupply outside', 'rule_ids': [ (0, False, { 'name': 'Buy', 'location_dest_id': warehouse_1.lot_stock_id.id, 'company_id': self.env.company.id, 'action': 'buy', 'sequence': 2, 'procure_method': 'make_to_stock', 'picking_type_id': self.env.ref('stock.picking_type_in').id, }), (0, False, { 'name': 'ressuply from stock', 'location_src_id': warehouse_1.lot_stock_id.id, 'location_dest_id': outside_loc.id, 'company_id': self.env.company.id, 'action': 'pull', 'procure_method': 'mts_else_mto', 'sequence': 1, 'picking_type_id': self.env.ref('stock.picking_type_out').id, }), ], }) vendor1 = self.env['res.partner'].create({'name': 'AAA', 'email': 'from.test@example.com'}) supplier_info1 = self.env['product.supplierinfo'].create({ 'partner_id': vendor1.id, 'price': 50, }) product = self.env['product.product'].create({ 'name': 'product_rr_3', 'is_storable': True, 'route_ids': [(4, route.id)], 'seller_ids': [(6, 0, [supplier_info1.id])], }) # create reordering rules # Required for `warehouse_id` to be visible in the view self.env['res.users'].browse(2).groups_id += self.env.ref('stock.group_stock_multi_locations') orderpoint_form = Form(self.env['stock.warehouse.orderpoint'].with_user(2)) orderpoint_form.warehouse_id = warehouse_1 orderpoint_form.location_id = outside_loc orderpoint_form.product_id = product orderpoint_form.product_min_qty = 0.000 orderpoint_form.product_max_qty = 0.000 order_point_1 = orderpoint_form.save() order_point_1.route_id = route order_point_1.trigger = 'manual' # Create move out of 10 product move = self.env['stock.move'].create({ 'name': 'move out', 'product_id': product.id, 'product_uom': product.uom_id.id, 'product_uom_qty': 10, 'location_id': outside_loc.id, 'location_dest_id': self.env.ref('stock.stock_location_customers').id, 'picking_type_id': self.env.ref('stock.picking_type_out').id, }) move._action_confirm() # Forecast on the order point should be -10 self.assertEqual(order_point_1.qty_forecast, -10) order_point_1.action_replenish() # Check purchase order created or not purchase_order = self.env['purchase.order.line'].search([('product_id', '=', product.id)]).order_id self.assertTrue(purchase_order, 'No purchase order created.') self.assertEqual(len(purchase_order.order_line), 1, 'Not enough purchase order lines created.') purchase_order.button_confirm() def test_reordering_rule_4(self): """ Test that a reordering rule where the min qty is larger than the max qty cannot be created """ warehouse_1 = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) with self.assertRaises(ValidationError, msg="The minimum quantity must be less than or equal to the maximum quantity."): self.env['stock.warehouse.orderpoint'].create({ 'warehouse_id': warehouse_1.id, 'location_id': warehouse_1.lot_stock_id.id, 'product_id': self.product_01.id, 'product_min_qty': 2, 'product_max_qty': 1, }) def test_reordering_rule_triggered_two_times(self): """ A product P wth RR 0-0-1. Confirm a delivery with 1 x P -> PO created for it. Confirm a second delivery, with 1 x P again: - The PO should be updated - The qty to order of the RR should be zero """ warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) stock_location = warehouse.lot_stock_id out_type = warehouse.out_type_id customer_location = self.env.ref('stock.stock_location_customers') rr = self.env['stock.warehouse.orderpoint'].create({ 'location_id': stock_location.id, 'product_id': self.product_01.id, 'product_min_qty': 0, 'product_max_qty': 0, 'qty_multiple': 1, }) delivery = self.env['stock.picking'].create({ 'picking_type_id': out_type.id, 'location_id': stock_location.id, 'location_dest_id': customer_location.id, 'move_ids': [(0, 0, { 'name': self.product_01.name, 'product_id': self.product_01.id, 'product_uom_qty': 1, 'product_uom': self.product_01.uom_id.id, 'location_id': stock_location.id, 'location_dest_id': customer_location.id, })] }) delivery.action_confirm() pol = self.env['purchase.order.line'].search([('product_id', '=', self.product_01.id)]) self.assertEqual(pol.product_qty, 1.0) self.assertEqual(rr.qty_to_order, 0.0) delivery = self.env['stock.picking'].create({ 'picking_type_id': out_type.id, 'location_id': stock_location.id, 'location_dest_id': customer_location.id, 'move_ids': [(0, 0, { 'name': self.product_01.name, 'product_id': self.product_01.id, 'product_uom_qty': 1, 'product_uom': self.product_01.uom_id.id, 'location_id': stock_location.id, 'location_dest_id': customer_location.id, })] }) delivery.action_confirm() self.assertEqual(pol.product_qty, 2.0) self.assertEqual(rr.qty_to_order, 0.0) def test_replenish_report_1(self): """Tests the auto generation of manual orderpoints. Opening multiple times the report should not duplicate the generated orderpoints. MTO products should not trigger the creation of generated orderpoints """ partner = self.env['res.partner'].create({ 'name': 'Tintin' }) route_buy = self.env.ref('purchase_stock.route_warehouse0_buy') route_mto = self.env.ref('stock.route_warehouse0_mto') product_form = Form(self.env['product.product']) product_form.name = 'Simple Product' product_form.is_storable = True with product_form.seller_ids.new() as s: s.partner_id = partner product = product_form.save() product_form = Form(self.env['product.product']) product_form.name = 'Product BUY + MTO' product_form.is_storable = True product_form.route_ids.add(route_buy) product_form.route_ids.add(route_mto) with product_form.seller_ids.new() as s: s.partner_id = partner product_buy_mto = product_form.save() # Create Delivery Order of 20 product and 10 buy + MTO picking_form = Form(self.env['stock.picking']) picking_form.partner_id = partner picking_form.picking_type_id = self.env.ref('stock.picking_type_out') with picking_form.move_ids_without_package.new() as move: move.product_id = product move.product_uom_qty = 10.0 with picking_form.move_ids_without_package.new() as move: move.product_id = product move.product_uom_qty = 10.0 with picking_form.move_ids_without_package.new() as move: move.product_id = product_buy_mto move.product_uom_qty = 10.0 customer_picking = picking_form.save() customer_picking.move_ids.filtered(lambda m: m.product_id == product_buy_mto).procure_method = 'make_to_order' customer_picking.action_confirm() self.env['stock.warehouse.orderpoint']._get_orderpoint_action() self.env['stock.warehouse.orderpoint']._get_orderpoint_action() orderpoint_product = self.env['stock.warehouse.orderpoint'].search( [('product_id', '=', product.id)]) orderpoint_product_mto_buy = self.env['stock.warehouse.orderpoint'].search( [('product_id', '=', product_buy_mto.id)]) self.assertFalse(orderpoint_product_mto_buy) self.assertEqual(len(orderpoint_product), 1.0) self.assertEqual(orderpoint_product.qty_to_order, 20.0) self.assertEqual(orderpoint_product.trigger, 'manual') self.assertEqual(orderpoint_product.create_uid.id, SUPERUSER_ID) orderpoint_product.action_replenish() po = self.env['purchase.order'].search([('partner_id', '=', partner.id)]) self.assertTrue(po) self.assertEqual(len(po.order_line), 2.0) po_line_product_mto = po.order_line.filtered(lambda l: l.product_id == product_buy_mto) po_line_product = po.order_line.filtered(lambda l: l.product_id == product) self.assertEqual(po_line_product_mto.product_uom_qty, 10.0) self.assertEqual(po_line_product.product_uom_qty, 20.0) self.env['stock.warehouse.orderpoint']._get_orderpoint_action() orderpoint_product = self.env['stock.warehouse.orderpoint'].search( [('product_id', '=', product.id)]) orderpoint_product_mto_buy = self.env['stock.warehouse.orderpoint'].search( [('product_id', '=', product_buy_mto.id)]) self.assertFalse(orderpoint_product) self.assertFalse(orderpoint_product_mto_buy) # Create Delivery Order of 10 product and 10 buy + MTO picking_form = Form(self.env['stock.picking']) picking_form.partner_id = partner picking_form.picking_type_id = self.env.ref('stock.picking_type_out') with picking_form.move_ids_without_package.new() as move: move.product_id = product move.product_uom_qty = 10.0 with picking_form.move_ids_without_package.new() as move: move.product_id = product_buy_mto move.product_uom_qty = 10.0 customer_picking = picking_form.save() customer_picking.move_ids.filtered(lambda m: m.product_id == product_buy_mto).procure_method = 'make_to_order' customer_picking.action_confirm() self.env['stock.warehouse.orderpoint'].flush_model() self.env['stock.warehouse.orderpoint']._get_orderpoint_action() orderpoint_product = self.env['stock.warehouse.orderpoint'].search( [('product_id', '=', product.id)]) orderpoint_product_mto_buy = self.env['stock.warehouse.orderpoint'].search( [('product_id', '=', product_buy_mto.id)]) self.assertFalse(orderpoint_product_mto_buy) self.assertEqual(len(orderpoint_product), 1.0) self.assertEqual(orderpoint_product.qty_to_order, 10.0) self.assertEqual(orderpoint_product.trigger, 'manual') self.assertEqual(orderpoint_product.create_uid.id, SUPERUSER_ID) def test_replenish_report_2(self): """Same then `test_replenish_report_1` but with two steps receipt enabled""" partner = self.env['res.partner'].create({ 'name': 'Tintin' }) for wh in self.env['stock.warehouse'].search([]): wh.write({'reception_steps': 'two_steps'}) route_buy = self.env.ref('purchase_stock.route_warehouse0_buy') route_mto = self.env.ref('stock.route_warehouse0_mto') product_form = Form(self.env['product.product']) product_form.name = 'Simple Product' product_form.is_storable = True with product_form.seller_ids.new() as s: s.partner_id = partner product = product_form.save() product_form = Form(self.env['product.product']) product_form.name = 'Product BUY + MTO' product_form.is_storable = True product_form.route_ids.add(route_buy) product_form.route_ids.add(route_mto) with product_form.seller_ids.new() as s: s.partner_id = partner product_buy_mto = product_form.save() # Create Delivery Order of 20 product and 10 buy + MTO picking_form = Form(self.env['stock.picking']) picking_form.partner_id = partner picking_form.picking_type_id = self.env.ref('stock.picking_type_out') with picking_form.move_ids_without_package.new() as move: move.product_id = product move.product_uom_qty = 10.0 with picking_form.move_ids_without_package.new() as move: move.product_id = product move.product_uom_qty = 10.0 with picking_form.move_ids_without_package.new() as move: move.product_id = product_buy_mto move.product_uom_qty = 10.0 customer_picking = picking_form.save() customer_picking.move_ids.filtered(lambda m: m.product_id == product_buy_mto).procure_method = 'make_to_order' customer_picking.action_confirm() self.env['stock.warehouse.orderpoint']._get_orderpoint_action() orderpoint_product = self.env['stock.warehouse.orderpoint'].search( [('product_id', '=', product.id)]) orderpoint_product_mto_buy = self.env['stock.warehouse.orderpoint'].search( [('product_id', '=', product_buy_mto.id)]) self.assertFalse(orderpoint_product_mto_buy) self.assertEqual(len(orderpoint_product), 1.0) self.assertEqual(orderpoint_product.qty_to_order, 20.0) self.assertEqual(orderpoint_product.trigger, 'manual') self.assertEqual(orderpoint_product.create_uid.id, SUPERUSER_ID) orderpoint_product.action_replenish() po = self.env['purchase.order'].search([('partner_id', '=', partner.id)]) self.assertTrue(po) self.assertEqual(len(po.order_line), 2.0) po_line_product_mto = po.order_line.filtered(lambda l: l.product_id == product_buy_mto) po_line_product = po.order_line.filtered(lambda l: l.product_id == product) self.assertEqual(po_line_product_mto.product_uom_qty, 10.0) self.assertEqual(po_line_product.product_uom_qty, 20.0) self.env['stock.warehouse.orderpoint'].flush_model() self.env['stock.warehouse.orderpoint']._get_orderpoint_action() orderpoint_product = self.env['stock.warehouse.orderpoint'].search( [('product_id', '=', product.id)]) orderpoint_product_mto_buy = self.env['stock.warehouse.orderpoint'].search( [('product_id', '=', product_buy_mto.id)]) self.assertFalse(orderpoint_product) self.assertFalse(orderpoint_product_mto_buy) # Create Delivery Order of 10 product and 10 buy + MTO picking_form = Form(self.env['stock.picking']) picking_form.partner_id = partner picking_form.picking_type_id = self.env.ref('stock.picking_type_out') with picking_form.move_ids_without_package.new() as move: move.product_id = product move.product_uom_qty = 10.0 with picking_form.move_ids_without_package.new() as move: move.product_id = product_buy_mto move.product_uom_qty = 10.0 customer_picking = picking_form.save() customer_picking.move_ids.filtered(lambda m: m.product_id == product_buy_mto).procure_method = 'make_to_order' customer_picking.action_confirm() self.env['stock.warehouse.orderpoint'].flush_model() self.env['stock.warehouse.orderpoint']._get_orderpoint_action() orderpoint_product = self.env['stock.warehouse.orderpoint'].search( [('product_id', '=', product.id)]) orderpoint_product_mto_buy = self.env['stock.warehouse.orderpoint'].search( [('product_id', '=', product_buy_mto.id)]) self.assertFalse(orderpoint_product_mto_buy) self.assertEqual(len(orderpoint_product), 1.0) self.assertEqual(orderpoint_product.qty_to_order, 10.0) self.assertEqual(orderpoint_product.trigger, 'manual') self.assertEqual(orderpoint_product.create_uid.id, SUPERUSER_ID) def test_procure_not_default_partner(self): """Define a product with 2 vendors. First run a "standard" procurement, default vendor should be used. Then, call a procurement with `partner_id` specified in values, the specified vendor should be used.""" purchase_route = self.env.ref("purchase_stock.route_warehouse0_buy") uom_unit = self.env.ref("uom.product_uom_unit") warehouse = self.env['stock.warehouse'].search( [('company_id', '=', self.env.company.id)], limit=1) product = self.env["product.product"].create({ "name": "product TEST", "standard_price": 100.0, "is_storable": True, "uom_id": uom_unit.id, "default_code": "A", "route_ids": [(6, 0, purchase_route.ids)], }) default_vendor = self.env["res.partner"].create({ "name": "Supplier A", }) secondary_vendor = self.env["res.partner"].create({ "name": "Supplier B", }) self.env["product.supplierinfo"].create({ "partner_id": default_vendor.id, "product_tmpl_id": product.product_tmpl_id.id, "delay": 7, }) self.env["product.supplierinfo"].create({ "partner_id": secondary_vendor.id, "product_tmpl_id": product.product_tmpl_id.id, "delay": 10, }) # Test standard procurement. po_line = self.env["purchase.order.line"].search( [("product_id", "=", product.id)]) self.assertFalse(po_line) self.env["procurement.group"].run( [self.env["procurement.group"].Procurement( product, 100, uom_unit, warehouse.lot_stock_id, "Test default vendor", "/", self.env.company, { "warehouse_id": warehouse, "date_planned": dt.today() + td(days=15), "rule_id": warehouse.buy_pull_id, "group_id": False, "route_ids": [], } )]) po_line = self.env["purchase.order.line"].search( [("product_id", "=", product.id)]) self.assertTrue(po_line) self.assertEqual(po_line.partner_id, default_vendor) po_line.order_id.button_cancel() po_line.order_id.unlink() # now force the vendor: po_line = self.env["purchase.order.line"].search( [("product_id", "=", product.id)]) self.assertFalse(po_line) self.env["procurement.group"].run( [self.env["procurement.group"].Procurement( product, 100, uom_unit, warehouse.lot_stock_id, "Test default vendor", "/", self.env.company, { "warehouse_id": warehouse, "date_planned": dt.today() + td(days=15), "rule_id": warehouse.buy_pull_id, "group_id": False, "route_ids": [], "supplierinfo_name": secondary_vendor, } )]) po_line = self.env["purchase.order.line"].search( [("product_id", "=", product.id)]) self.assertTrue(po_line) self.assertEqual(po_line.partner_id, secondary_vendor) def test_procure_multi_lingual(self): """ Define a product with description in English and French. Run a procurement specifying a group_id with a partner (customer) set up with French as language. Verify that the PO is generated using the default (English) language. """ purchase_route = self.env.ref("purchase_stock.route_warehouse0_buy") # create a new warehouse to make sure it gets the mts/mto rule warehouse = self.env['stock.warehouse'].create({ "name": "test warehouse", "active": True, 'reception_steps': 'one_step', 'delivery_steps': 'ship_only', 'code': 'TEST' }) customer_loc, _ = warehouse._get_partner_locations() mto_rule = self.env['stock.rule'].search( [('warehouse_id', '=', warehouse.id), ('procure_method', '=', 'make_to_order'), ('location_dest_id', '=', customer_loc.id) ] ) route_mto = self.env["stock.route"].create({ "name": "MTO", "active": True, "sequence": 3, "product_selectable": True, "rule_ids": [(6, 0, [ mto_rule.id ])] }) uom_unit = self.env.ref("uom.product_uom_unit") product = self.env["product.product"].create({ "name": "product TEST", "standard_price": 100.0, "is_storable": True, "uom_id": uom_unit.id, "default_code": "A", "route_ids": [(6, 0, [ route_mto.id, purchase_route.id, ])], }) self.env['res.lang']._activate_lang('fr_FR') product.product_tmpl_id.with_context(lang='fr_FR').name = 'produit en français' product.with_context(lang='fr_FR').name = 'produit en français' default_vendor = self.env["res.partner"].create({ "name": "Supplier A", }) self.env["product.supplierinfo"].create({ "partner_id": default_vendor.id, "product_tmpl_id": product.product_tmpl_id.id, "delay": 7, }) customer = self.env["res.partner"].create({ "name": "Customer", "lang": "fr_FR" }) proc_group = self.env["procurement.group"].create({ "partner_id": customer.id }) procurement = self.env["procurement.group"].Procurement( product, 100, uom_unit, customer.property_stock_customer, "Test default vendor", "/", self.env.company, { "warehouse_id": warehouse, "date_planned": dt.today() + td(days=15), "group_id": proc_group, "route_ids": [], } ) self.env.invalidate_all() self.env["procurement.group"].run([procurement]) po_line = self.env["purchase.order.line"].search( [("product_id", "=", product.id)]) self.assertTrue(po_line) self.assertEqual("[A] product TEST", po_line.name) def test_multi_locations_and_reordering_rule(self): """ Suppose two orderpoints for the same product, each one to a different location If the user triggers each orderpoint separately, it should still produce two different purchase order lines (one for each orderpoint) """ # Required for `warehouse_id` to be visible in the view self.env.user.groups_id += self.env.ref('stock.group_stock_multi_locations') warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) stock_location = warehouse.lot_stock_id sub_location = self.env['stock.location'].create({'name': 'subloc_1', 'location_id': stock_location.id}) orderpoint_form = Form(self.env['stock.warehouse.orderpoint']) orderpoint_form.warehouse_id = warehouse orderpoint_form.location_id = stock_location orderpoint_form.product_id = self.product_01 orderpoint_form.product_min_qty = 1 orderpoint_form.product_max_qty = 1 stock_op = orderpoint_form.save() orderpoint_form = Form(self.env['stock.warehouse.orderpoint']) orderpoint_form.warehouse_id = warehouse orderpoint_form.location_id = sub_location orderpoint_form.product_id = self.product_01 orderpoint_form.product_min_qty = 2 orderpoint_form.product_max_qty = 2 sub_op = orderpoint_form.save() stock_op.action_replenish() sub_op.action_replenish() po = self.env['purchase.order'].search([('partner_id', '=', self.partner.id)]) self.assertRecordValues(po.order_line, [ {'product_id': self.product_01.id, 'product_qty': 1.0, 'orderpoint_id': stock_op.id}, {'product_id': self.product_01.id, 'product_qty': 2.0, 'orderpoint_id': sub_op.id}, ]) po.button_confirm() picking = po.picking_ids picking.button_validate() self.assertRecordValues(picking.move_line_ids, [ {'product_id': self.product_01.id, 'quantity': 1.0, 'state': 'done', 'location_dest_id': stock_location.id}, {'product_id': self.product_01.id, 'quantity': 2.0, 'state': 'done', 'location_dest_id': sub_location.id}, ]) def test_2steps_and_partner_on_orderpoint(self): """ Suppose a 2-steps receipt This test ensures that an orderpoint with its route and supplied defined correctly works """ warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)]) route_buy_id = self.ref('purchase_stock.route_warehouse0_buy') warehouse.reception_steps = 'two_steps' orderpoint = self.env['stock.warehouse.orderpoint'].create({ 'name': 'RR for %s' % self.product_01.name, 'warehouse_id': warehouse.id, 'location_id': warehouse.lot_stock_id.id, 'trigger': 'manual', 'product_id': self.product_01.id, 'product_min_qty': 1, 'product_max_qty': 5, 'route_id': route_buy_id, 'supplier_id': self.product_01.seller_ids.id, }) orderpoint.action_replenish() po_line = self.env['purchase.order.line'].search([('partner_id', '=', self.partner.id), ('product_id', '=', self.product_01.id)]) self.assertEqual(po_line.product_qty, 5) def test_change_of_scheduled_date(self): """ A user creates a delivery, an orderpoint is created. Its forecast quantity becomes -1 and the quantity to order is 1. Then the user postpones the scheduled date of the delivery. The quantities of the orderpoint should be reset to zero. """ delivery_form = Form(self.env['stock.picking']) delivery_form.partner_id = self.partner delivery_form.picking_type_id = self.env.ref('stock.picking_type_out') with delivery_form.move_ids_without_package.new() as move: move.product_id = self.product_01 move.product_uom_qty = 1 delivery = delivery_form.save() delivery.action_confirm() delivery.move_ids.flush_recordset() self.env['stock.warehouse.orderpoint']._get_orderpoint_action() orderpoint = self.env['stock.warehouse.orderpoint'].search([('product_id', '=', self.product_01.id)]) self.assertRecordValues(orderpoint, [ {'qty_forecast': -1, 'qty_to_order': 1}, ]) # invalidate the fields that will eventually be inconsistent orderpoint.invalidate_model(fnames=['qty_forecast', 'qty_to_order']) orderpoint.product_id.invalidate_model(fnames=['virtual_available']) delivery.scheduled_date += td(days=7) self.assertRecordValues(orderpoint, [ {'qty_forecast': 0, 'qty_to_order': 0}, ]) def test_decrease_qty_multi_step_receipt(self): """ Two-steps receipt. An orderpoint generates a move from Input to Stock with 5 x Product01 and a purchase order to fulfill the need of that SM. Then, the user decreases the qty on the PO and confirms it. The existing SM should be updated and another one should be created (from Vendors to Input, for the PO) """ warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1) warehouse.reception_steps = 'two_steps' input_location_id = warehouse.wh_input_stock_loc_id.id stock_location_id = warehouse.lot_stock_id.id customer_location_id = self.ref('stock.stock_location_customers') supplier_location_id = self.ref('stock.stock_location_suppliers') self.product_01.description = 'Super Note' op = self.env['stock.warehouse.orderpoint'].create({ 'name': self.product_01.name, 'location_id': stock_location_id, 'product_id': self.product_01.id, 'product_min_qty': 0, 'product_max_qty': 0, 'trigger': 'manual', }) out_move = self.env['stock.move'].create({ 'name': self.product_01.name, 'product_id': self.product_01.id, 'product_uom': self.product_01.uom_id.id, 'product_uom_qty': 5, 'location_id': stock_location_id, 'location_dest_id': customer_location_id, }) out_move._action_confirm() op.action_replenish() purchase = self.env['purchase.order'].search([('partner_id', '=', self.partner.id)], order="id desc", limit=1) with Form(purchase) as form: with form.order_line.edit(0) as line: line.product_qty = 4 purchase.button_confirm() moves = self.env['stock.move'].search([('id', '!=', out_move.id), ('product_id', '=', self.product_01.id)], order='id desc') self.assertRecordValues(moves, [ {'location_id': supplier_location_id, 'location_dest_id': input_location_id, 'product_qty': 4}, ]) moves.picking_id.button_validate() moves = self.env['stock.move'].search([('id', '!=', out_move.id), ('product_id', '=', self.product_01.id)], order='id desc') self.assertRecordValues(moves, [ {'location_id': input_location_id, 'location_dest_id': stock_location_id, 'product_qty': 4}, {'location_id': supplier_location_id, 'location_dest_id': input_location_id, 'product_qty': 4}, ]) def test_decrease_qty_multi_step_receipt02(self): """ Two-steps receipt. An orderpoint generates a move from Input to Stock with 4 x Product01 and a purchase order to fulfill the need of that SM. Then, the user increases and decreases the qty on the PO. The existing SMs should be updated. """ warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1) warehouse.reception_steps = 'two_steps' input_location_id = warehouse.wh_input_stock_loc_id.id stock_location_id = warehouse.lot_stock_id.id supplier_location_id = self.ref('stock.stock_location_suppliers') self.product_01.description = False op = self.env['stock.warehouse.orderpoint'].create({ 'name': self.product_01.name, 'location_id': stock_location_id, 'product_id': self.product_01.id, 'product_min_qty': 4, 'product_max_qty': 4, 'trigger': 'manual', }) op.action_replenish() purchase = self.env['purchase.order'].search([('partner_id', '=', self.partner.id)], order="id desc", limit=1) with Form(purchase) as form: with form.order_line.edit(0) as line: line.product_qty = 10 purchase.button_confirm() moves = self.env['stock.move'].search([('product_id', '=', self.product_01.id)], order='id desc') self.assertRecordValues(moves, [ {'location_id': supplier_location_id, 'location_dest_id': input_location_id, 'product_qty': 10}, ]) with Form(purchase) as form: with form.order_line.edit(0) as line: line.product_qty = 1 moves = self.env['stock.move'].search([('product_id', '=', self.product_01.id)], order='id desc') self.assertRecordValues(moves, [ {'location_id': supplier_location_id, 'location_dest_id': input_location_id, 'product_qty': 1}, ]) def test_add_line_to_existing_draft_po(self): """ Days to purchase = 10 Two products P1, P2 from the same supplier Several use cases, each time we run the RR one by one. Then, according to the dates and the configuration, it should use the existing PO or not """ warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1) self.env.company.days_to_purchase = 10 expected_order_date = dt.combine(dt.today() + td(days=10), time(12)) expected_delivery_date = expected_order_date + td(days=1.0) product_02 = self.env['product.product'].create({ 'name': 'Super Product', 'is_storable': True, 'seller_ids': [(0, 0, {'partner_id': self.partner.id})], }) op_01, op_02 = self.env['stock.warehouse.orderpoint'].create([{ 'warehouse_id': warehouse.id, 'location_id': warehouse.lot_stock_id.id, 'product_id': p.id, 'product_min_qty': 1, 'product_max_qty': 1, } for p in [self.product_01, product_02]]) op_01.action_replenish() po01 = self.env['purchase.order'].search([], order='id desc', limit=1) self.assertEqual(po01.date_order, expected_order_date) op_02.action_replenish() self.assertEqual(po01.date_order, expected_order_date) self.assertRecordValues(po01.order_line, [ {'product_id': self.product_01.id, 'date_planned': expected_delivery_date}, {'product_id': product_02.id, 'date_planned': expected_delivery_date}, ]) # Reset and try another flow po01.button_cancel() op_01.action_replenish() po02 = self.env['purchase.order'].search([], order='id desc', limit=1) self.assertNotEqual(po02, po01) with freeze_time(dt.today() + td(days=1)): op_02.invalidate_recordset(fnames=['lead_days_date']) op_02.action_replenish() self.assertEqual(po02.date_order, expected_order_date) self.assertRecordValues(po02.order_line, [ {'product_id': self.product_01.id, 'date_planned': expected_delivery_date}, {'product_id': product_02.id, 'date_planned': expected_delivery_date + td(days=1)}, ]) # Restrict the merge with POs that have their order deadline in [today - 2 days, today + 2 days] self.env['ir.config_parameter'].set_param('purchase_stock.delta_days_merge', '2') # Reset and try with a second RR executed in the dates range (-> should still use the existing PO) po02.button_cancel() op_01.action_replenish() po03 = self.env['purchase.order'].search([], order='id desc', limit=1) self.assertNotEqual(po03, po02) with freeze_time(dt.today() + td(days=2)): op_02.invalidate_recordset(fnames=['lead_days_date']) op_02.action_replenish() self.assertEqual(po03.date_order, expected_order_date) self.assertRecordValues(po03.order_line, [ {'product_id': self.product_01.id, 'date_planned': expected_delivery_date}, {'product_id': product_02.id, 'date_planned': expected_delivery_date + td(days=2)}, ]) # Reset and try with a second RR executed after the dates range (-> should not use the existing PO) po03.button_cancel() op_01.action_replenish() po04 = self.env['purchase.order'].search([], order='id desc', limit=1) self.assertNotEqual(po04, po03) with freeze_time(dt.today() + td(days=3)): op_02.invalidate_recordset(fnames=['lead_days_date']) op_02.action_replenish() self.assertEqual(po04.order_line.product_id, self.product_01, 'There should be only a line for product 01') po05 = self.env['purchase.order'].search([], order='id desc', limit=1) self.assertNotEqual(po05, po04, 'A new PO should be generated') self.assertEqual(po05.order_line.product_id, product_02) def test_reordering_rule_visibility_days(self): """ Test the visibility days on the reordering rule update the qty_to_order but do not update the forecasted quantity of the current day. ex: - We are January 14th - visibility days = 10 - A sale order is scheduled on January 20th -> 2 scenarios 1. Today's forecasted quantity is < orderpoint's min qty the sale order will be taken into account in the forecasted quantity 2. Todays's forecasted quantity is >= orderpoint's min qty the sale order will not be taken into account in the forecasted quantity """ # create reordering rule wh = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) op = self.env['stock.warehouse.orderpoint'].create({ 'warehouse_id': wh.id, 'location_id': wh.lot_stock_id.id, 'product_id': self.product_01.id, 'product_min_qty': 0, 'product_max_qty': 0, 'visibility_days': 10, }) # out move on January 20th move = self.env['stock.move'].create({ 'name': 'Test move', 'product_id': self.product_01.id, 'product_uom': self.product_01.uom_id.id, 'product_uom_qty': 1, 'location_id': wh.lot_stock_id.id, 'location_dest_id': self.env.ref('stock.stock_location_customers').id, 'date': dt.today() + td(days=6), }) move._action_confirm() self.assertEqual(op.qty_to_order, 0, 'sale order is ignored') # out move today to force the forecast to be negative move = self.env['stock.move'].create({ 'name': 'Test move', 'product_id': self.product_01.id, 'product_uom': self.product_01.uom_id.id, 'product_uom_qty': 1, 'location_id': wh.lot_stock_id.id, 'location_dest_id': self.env.ref('stock.stock_location_customers').id, }) move._action_confirm() # virtual available is -1 but we need to replenish 2 self.product_01.virtual_available = -1 self.assertEqual(op.qty_to_order, 2, 'sale order is ignored') def test_reordering_rule_visibility_days_display(self): """ Checks that the visibility days are properly shown on the info wizard & the orderpoint forecast. """ today = dt.today() warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) orderpoint = self.env['stock.warehouse.orderpoint'].create({ 'warehouse_id': warehouse.id, 'location_id': warehouse.lot_stock_id.id, 'product_id': self.product_01.id, 'product_min_qty': 0, 'product_max_qty': 0, 'visibility_days': 5, }) # Out move in 5 days out_5_days = self.env['stock.move'].create({ 'name': '5 days', 'product_id': self.product_01.id, 'product_uom_qty': 5, 'location_id': warehouse.lot_stock_id.id, 'picking_type_id': warehouse.out_type_id.id, 'date': today + td(days=5), }) out_5_days._action_confirm() # Visibility days should be ignored if nothing is found within lead times (today + 1 day) replenishment_info = loads(self.env['stock.replenishment.info'].create({'orderpoint_id': orderpoint.id}).json_lead_days) self.assertEqual(replenishment_info['lead_days_date'], format_date(orderpoint.env, today + td(days=1))) self.assertEqual(float(replenishment_info['qty_to_order']), 0) self.assertEqual(replenishment_info['visibility_days'], 0) # Extra lines for forecast are given through its context context = orderpoint.action_product_forecast_report()['context'] self.assertEqual(context['qty_to_order'], 0) self.assertEqual(context['lead_days_date'], format_date(orderpoint.env, today + td(days=1))) self.assertEqual(context['qty_to_order_with_visibility_days'], 0) # Out move today out_today = self.env['stock.move'].create({ 'name': 'today', 'product_id': self.product_01.id, 'product_uom_qty': 3, 'location_id': warehouse.lot_stock_id.id, 'picking_type_id': warehouse.out_type_id.id, 'partner_id': self.partner.id, # Avoids the two moves being merged 'date': today, }) out_today._action_confirm() # Visibility days should be used something is found within lead times replenishment_info = loads(self.env['stock.replenishment.info'].create({'orderpoint_id': orderpoint.id}).json_lead_days) self.assertEqual(replenishment_info['lead_days_date'], format_date(orderpoint.env, today + td(days=1))) self.assertEqual(float(replenishment_info['qty_to_order']), 8) self.assertEqual(replenishment_info['visibility_days'], 5) self.assertEqual(replenishment_info['visibility_days_date'], format_date(orderpoint.env, today + td(days=1) + td(days=5))) # Extra lines for forecast are given through its context context = orderpoint.action_product_forecast_report()['context'] self.assertEqual(context['qty_to_order'], 3) self.assertEqual(context['lead_days_date'], format_date(orderpoint.env, today + td(days=1))) self.assertEqual(context['qty_to_order_with_visibility_days'], 8) self.assertEqual(context['visibility_days_date'], format_date(orderpoint.env, today + td(days=1) + td(days=5))) def test_update_po_line_without_purchase_access_right(self): """ Test that a user without purchase access right can update a PO line from picking.""" # create a user with only inventory access right user = self.env['res.users'].create({ 'name': 'Inventory Manager', 'login': 'inv_manager', 'groups_id': [(6, 0, [self.env.ref('stock.group_stock_user').id])] }) product = self.env['product.product'].create({ 'name': 'Storable Product', 'is_storable': True, 'seller_ids': [(0, 0, {'partner_id': self.partner.id})], }) warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1) self.env['stock.warehouse.orderpoint'].create({ 'warehouse_id': warehouse.id, 'location_id': warehouse.lot_stock_id.id, 'product_id': product.id, 'product_min_qty': 5, 'product_max_qty': 5, }) # run the scheduler self.env['procurement.group'].run_scheduler() # check that the PO line is created po_line = self.env['purchase.order.line'].search([('product_id', '=', product.id)]) self.assertEqual(len(po_line), 1, 'There should be only one PO line') self.assertEqual(po_line.product_qty, 5, 'The PO line quantity should be 5') # Update the po line from the picking picking = self.env['stock.picking'].with_user(user).create({ 'location_id': warehouse.lot_stock_id.id, 'location_dest_id': self.env.ref('stock.stock_location_customers').id, 'picking_type_id': warehouse.out_type_id.id, 'move_ids': [(0, 0, { 'name': product.name, 'product_id': product.id, 'product_uom': product.uom_id.id, 'product_uom_qty': 1, 'location_id': warehouse.lot_stock_id.id, 'location_dest_id': self.env.ref('stock.stock_location_customers').id, })], 'state': 'draft', }) picking.with_user(user).action_assign() # check that the PO line quantity has been updated self.assertEqual(po_line.product_qty, 6, 'The PO line quantity should be 6') def test_set_supplier_in_orderpoint(self): """ Test that qty_to_order is correctly computed when setting the supplier in an orderpoint Have a product with a uom in Kg and a purchase uom in Tonne (the purchase UOM should be bigger that the UOM) and a supplier with a min_qty of 6T Create an orderpoint with a min_qty of 500Kg and a max_qty of 0Kg Set the supplier in the orderpoint and check that the qty_to_order is correctly updated to 6000Kg """ product = self.env['product.product'].create({ 'name': 'Storable Product', 'is_storable': True, 'uom_id': self.env.ref('uom.product_uom_categ_kgm').uom_ids[3].id, 'uom_po_id': self.env.ref('uom.product_uom_categ_kgm').uom_ids[4].id, 'seller_ids': [(0, 0, {'partner_id': self.partner.id, 'min_qty': 6})], }) warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1) orderpoint = self.env['stock.warehouse.orderpoint'].create({ 'warehouse_id': warehouse.id, 'location_id': warehouse.lot_stock_id.id, 'product_id': product.id, 'product_min_qty': 500, 'product_max_qty': 500, }) product.seller_ids.with_context(orderpoint_id=orderpoint.id).action_set_supplier() self.assertEqual(orderpoint.supplier_id, product.seller_ids, 'The supplier should be set in the orderpoint') self.assertEqual(orderpoint.product_uom, product.uom_id, 'The orderpoint uom should be the same as the product uom') self.assertEqual(orderpoint.qty_to_order, 6000) def test_tax_po_line_reordering_rule_with_branch_company(self): """ Test that the parent company tax is correctly set in the purchase order line when the scheduler is triggered and the branch company is used." """ self.env.company.write({ 'child_ids': [Command.create({ 'name': 'Branch A', 'zip': '85120', })], }) self.cr.precommit.run() # load the CoA branch = self.env.company.child_ids product = self.env['product.product'].with_company(branch).create({ 'name': 'Storable Product', 'is_storable': True, 'seller_ids': [Command.create({'partner_id': self.partner.id, 'min_qty': 1})], }) warehouse = self.env['stock.warehouse'].search([('company_id', '=', branch.id)], limit=1) product.env['stock.warehouse.orderpoint'].create({ 'warehouse_id': warehouse.id, 'location_id': warehouse.lot_stock_id.id, 'product_id': product.id, 'product_min_qty': 10, 'product_max_qty': 10, }) # run the scheduler self.env['procurement.group'].run_scheduler() # check that the PO line is created po_line = self.env['purchase.order.line'].search([('product_id', '=', product.id)]) self.assertEqual(len(po_line), 1, 'There should be only one PO line') self.assertEqual(po_line.product_qty, 10, 'The PO line quantity should be 10') self.assertTrue(po_line.taxes_id) def test_forbid_snoozing_auto_trigger_orderpoint(self): """ Check that you can not snooze an auto-trigger reoredering rule """ buy_route = self.env.ref('purchase_stock.route_warehouse0_buy') product = self.env['product.product'].create({ 'name': 'Super product', 'is_storable': True, 'route_ids': [Command.set(buy_route.ids)], }) # check that you can not create a snoozed auto-trigger reoredering rule with self.assertRaises(UserError): orderpoint = self.env['stock.warehouse.orderpoint'].create({ 'name': 'Super product RR', 'route_id': buy_route.id, 'product_id': product.id, 'product_min_qty': 0, 'product_max_qty': 5, 'snoozed_until': add(Date.today(), days=1), }) # check that you can not snooze an existing one orderpoint = self.env['stock.warehouse.orderpoint'].create({ 'name': 'Super product RR', 'route_id': buy_route.id, 'product_id': product.id, 'product_min_qty': 0, 'product_max_qty': 5, }) with self.assertRaises(UserError): orderpoint.snoozed_until = add(Date.today(), days=1) def test_supplierinfo_last_purchase_date(self): """ Test that the last_purchase_date on the replenishment information is correctly computed A user creates two purchase orders The last_purchase_date on the supplier info should be computed as the most recent date_order from the purchase orders """ res_partner = self.env['res.partner'].create({ 'name': 'Test Partner', }) product = self.env['product.product'].create({ 'name': 'Storable Product', 'is_storable': True, }) orderpoint = self.env['stock.warehouse.orderpoint'].create({ 'product_id': product.id, 'product_min_qty': 0, 'product_max_qty': 0, }) po1_vals = { 'partner_id': res_partner.id, 'date_order': dt.today() - td(days=15), 'order_line': [ (0, 0, { 'name': product.name, 'product_id': product.id, 'product_qty': 1.0, })], } po2_vals = { 'partner_id': res_partner.id, 'date_order': dt.today(), 'order_line': [ (0, 0, { 'name': product.name, 'product_id': product.id, 'product_qty': 1.0, })], } po1 = self.env['purchase.order'].create(po1_vals) po1.button_confirm() po2 = self.env['purchase.order'].create(po2_vals) po2.button_confirm() replenishment_info = self.env['stock.replenishment.info'].create({'orderpoint_id': orderpoint.id}) supplier_info = replenishment_info.supplierinfo_ids self.assertEqual(supplier_info.last_purchase_date, dt.today().date(), "The last_purhchase_date should be set to the most recent date_order from the purchase orders")