vendor_bill_editable_totals/tests/test_edge_cases.py
2025-11-21 18:02:20 +07:00

334 lines
12 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import TransactionCase
from odoo.exceptions import UserError
class TestEdgeCases(TransactionCase):
"""
Unit tests for edge cases in vendor bill editable totals.
Tests cover:
- Zero quantity error handling (Requirement 3.1)
- Negative values for credit notes (Requirement 3.2)
- No taxes scenario (Requirement 3.3)
- Single tax calculation (Requirement 3.4)
- Multiple taxes calculation (Requirement 3.4)
"""
def setUp(self):
super().setUp()
# Create a vendor partner
self.vendor = self.env['res.partner'].create({
'name': 'Test Vendor',
'supplier_rank': 1,
})
# Create a product
self.product = self.env['res.product'].create({
'name': 'Test Product',
'type': 'service',
'list_price': 100.0,
})
# Create a vendor bill
self.vendor_bill = self.env['account.move'].create({
'move_type': 'in_invoice',
'partner_id': self.vendor.id,
'invoice_date': '2024-01-01',
})
# Create a vendor refund (credit note)
self.vendor_refund = self.env['account.move'].create({
'move_type': 'in_refund',
'partner_id': self.vendor.id,
'invoice_date': '2024-01-01',
})
# Create tax records for testing
# Single tax: 10%
self.tax_10 = self.env['account.tax'].create({
'name': 'Tax 10%',
'amount': 10.0,
'amount_type': 'percent',
'type_tax_use': 'purchase',
'price_include': False,
})
# Another tax: 5%
self.tax_5 = self.env['account.tax'].create({
'name': 'Tax 5%',
'amount': 5.0,
'amount_type': 'percent',
'type_tax_use': 'purchase',
'price_include': False,
})
# Tax-included tax: 15%
self.tax_15_included = self.env['account.tax'].create({
'name': 'Tax 15% Included',
'amount': 15.0,
'amount_type': 'percent',
'type_tax_use': 'purchase',
'price_include': True,
})
# Get decimal precision for Product Price
self.precision = self.env['decimal.precision'].precision_get('Product Price')
# Test zero quantity error handling (Requirement 3.1)
def test_zero_quantity_price_subtotal_raises_error(self):
"""
Test that modifying price_subtotal with zero quantity raises UserError.
Requirement 3.1: Division by zero protection
"""
line = self.env['account.move.line'].create({
'move_id': self.vendor_bill.id,
'product_id': self.product.id,
'name': 'Test Line',
'quantity': 0.0,
'price_unit': 100.0,
})
line.price_subtotal = 500.0
with self.assertRaises(UserError) as context:
line._onchange_price_subtotal()
self.assertIn('quantity must be greater than zero', str(context.exception))
def test_zero_quantity_price_total_raises_error(self):
"""
Test that modifying price_total with zero quantity raises UserError.
Requirement 3.1: Division by zero protection
"""
line = self.env['account.move.line'].create({
'move_id': self.vendor_bill.id,
'product_id': self.product.id,
'name': 'Test Line',
'quantity': 0.0,
'price_unit': 100.0,
})
line.price_total = 550.0
with self.assertRaises(UserError) as context:
line._onchange_price_total()
self.assertIn('quantity must be greater than zero', str(context.exception))
# Test negative values (credit notes) (Requirement 3.2)
def test_negative_price_subtotal_credit_note(self):
"""
Test that negative price_subtotal values are handled correctly for credit notes.
Requirement 3.2: Accept and process negative values correctly
"""
line = self.env['account.move.line'].create({
'move_id': self.vendor_refund.id,
'product_id': self.product.id,
'name': 'Test Line',
'quantity': 5.0,
'price_unit': 100.0,
})
# Set negative price_subtotal (credit note scenario)
line.price_subtotal = -500.0
line._onchange_price_subtotal()
# Expected: price_unit = -500.0 / 5.0 = -100.0
expected_price_unit = -100.0
self.assertAlmostEqual(
line.price_unit,
expected_price_unit,
places=self.precision,
msg=f"Negative price_subtotal not handled correctly: expected {expected_price_unit}, got {line.price_unit}"
)
def test_negative_price_total_credit_note(self):
"""
Test that negative price_total values are handled correctly for credit notes.
Requirement 3.2: Accept and process negative values correctly
"""
line = self.env['account.move.line'].create({
'move_id': self.vendor_refund.id,
'product_id': self.product.id,
'name': 'Test Line',
'quantity': 5.0,
'price_unit': 100.0,
'tax_ids': [(6, 0, [self.tax_10.id])],
})
# Set negative price_total (credit note scenario with tax)
# If price_subtotal = -500, with 10% tax, price_total = -550
line.price_total = -550.0
line._onchange_price_total()
# Expected: price_subtotal = -550.0 / 1.10 = -500.0
# price_unit = -500.0 / 5.0 = -100.0
expected_price_unit = -100.0
self.assertAlmostEqual(
line.price_unit,
expected_price_unit,
places=self.precision,
msg=f"Negative price_total not handled correctly: expected {expected_price_unit}, got {line.price_unit}"
)
# Test no taxes scenario (Requirement 3.3)
def test_no_taxes_price_total_equals_subtotal(self):
"""
Test that when no taxes are configured, price_total is treated as price_subtotal.
Requirement 3.3: Handle no taxes scenario correctly
"""
line = self.env['account.move.line'].create({
'move_id': self.vendor_bill.id,
'product_id': self.product.id,
'name': 'Test Line',
'quantity': 10.0,
'price_unit': 100.0,
# No tax_ids set
})
# Set price_total when no taxes are configured
line.price_total = 1000.0
line._onchange_price_total()
# Expected: price_unit = 1000.0 / 10.0 = 100.0
expected_price_unit = 100.0
self.assertAlmostEqual(
line.price_unit,
expected_price_unit,
places=self.precision,
msg=f"No taxes scenario not handled correctly: expected {expected_price_unit}, got {line.price_unit}"
)
# Test single tax calculation (Requirement 3.4)
def test_single_tax_calculation(self):
"""
Test that a single tax is correctly computed in price_total calculation.
Requirement 3.4: Correctly compute single tax effect
"""
line = self.env['account.move.line'].create({
'move_id': self.vendor_bill.id,
'product_id': self.product.id,
'name': 'Test Line',
'quantity': 10.0,
'price_unit': 100.0,
'tax_ids': [(6, 0, [self.tax_10.id])],
})
# Set price_total with 10% tax
# If price_subtotal = 1000, with 10% tax, price_total = 1100
line.price_total = 1100.0
line._onchange_price_total()
# Expected: price_subtotal = 1100.0 / 1.10 = 1000.0
# price_unit = 1000.0 / 10.0 = 100.0
expected_price_unit = 100.0
self.assertAlmostEqual(
line.price_unit,
expected_price_unit,
places=self.precision,
msg=f"Single tax calculation failed: expected {expected_price_unit}, got {line.price_unit}"
)
def test_single_tax_included_calculation(self):
"""
Test that a single tax-included tax is correctly computed.
Requirement 3.4: Correctly compute single tax effect with price_include=True
"""
line = self.env['account.move.line'].create({
'move_id': self.vendor_bill.id,
'product_id': self.product.id,
'name': 'Test Line',
'quantity': 10.0,
'price_unit': 100.0,
'tax_ids': [(6, 0, [self.tax_15_included.id])],
})
# Set price_total with 15% tax-included
# For tax-included, price_unit = price_total / quantity
line.price_total = 1150.0
line._onchange_price_total()
# Expected: price_unit = 1150.0 / 10.0 = 115.0
expected_price_unit = 115.0
self.assertAlmostEqual(
line.price_unit,
expected_price_unit,
places=self.precision,
msg=f"Single tax-included calculation failed: expected {expected_price_unit}, got {line.price_unit}"
)
# Test multiple taxes calculation (Requirement 3.4)
def test_multiple_taxes_calculation(self):
"""
Test that multiple taxes are correctly computed in price_total calculation.
Requirement 3.4: Correctly compute combined tax effect
"""
line = self.env['account.move.line'].create({
'move_id': self.vendor_bill.id,
'product_id': self.product.id,
'name': 'Test Line',
'quantity': 10.0,
'price_unit': 100.0,
'tax_ids': [(6, 0, [self.tax_10.id, self.tax_5.id])],
})
# Set price_total with 10% + 5% = 15% combined tax
# If price_subtotal = 1000, with 15% tax, price_total = 1150
line.price_total = 1150.0
line._onchange_price_total()
# Expected: price_subtotal = 1150.0 / 1.15 = 1000.0
# price_unit = 1000.0 / 10.0 = 100.0
expected_price_unit = 100.0
self.assertAlmostEqual(
line.price_unit,
expected_price_unit,
places=self.precision,
msg=f"Multiple taxes calculation failed: expected {expected_price_unit}, got {line.price_unit}"
)
def test_multiple_taxes_with_different_amounts(self):
"""
Test multiple taxes with different amounts to verify correct computation.
Requirement 3.4: Correctly compute combined tax effect with various amounts
"""
line = self.env['account.move.line'].create({
'move_id': self.vendor_bill.id,
'product_id': self.product.id,
'name': 'Test Line',
'quantity': 5.0,
'price_unit': 200.0,
'tax_ids': [(6, 0, [self.tax_10.id, self.tax_5.id])],
})
# Set price_total with 10% + 5% = 15% combined tax
# If price_subtotal = 1000 (5 * 200), with 15% tax, price_total = 1150
line.price_total = 1150.0
line._onchange_price_total()
# Expected: price_subtotal = 1150.0 / 1.15 = 1000.0
# price_unit = 1000.0 / 5.0 = 200.0
expected_price_unit = 200.0
self.assertAlmostEqual(
line.price_unit,
expected_price_unit,
places=self.precision,
msg=f"Multiple taxes with different amounts failed: expected {expected_price_unit}, got {line.price_unit}"
)