add more tests for bemade_vendor_pricelist to get coverage up

This commit is contained in:
Marc Durepos 2024-09-19 10:58:14 -04:00
parent 5f6fb6002a
commit 626d4c5dc2
4 changed files with 377 additions and 213 deletions

View file

@ -12,20 +12,6 @@ class SaleOrderLine(models.Model):
digits='Product Price'
)
gross_profit_percent_vendor = fields.Float(
string='GP (%) on Vendor Price',
groups='base.group_user',
group_operator='avg',
compute='_compute_gp_vendor'
)
gross_profit_vendor = fields.Float(
string='GP on Vendor Price',
groups='base.group_user',
digits='Product Price',
compute='_compute_gp_vendor'
)
purchase_price_actual = fields.Float(
compute="_compute_actual_gp",
digits='Product Price',
@ -102,31 +88,13 @@ class SaleOrderLine(models.Model):
if is_order and self.qty_to_deliver <= 0:
return 0
elif is_order and self.qty_to_deliver > 0:
reserved = sum([q.reserved_quantity for q in self.move_ids.mapped('move_line_ids').mapped('quant_id')])
missing = self.qty_to_deliver - reserved
if float_compare(missing, 0.0,
precision_rounding=self.product_uom.rounding) == 1:
# Not enough reserved, check stock
missing = missing - qty_available
if float_compare(missing, 0.0,
precision_rounding=self.product_uom.rounding) == 1:
# Missing some stock to meet demand, return the quantity
return missing
else:
# Enough stock available to meet this line's demand
return 0
else:
# Already have stock reserved
return 0
reserved = sum([q.reserved_quantity for q in
self.move_ids.mapped('move_line_ids').mapped('quant_id')])
return max(0, self.qty_to_deliver - reserved - qty_available)
else:
# This is a quotation, don't bother with stock reservations
# Also, if available is negative for some reason,
missing = self.product_uom_qty - qty_available
if float_compare(missing, 0.0,
precision_rounding=self.product_uom.rounding) == 1:
return missing
else:
return 0
return max(0, self.product_uom_qty - qty_available)
@api.depends('product_id', 'product_id.seller_ids',
'product_id.seller_ids.price')
@ -147,15 +115,3 @@ class SaleOrderLine(models.Model):
date=line.order_id.date_order or fields.Date.today(),
round=False,
) if to_cur and suppinfo.price else suppinfo.price
@api.depends('purchase_price_vendor')
def _compute_gp_vendor(self):
for line in self:
if not line.price_unit or float_is_zero(line.price_unit):
line.gross_profit_vendor = 0
line.gross_profit_percent_vendor = 0
continue
unit_gp = line.price_unit - line.purchase_price_vendor
line.gross_profit_percent_vendor = unit_gp / line.price_unit
line.gross_profit_vendor = unit_gp * line.product_uom_qty

View file

@ -1 +1 @@
from . import test_sale_order_line
from . import test_margins

View file

