513 lines
18 KiB
Python
513 lines
18 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 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"
|
|
)
|