# -*- 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}" )