[ADD] New module replenish_by_first_vendor.

Added a new module for setting the vendor on manual replenishments that
are created by the system. This permits buyers using the Replenishment
interface (stock.orderpoint list view) to more easily order items as
they can group by vendor when working to set up purchase orders.
This commit is contained in:
Marc Durepos 2025-08-28 10:16:46 -04:00
parent 736f7011a7
commit e5e90d6c7e
6 changed files with 245 additions and 0 deletions

View file

@ -0,0 +1 @@
from . import models

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
{
"name": "Replenish by First Vendor",
"version": "18.0.0.0.1",
"category": "Inventory/Inventory",
"license": "GPL-3",
"summary": "Auto-select first vendor when creating manual orderpoints",
"description": """
Automatically selects the first vendor from a product's vendor list (vendor pricelists)
when an orderpoint with manual trigger is created, if no vendor is already set.
This helps streamline the orderpoint creation process by automatically setting
the most relevant vendor based on the product's supplier information.
""",
"author": "Bemade Inc.",
"website": "https://www.bemade.org",
"depends": [
"stock",
"purchase",
"purchase_stock",
],
"data": [],
"auto_install": False,
"installable": True,
}

View file

@ -0,0 +1 @@
from . import stock_orderpoint

View file

@ -0,0 +1,17 @@
from odoo import models, fields, api
class StockOrderpoint(models.Model):
_inherit = "stock.warehouse.orderpoint"
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
for orderpoint in res:
if (
orderpoint.trigger == "manual"
and not orderpoint.supplier_id
and orderpoint.product_id.seller_ids
):
orderpoint.supplier_id = orderpoint.product_id.seller_ids[0]
return res

View file

@ -0,0 +1 @@
from . import test_replenish_by_first_vendor

View file

@ -0,0 +1,200 @@
from odoo.tests import TransactionCase, tagged
@tagged("-at_install", "post_install")
class TestReplenishByFirstVendor(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Create test vendors
cls.vendor1 = cls.env["res.partner"].create(
{
"name": "First Vendor",
"supplier_rank": 1,
}
)
cls.vendor2 = cls.env["res.partner"].create(
{
"name": "Second Vendor",
"supplier_rank": 1,
}
)
# Create test product with Make to Stock route
cls.product_mts = cls.env["product.product"].create(
{
"name": "Test Product MTS",
"type": "consu",
"tracking": "none",
"list_price": 100.0,
"standard_price": 80.0,
}
)
# Create supplier info for the product (vendor1 first, vendor2 second)
cls.supplierinfo1 = cls.env["product.supplierinfo"].create(
{
"partner_id": cls.vendor1.id,
"product_tmpl_id": cls.product_mts.product_tmpl_id.id,
"min_qty": 1,
"price": 75.0,
"sequence": 1, # First vendor
}
)
cls.supplierinfo2 = cls.env["product.supplierinfo"].create(
{
"partner_id": cls.vendor2.id,
"product_tmpl_id": cls.product_mts.product_tmpl_id.id,
"min_qty": 1,
"price": 70.0,
"sequence": 2, # Second vendor
}
)
# Create a product without vendors
cls.product_no_vendor = cls.env["product.product"].create(
{
"name": "Product Without Vendor",
"type": "consu",
"tracking": "none",
"list_price": 50.0,
"standard_price": 40.0,
}
)
# Get warehouse and location
cls.warehouse = cls.env["stock.warehouse"].search([], limit=1)
cls.location = cls.warehouse.lot_stock_id
def test_manual_orderpoint_sets_first_vendor(self):
"""Test that manual orderpoints automatically set the first vendor."""
orderpoint = self.env["stock.warehouse.orderpoint"].create(
{
"product_id": self.product_mts.id,
"location_id": self.location.id,
"warehouse_id": self.warehouse.id,
"product_min_qty": 5.0,
"product_max_qty": 20.0,
"trigger": "manual",
}
)
# Check that supplier_id is set to the first vendor's supplierinfo
self.assertEqual(
orderpoint.supplier_id,
self.supplierinfo1,
"Manual orderpoint should automatically set the first vendor as supplier_id",
)
def test_auto_orderpoint_no_vendor_setting(self):
"""Test that automatic orderpoints don't get auto-vendor setting."""
orderpoint = self.env["stock.warehouse.orderpoint"].create(
{
"product_id": self.product_mts.id,
"location_id": self.location.id,
"warehouse_id": self.warehouse.id,
"product_min_qty": 5.0,
"product_max_qty": 20.0,
"trigger": "auto",
}
)
# Check that supplier_id is not set for auto trigger
self.assertFalse(
orderpoint.supplier_id,
"Auto orderpoint should not automatically set supplier_id",
)
def test_manual_orderpoint_with_existing_vendor(self):
"""Test that manual orderpoints with existing vendor don't get overridden."""
orderpoint = self.env["stock.warehouse.orderpoint"].create(
{
"product_id": self.product_mts.id,
"location_id": self.location.id,
"warehouse_id": self.warehouse.id,
"product_min_qty": 5.0,
"product_max_qty": 20.0,
"trigger": "manual",
"supplier_id": self.supplierinfo2.id, # Pre-set to second vendor
}
)
# Check that supplier_id remains the pre-set vendor
self.assertEqual(
orderpoint.supplier_id,
self.supplierinfo2,
"Manual orderpoint with existing supplier should not be overridden",
)
def test_manual_orderpoint_no_vendors_available(self):
"""Test that manual orderpoints for products without vendors don't crash."""
orderpoint = self.env["stock.warehouse.orderpoint"].create(
{
"product_id": self.product_no_vendor.id,
"location_id": self.location.id,
"warehouse_id": self.warehouse.id,
"product_min_qty": 5.0,
"product_max_qty": 20.0,
"trigger": "manual",
}
)
# Check that supplier_id remains empty
self.assertFalse(
orderpoint.supplier_id,
"Manual orderpoint for product without vendors should have no supplier_id",
)
def test_vendor_sequence_respected(self):
"""Test that the vendor with the lowest sequence is selected."""
# Update sequences to ensure vendor2 comes first
self.supplierinfo1.sequence = 10
self.supplierinfo2.sequence = 5
orderpoint = self.env["stock.warehouse.orderpoint"].create(
{
"product_id": self.product_mts.id,
"location_id": self.location.id,
"warehouse_id": self.warehouse.id,
"product_min_qty": 5.0,
"product_max_qty": 20.0,
"trigger": "manual",
}
)
# Check that supplier_id is set to vendor2 (lower sequence)
self.assertEqual(
orderpoint.supplier_id,
self.supplierinfo2,
"Manual orderpoint should select vendor with lowest sequence",
)
def test_vendor_id_field_also_set(self):
"""Test that vendor_id field is also properly set when supplier_id is set."""
orderpoint = self.env["stock.warehouse.orderpoint"].create(
{
"product_id": self.product_mts.id,
"location_id": self.location.id,
"warehouse_id": self.warehouse.id,
"product_min_qty": 5.0,
"product_max_qty": 20.0,
"trigger": "manual",
}
)
# Check that supplier_id is set to first vendor's supplierinfo
self.assertEqual(
orderpoint.supplier_id,
self.supplierinfo1,
"supplier_id should be set to first vendor's supplierinfo",
)
# Check that the partner (vendor) is accessible through supplier_id
self.assertEqual(
orderpoint.supplier_id.partner_id,
self.vendor1,
"supplier_id.partner_id should be the first vendor",
)