@ -0,0 +1,372 @@
from odoo.tests import TransactionCase, tagged
from unittest.mock import patch, PropertyMock
@tagged("-at_install", "post_install")
class TestSaleOrderLine(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Create a test vendor
cls.vendor = cls.env['res.partner'].create({
'name': 'Test Vendor',
'supplier_rank': 1,
})
# Create a test product
cls.product = cls.env['product.product'].create({
'name': 'Test Product',
'type': 'product',
'list_price': 100.0,
'standard_price': 80.0,
})
# Link supplier info to the product
cls.supplierinfo = cls.env['product.supplierinfo'].create({
'partner_id': cls.vendor.id,
'product_tmpl_id': cls.product.product_tmpl_id.id,
'min_qty': 1,
'price': 75.0, # Vendor price
'currency_id': cls.env.ref('base.USD').id, # Assuming USD for simplicity
})
# Create a test sale order
cls.sale_order = cls.env['sale.order'].create({
'partner_id': cls.env.ref('base.res_partner_1').id,
# Assuming a default partner
})
# Create a test sale order line
cls.sale_order_line = cls.env['sale.order.line'].create({
'order_id': cls.sale_order.id,
'product_id': cls.product.id,
'product_uom_qty': 10,
'price_unit': 120.0,
})
def test_purchase_price_all_from_stock(self):
"""Test when all quantity is fulfilled from stock."""
with patch.object(
self.sale_order_line.product_id.__class__,
"qty_available",
new_callable=PropertyMock
) as mock_qty_available:
mock_qty_available.return_value = 10 # All available
# Recompute the fields
self.sale_order_line._compute_actual_gp()
# Assert purchase_price_actual equals purchase_price (from stock)
self.assertEqual(
self.sale_order_line.purchase_price_actual,
self.sale_order_line.purchase_price,
"Purchase price should be from stock when all quantity is available."
)
# Assert gross_profit is calculated correctly
expected_gross_profit = self.sale_order_line.price_subtotal - (
self.sale_order_line.purchase_price_actual * self.sale_order_line.product_uom_qty
)
self.assertAlmostEqual(
self.sale_order_line.gross_profit,
expected_gross_profit,
msg="Gross profit should be correctly calculated from stock purchase price."
)
# Ensure the SO is correct as well
self.assertSaleOrderComputesLikeSaleOrderLine()
def assertSaleOrderComputesLikeSaleOrderLine(self):
self.assertAlmostEqual(
self.sale_order_line.gross_profit,
self.sale_order.gross_profit,
)
self.assertAlmostEqual(
self.sale_order_line.gross_profit_percent,
self.sale_order.gross_profit_percent,
)
def test_purchase_price_all_from_vendor(self):
"""Test when all quantity is fulfilled from vendor."""
with patch.object(
self.sale_order_line.product_id.__class__,
"qty_available",
new_callable=PropertyMock
) as mock_qty_available:
mock_qty_available.return_value = 0
# Assert purchase_price_actual equals purchase_price_vendor (from vendor)
self.assertEqual(
self.sale_order_line.purchase_price_actual,
self.sale_order_line.purchase_price_vendor,
"Purchase price should be from vendor when all quantity is missing from stock."
)
# Assert gross_profit is calculated correctly
expected_gross_profit = self.sale_order_line.price_subtotal - (
self.sale_order_line.purchase_price_actual * self.sale_order_line.product_uom_qty
)
self.assertAlmostEqual(
self.sale_order_line.gross_profit,
expected_gross_profit,
msg="Gross profit should be correctly calculated from vendor purchase price."
)
# Ensure the SO is correct as well
self.assertSaleOrderComputesLikeSaleOrderLine()
def test_purchase_price_mixed(self):
"""Test when quantity is partially fulfilled from stock and partially from vendor."""
with patch.object(
self.sale_order_line.product_id.__class__,
"qty_available",
new_callable=PropertyMock
) as mock_qty_available:
mock_qty_available.return_value = 5
# Calculate expected blended purchase price
qty_from_vendor = 5
qty_from_stock = 5
expected_purchase_price = (
(
qty_from_vendor * self.sale_order_line.purchase_price_vendor) +
(
qty_from_stock * self.sale_order_line.purchase_price)
) / self.sale_order_line.product_uom_qty
self.assertAlmostEqual(
self.sale_order_line.purchase_price_actual,
expected_purchase_price,
msg="Purchase price should be a blend of stock and vendor prices."
)
# Assert gross_profit is calculated correctly
expected_gross_profit = self.sale_order_line.price_subtotal - (
expected_purchase_price * self.sale_order_line.product_uom_qty
)
self.assertAlmostEqual(
self.sale_order_line.gross_profit,
expected_gross_profit,
msg="Gross profit should be correctly calculated from blended purchase price."
)
# Ensure the SO is correct as well
self.assertSaleOrderComputesLikeSaleOrderLine()
def test_negative_available_stock_zero_qty_ordered(self):
"""We had a division by zero error with a zero product_uom_qty field
and a negative available stock. This test aims to root out this and
other possible causes of a div by zero error."""
with patch.object(
self.sale_order_line.product_id.__class__,
"qty_available",
new_callable=PropertyMock
) as mock_qty_available:
mock_qty_available.return_value = -1 # Negative available stock
# Set product_uom_qty to zero
self.sale_order_line.product_uom_qty = 0
# Recompute the fields
self.sale_order_line._compute_actual_gp()
# Assert gross_profit is 0
self.assertEqual(
self.sale_order_line.gross_profit,
0.0,
"Gross profit should be 0 when product_uom_qty is zero."
)
# Assert gross_profit_percent is 0
self.assertEqual(
self.sale_order_line.gross_profit_percent,
0.0,
"Gross profit percent should be 0 when product_uom_qty is zero."
)
# Ensure the SO is correct as well
self.assertSaleOrderComputesLikeSaleOrderLine()
def test_purchase_price_all_from_stock_confirmed(self):
"""Test when all quantity is fulfilled from stock."""
self.sale_order.action_confirm()
with patch.object(
self.sale_order_line.product_id.__class__,
"qty_available",
new_callable=PropertyMock
) as mock_qty_available:
mock_qty_available.return_value = 10 # All available
# Recompute the fields
self.sale_order_line._compute_actual_gp()
# Assert purchase_price_actual equals purchase_price (from stock)
self.assertEqual(
self.sale_order_line.purchase_price_actual,
self.sale_order_line.purchase_price,
"Purchase price should be from stock when all quantity is available."
)
# Assert gross_profit is calculated correctly
expected_gross_profit = self.sale_order_line.price_subtotal - (
self.sale_order_line.purchase_price_actual * self.sale_order_line.product_uom_qty
)
self.assertAlmostEqual(
self.sale_order_line.gross_profit,
expected_gross_profit,
msg="Gross profit should be correctly calculated from stock purchase price."
)
# Ensure the SO is correct as well
self.assertSaleOrderComputesLikeSaleOrderLine()
def test_purchase_price_all_from_vendor_confirmed(self):
"""Test when all quantity is fulfilled from vendor."""
self.sale_order.action_confirm()
with patch.object(
self.sale_order_line.product_id.__class__,
"qty_available",
new_callable=PropertyMock
) as mock_qty_available:
mock_qty_available.return_value = 0
# Assert purchase_price_actual equals purchase_price_vendor (from vendor)
self.assertEqual(
self.sale_order_line.purchase_price_actual,
self.sale_order_line.purchase_price_vendor,
"Purchase price should be from vendor when all quantity is missing from stock."
)
# Assert gross_profit is calculated correctly
expected_gross_profit = self.sale_order_line.price_subtotal - (
self.sale_order_line.purchase_price_actual * self.sale_order_line.product_uom_qty
)
self.assertAlmostEqual(
self.sale_order_line.gross_profit,
expected_gross_profit,
msg="Gross profit should be correctly calculated from vendor purchase price."
)
# Ensure the SO is correct as well
self.assertSaleOrderComputesLikeSaleOrderLine()
def test_purchase_price_mixed_confirmed(self):
"""Test when quantity is partially fulfilled from stock and partially from vendor."""
self.sale_order.action_confirm()
with patch.object(
self.sale_order_line.product_id.__class__,
"qty_available",
new_callable=PropertyMock
) as mock_qty_available:
mock_qty_available.return_value = 5
# Calculate expected blended purchase price
qty_from_vendor = 5
qty_from_stock = 5
expected_purchase_price = (
(
qty_from_vendor * self.sale_order_line.purchase_price_vendor) +
(
qty_from_stock * self.sale_order_line.purchase_price)
) / self.sale_order_line.product_uom_qty
self.assertAlmostEqual(
self.sale_order_line.purchase_price_actual,
expected_purchase_price,
msg="Purchase price should be a blend of stock and vendor prices."
)
# Assert gross_profit is calculated correctly
expected_gross_profit = self.sale_order_line.price_subtotal - (
expected_purchase_price * self.sale_order_line.product_uom_qty
)
self.assertAlmostEqual(
self.sale_order_line.gross_profit,
expected_gross_profit,
msg="Gross profit should be correctly calculated from blended purchase price."
)
# Ensure the SO is correct as well
self.assertSaleOrderComputesLikeSaleOrderLine()
def test_negative_available_stock_zero_qty_ordered_confirmed(self):
"""We had a division by zero error with a zero product_uom_qty field
and a negative available stock. This test aims to root out this and
other possible causes of a div by zero error."""
self.sale_order.action_confirm()
with patch.object(
self.sale_order_line.product_id.__class__,
"qty_available",
new_callable=PropertyMock
) as mock_qty_available:
mock_qty_available.return_value = -1 # Negative available stock
# Set product_uom_qty to zero
self.sale_order_line.product_uom_qty = 0
# Recompute the fields
self.sale_order_line._compute_actual_gp()
# Assert gross_profit is 0
self.assertEqual(
self.sale_order_line.gross_profit,
0.0,
"Gross profit should be 0 when product_uom_qty is zero."
)
# Assert gross_profit_percent is 0
self.assertEqual(
self.sale_order_line.gross_profit_percent,
0.0,
"Gross profit percent should be 0 when product_uom_qty is zero."
)
# Ensure the SO is correct as well
self.assertSaleOrderComputesLikeSaleOrderLine()
def test_determine_missing_stock_all_reserved(self):
"""Test the actual _determine_missing_stock method when all stock is reserved."""
# Set initial stock for the product
location = self.env["stock.warehouse"].search([])[0].lot_stock_id
quant = self.env["stock.quant"].create({
"product_id": self.product.id,
"location_id": location.id,
"inventory_quantity": 10,
"product_uom_id": self.product.uom_id.id,
})
quant.action_apply_inventory()
self.sale_order.action_confirm()
# Force the move to be reserved
self.sale_order.picking_ids.action_assign()
# Now, call _determine_missing_stock without mocking
missing_stock = self.sale_order_line._determine_missing_stock()
# Assert that missing_stock is 0
self.assertEqual(
missing_stock,
0,
"_determine_missing_stock should return 0 when all stock is reserved."
)
# Additionally, verify that purchase_price_actual is from stock
self.sale_order_line._compute_actual_gp()
self.assertEqual(
self.sale_order_line.purchase_price_actual,
self.sale_order_line.purchase_price,
"Purchase price should be from stock when all stock is reserved."
)
# Assert gross_profit is calculated correctly
expected_gross_profit = self.sale_order_line.price_subtotal - (
self.sale_order_line.purchase_price_actual * self.sale_order_line.product_uom_qty
)
self.assertAlmostEqual(
self.sale_order_line.gross_profit,
expected_gross_profit,
msg="Gross profit should be correctly calculated when all stock is reserved."
)
# Assert gross_profit_percent is calculated correctly
if self.sale_order_line.price_subtotal:
expected_gross_profit_percent = self.sale_order_line.gross_profit / self.sale_order_line.price_subtotal
else:
expected_gross_profit_percent = 0.0
self.assertAlmostEqual(
self.sale_order_line.gross_profit_percent,
expected_gross_profit_percent,
msg="Gross profit percent should be correctly calculated when all stock is reserved."
)
# Ensure the SO is correct as well
self.assertSaleOrderComputesLikeSaleOrderLine()

View file

@ -1,164 +0,0 @@
from odoo.tests import TransactionCase, tagged
from unittest.mock import patch, PropertyMock
@tagged("-at_install", "post_install")
class TestSaleOrderLine(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Create a test vendor
cls.vendor = cls.env['res.partner'].create({
'name': 'Test Vendor',
'supplier_rank': 1,
})
# Create a test product
cls.product = cls.env['product.product'].create({
'name': 'Test Product',
'type': 'product',
'list_price': 100.0,
'standard_price': 80.0,
})
# Link supplier info to the product
cls.supplierinfo = cls.env['product.supplierinfo'].create({
'partner_id': cls.vendor.id,
'product_tmpl_id': cls.product.product_tmpl_id.id,
'min_qty': 1,
'price': 75.0, # Vendor price
'currency_id': cls.env.ref('base.USD').id, # Assuming USD for simplicity
})
# Create a test sale order
cls.sale_order = cls.env['sale.order'].create({
'partner_id': cls.env.ref('base.res_partner_1').id,
# Assuming a default partner
})
# Create a test sale order line
cls.sale_order_line = cls.env['sale.order.line'].create({
'order_id': cls.sale_order.id,
'product_id': cls.product.id,
'product_uom_qty': 10,
'price_unit': 120.0,
})
def test_purchase_price_all_from_stock(self):
"""Test when all quantity is fulfilled from stock."""
with patch.object(
self.sale_order_line.product_id.__class__,
"qty_available",
new_callable=PropertyMock
) as mock_qty_available:
mock_qty_available.return_value = 10 # All available
# Recompute the fields
self.sale_order_line._compute_actual_gp()
# Assert purchase_price_actual equals purchase_price (from stock)
self.assertEqual(
self.sale_order_line.purchase_price_actual,
self.sale_order_line.purchase_price,
"Purchase price should be from stock when all quantity is available."
)
# Assert gross_profit is calculated correctly
expected_gross_profit = self.sale_order_line.price_subtotal - (
self.sale_order_line.purchase_price_actual * self.sale_order_line.product_uom_qty
)
self.assertAlmostEqual(
self.sale_order_line.gross_profit,
expected_gross_profit,
msg="Gross profit should be correctly calculated from stock purchase price."
)
def test_purchase_price_all_from_vendor(self):
"""Test when all quantity is fulfilled from vendor."""
with patch.object(
self.sale_order_line.product_id.__class__,
"qty_available",
new_callable=PropertyMock
) as mock_qty_available:
mock_qty_available.return_value = 0
# Assert purchase_price_actual equals purchase_price_vendor (from vendor)
self.assertEqual(
self.sale_order_line.purchase_price_actual,
self.sale_order_line.purchase_price_vendor,
"Purchase price should be from vendor when all quantity is missing from stock."
)
# Assert gross_profit is calculated correctly
expected_gross_profit = self.sale_order_line.price_subtotal - (
self.sale_order_line.purchase_price_actual * self.sale_order_line.product_uom_qty
)
self.assertAlmostEqual(
self.sale_order_line.gross_profit,
expected_gross_profit,
msg="Gross profit should be correctly calculated from vendor purchase price."
)
def test_purchase_price_mixed(self):
"""Test when quantity is partially fulfilled from stock and partially from vendor."""
with patch.object(
self.sale_order_line.product_id.__class__,
"qty_available",
new_callable=PropertyMock
) as mock_qty_available:
mock_qty_available.return_value = 5
# Calculate expected blended purchase price
qty_from_vendor = 5
qty_from_stock = 5
expected_purchase_price = (
(
qty_from_vendor * self.sale_order_line.purchase_price_vendor) +
(
qty_from_stock * self.sale_order_line.purchase_price)
) / self.sale_order_line.product_uom_qty
self.assertAlmostEqual(
self.sale_order_line.purchase_price_actual,
expected_purchase_price,
msg="Purchase price should be a blend of stock and vendor prices."
)
# Assert gross_profit is calculated correctly
expected_gross_profit = self.sale_order_line.price_subtotal - (
expected_purchase_price * self.sale_order_line.product_uom_qty
)
self.assertAlmostEqual(
self.sale_order_line.gross_profit,
expected_gross_profit,
msg="Gross profit should be correctly calculated from blended purchase price."
)
def test_negative_available_stock_zero_qty_ordered(self):
"""We had a division by zero error with a zero product_uom_qty field
and a negative available stock. This test aims to root out this and
other possible causes of a div by zero error."""
with patch.object(
self.sale_order_line.product_id.__class__,
"qty_available",
new_callable=PropertyMock
) as mock_qty_available:
mock_qty_available.return_value = -1 # Negative available stock
# Set product_uom_qty to zero
self.sale_order_line.product_uom_qty = 0
# Recompute the fields
self.sale_order_line._compute_actual_gp()
# Assert gross_profit is 0
self.assertEqual(
self.sale_order_line.gross_profit,
0.0,
"Gross profit should be 0 when product_uom_qty is zero."
)
# Assert gross_profit_percent is 0
self.assertEqual(
self.sale_order_line.gross_profit_percent,
0.0,
"Gross profit percent should be 0 when product_uom_qty is zero."
)