# -*- 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 TestIntegration(TransactionCase): """ Integration tests for vendor bill editable totals module. Tests cover full workflows: - Create vendor bill → modify price_subtotal → save → verify (Requirement 4.3) - Create vendor bill → modify price_total → save → verify (Requirement 4.3) - Compatibility with standard Odoo validations (Requirement 4.3) """ def setUp(self): super().setUp() # Create a vendor partner self.vendor = self.env['res.partner'].create({ 'name': 'Integration Test Vendor', 'supplier_rank': 1, }) # Create products self.product_a = self.env['res.product'].create({ 'name': 'Product A', 'type': 'service', 'list_price': 100.0, }) self.product_b = self.env['res.product'].create({ 'name': 'Product B', 'type': 'consu', 'list_price': 250.0, }) # Create tax records self.tax_10 = self.env['account.tax'].create({ 'name': 'Tax 10%', 'amount': 10.0, 'amount_type': 'percent', 'type_tax_use': 'purchase', 'price_include': False, }) self.tax_20 = self.env['account.tax'].create({ 'name': 'Tax 20%', 'amount': 20.0, 'amount_type': 'percent', 'type_tax_use': 'purchase', 'price_include': False, }) # Get decimal precision self.precision = self.env['decimal.precision'].precision_get('Product Price') def test_full_workflow_modify_price_subtotal(self): """ Test full workflow: create vendor bill → modify price_subtotal → save → verify. This integration test verifies that: 1. A vendor bill can be created successfully 2. Invoice lines can be added 3. price_subtotal can be modified via onchange 4. The bill can be saved with the modified values 5. The saved values are correct and persistent Requirement 4.3: Standard Odoo validations and computations work correctly """ # Step 1: Create a vendor bill vendor_bill = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.vendor.id, 'invoice_date': '2024-01-15', }) self.assertEqual(vendor_bill.state, 'draft', "Vendor bill should be in draft state") # Step 2: Add invoice line line = self.env['account.move.line'].create({ 'move_id': vendor_bill.id, 'product_id': self.product_a.id, 'name': 'Product A - Test', 'quantity': 5.0, 'price_unit': 100.0, 'tax_ids': [(6, 0, [self.tax_10.id])], }) # Verify initial values self.assertEqual(line.quantity, 5.0) self.assertEqual(line.price_unit, 100.0) # Step 3: Modify price_subtotal # User wants total subtotal of 600 instead of 500 line.price_subtotal = 600.0 line._onchange_price_subtotal() # Step 4: Verify price_unit was recalculated # Expected: price_unit = 600.0 / 5.0 = 120.0 expected_price_unit = 120.0 self.assertAlmostEqual( line.price_unit, expected_price_unit, places=self.precision, msg=f"price_unit should be recalculated to {expected_price_unit}" ) # Step 5: Trigger recomputation (simulating Odoo's compute methods) line._compute_price_subtotal() # Step 6: Verify price_subtotal is maintained self.assertAlmostEqual( line.price_subtotal, 600.0, places=2, msg="price_subtotal should be maintained after recomputation" ) # Step 7: Verify price_total is correct (with 10% tax) expected_price_total = 660.0 # 600 * 1.10 self.assertAlmostEqual( line.price_total, expected_price_total, places=2, msg=f"price_total should be {expected_price_total}" ) # Step 8: Save the vendor bill (flush to database) vendor_bill.flush_recordset() # Step 9: Reload and verify persistence line.invalidate_recordset() self.assertAlmostEqual( line.price_unit, expected_price_unit, places=self.precision, msg="price_unit should persist after save" ) def test_full_workflow_modify_price_total(self): """ Test full workflow: create vendor bill → modify price_total → save → verify. This integration test verifies that: 1. A vendor bill can be created successfully 2. Invoice lines with taxes can be added 3. price_total can be modified via onchange 4. The bill can be saved with the modified values 5. The saved values are correct and persistent Requirement 4.3: Standard Odoo validations and computations work correctly """ # Step 1: Create a vendor bill vendor_bill = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.vendor.id, 'invoice_date': '2024-01-20', }) self.assertEqual(vendor_bill.state, 'draft', "Vendor bill should be in draft state") # Step 2: Add invoice line with tax line = self.env['account.move.line'].create({ 'move_id': vendor_bill.id, 'product_id': self.product_b.id, 'name': 'Product B - Test', 'quantity': 10.0, 'price_unit': 250.0, 'tax_ids': [(6, 0, [self.tax_20.id])], }) # Verify initial values self.assertEqual(line.quantity, 10.0) self.assertEqual(line.price_unit, 250.0) # Step 3: Modify price_total # User wants total with tax of 3600 instead of 3000 (2500 * 1.20) line.price_total = 3600.0 line._onchange_price_total() # Step 4: Verify price_unit was recalculated # Expected: price_subtotal = 3600.0 / 1.20 = 3000.0 # price_unit = 3000.0 / 10.0 = 300.0 expected_price_unit = 300.0 self.assertAlmostEqual( line.price_unit, expected_price_unit, places=self.precision, msg=f"price_unit should be recalculated to {expected_price_unit}" ) # Step 5: Trigger recomputation (simulating Odoo's compute methods) line._compute_price_subtotal() # Step 6: Verify price_subtotal is correct expected_price_subtotal = 3000.0 self.assertAlmostEqual( line.price_subtotal, expected_price_subtotal, places=2, msg=f"price_subtotal should be {expected_price_subtotal}" ) # Step 7: Verify price_total is maintained self.assertAlmostEqual( line.price_total, 3600.0, places=2, msg="price_total should be maintained after recomputation" ) # Step 8: Save the vendor bill (flush to database) vendor_bill.flush_recordset() # Step 9: Reload and verify persistence line.invalidate_recordset() self.assertAlmostEqual( line.price_unit, expected_price_unit, places=self.precision, msg="price_unit should persist after save" ) def test_multiple_lines_workflow(self): """ Test workflow with multiple invoice lines being modified. Verifies that modifications to multiple lines work independently and all changes are saved correctly. Requirement 4.3: Module doesn't interfere with standard Odoo flows """ # Create vendor bill vendor_bill = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.vendor.id, 'invoice_date': '2024-01-25', }) # Add first line line1 = self.env['account.move.line'].create({ 'move_id': vendor_bill.id, 'product_id': self.product_a.id, 'name': 'Line 1', 'quantity': 3.0, 'price_unit': 100.0, 'tax_ids': [(6, 0, [self.tax_10.id])], }) # Add second line line2 = self.env['account.move.line'].create({ 'move_id': vendor_bill.id, 'product_id': self.product_b.id, 'name': 'Line 2', 'quantity': 2.0, 'price_unit': 250.0, 'tax_ids': [(6, 0, [self.tax_20.id])], }) # Modify first line's price_subtotal line1.price_subtotal = 450.0 line1._onchange_price_subtotal() # Modify second line's price_total line2.price_total = 720.0 line2._onchange_price_total() # Verify line 1 expected_price_unit_1 = 150.0 # 450 / 3 self.assertAlmostEqual( line1.price_unit, expected_price_unit_1, places=self.precision, msg="Line 1 price_unit should be recalculated correctly" ) # Verify line 2 expected_price_unit_2 = 300.0 # (720 / 1.20) / 2 self.assertAlmostEqual( line2.price_unit, expected_price_unit_2, places=self.precision, msg="Line 2 price_unit should be recalculated correctly" ) # Save and verify persistence vendor_bill.flush_recordset() line1.invalidate_recordset() line2.invalidate_recordset() self.assertAlmostEqual(line1.price_unit, expected_price_unit_1, places=self.precision) self.assertAlmostEqual(line2.price_unit, expected_price_unit_2, places=self.precision) def test_compatibility_with_standard_validations(self): """ Test that standard Odoo validations still work correctly. Verifies that: 1. Required fields are still validated 2. Partner validation works 3. Date validation works 4. The module doesn't break standard Odoo behavior Requirement 4.3: Trigger all standard Odoo validations """ # Test 1: Vendor bill requires a partner with self.assertRaises(Exception): vendor_bill = self.env['account.move'].create({ 'move_type': 'in_invoice', # Missing partner_id - should fail 'invoice_date': '2024-01-30', }) # Test 2: Create valid vendor bill vendor_bill = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.vendor.id, 'invoice_date': '2024-01-30', }) # Test 3: Add line and verify standard compute methods work line = self.env['account.move.line'].create({ 'move_id': vendor_bill.id, 'product_id': self.product_a.id, 'name': 'Test Line', 'quantity': 5.0, 'price_unit': 100.0, 'tax_ids': [(6, 0, [self.tax_10.id])], }) # Trigger standard Odoo computations line._compute_price_subtotal() # Verify standard computation works expected_subtotal = 500.0 # 5 * 100 self.assertAlmostEqual( line.price_subtotal, expected_subtotal, places=2, msg="Standard Odoo price_subtotal computation should work" ) # Test 4: Modify via our onchange and verify it doesn't break validations line.price_subtotal = 600.0 line._onchange_price_subtotal() # Verify we can still save vendor_bill.flush_recordset() # Test 5: Verify the bill can be posted (if accounting is configured) # Note: This might fail in test environment without proper accounting setup # but we verify the state transition is possible self.assertEqual(vendor_bill.state, 'draft') def test_refund_workflow(self): """ Test workflow with vendor refund (credit note). Verifies that the module works correctly with refunds, including negative values. Requirement 4.3: Module works with all vendor bill types """ # Create vendor refund vendor_refund = self.env['account.move'].create({ 'move_type': 'in_refund', 'partner_id': self.vendor.id, 'invoice_date': '2024-02-01', }) self.assertEqual(vendor_refund.state, 'draft') # Add line with negative values line = self.env['account.move.line'].create({ 'move_id': vendor_refund.id, 'product_id': self.product_a.id, 'name': 'Refund Line', 'quantity': 5.0, 'price_unit': -100.0, # Negative for refund 'tax_ids': [(6, 0, [self.tax_10.id])], }) # Modify price_subtotal with negative value line.price_subtotal = -600.0 line._onchange_price_subtotal() # Verify calculation works with negative values expected_price_unit = -120.0 # -600 / 5 self.assertAlmostEqual( line.price_unit, expected_price_unit, places=self.precision, msg="Refund with negative values should calculate correctly" ) # Save and verify vendor_refund.flush_recordset() line.invalidate_recordset() self.assertAlmostEqual( line.price_unit, expected_price_unit, places=self.precision, msg="Negative values should persist correctly" ) def test_no_interference_with_other_move_types(self): """ Test that the module doesn't interfere with non-vendor-bill move types. Verifies that customer invoices and other move types are not affected. Requirement 4.2: Module doesn't interfere with standard Odoo flows """ # Create a customer invoice (out_invoice) customer = self.env['res.partner'].create({ 'name': 'Test Customer', 'customer_rank': 1, }) customer_invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': customer.id, 'invoice_date': '2024-02-05', }) # Add line to customer invoice line = self.env['account.move.line'].create({ 'move_id': customer_invoice.id, 'product_id': self.product_a.id, 'name': 'Customer Line', 'quantity': 5.0, 'price_unit': 100.0, }) # Store original price_unit original_price_unit = line.price_unit # Try to modify price_subtotal (should be skipped by onchange) line.price_subtotal = 600.0 line._onchange_price_subtotal() # Verify price_unit was NOT changed (because it's not a vendor bill) self.assertEqual( line.price_unit, original_price_unit, msg="Customer invoice should not be affected by vendor bill module" ) def test_workflow_with_decimal_precision(self): """ Test workflow with values that require decimal precision handling. Verifies that decimal precision is correctly applied throughout the workflow. Requirement 3.5: Use Odoo's configured decimal precision """ # Create vendor bill vendor_bill = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.vendor.id, 'invoice_date': '2024-02-10', }) # Add line with values that will result in many decimal places line = self.env['account.move.line'].create({ 'move_id': vendor_bill.id, 'product_id': self.product_a.id, 'name': 'Precision Test', 'quantity': 3.0, 'price_unit': 100.0, 'tax_ids': [(6, 0, [self.tax_10.id])], }) # Modify price_subtotal to a value that will create many decimals # 1000 / 3 = 333.333333... line.price_subtotal = 1000.0 line._onchange_price_subtotal() # Verify price_unit is rounded to configured precision expected_price_unit = round(1000.0 / 3.0, self.precision) self.assertAlmostEqual( line.price_unit, expected_price_unit, places=self.precision, msg="price_unit should be rounded to configured precision" ) # Verify the number of decimal places doesn't exceed precision price_unit_str = str(line.price_unit) if '.' in price_unit_str: decimal_places = len(price_unit_str.split('.')[1]) self.assertLessEqual( decimal_places, self.precision, msg=f"Decimal places should not exceed {self.precision}" ) # Save and verify persistence vendor_bill.flush_recordset() line.invalidate_recordset() self.assertAlmostEqual( line.price_unit, expected_price_unit, places=self.precision, msg="Rounded price_unit should persist" )