# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from lxml import etree from odoo.tests import TransactionCase class TestViewConfiguration(TransactionCase): """ Unit tests for view configuration in vendor bill editable totals. Tests cover: - Verify price_subtotal field is editable in vendor bill form (Requirement 1.1) - Verify price_total field is editable in vendor bill form (Requirement 2.1) - Verify fields are readonly in non-vendor-bill contexts (Requirement 5.2) """ def setUp(self): super().setUp() # Get the view record self.view = self.env.ref('vendor_bill_editable_totals.view_move_form_editable_totals') # Get the base account.move form view self.base_view = self.env.ref('account.view_move_form') def test_view_exists(self): """ Test that the custom view exists and inherits from the correct base view. Requirement 5.2: Use proper view inheritance """ self.assertTrue(self.view, "Custom view should exist") self.assertEqual( self.view.model, 'account.move', "View should be for account.move model" ) self.assertEqual( self.view.inherit_id.id, self.base_view.id, "View should inherit from account.view_move_form" ) def test_price_subtotal_field_editable(self): """ Test that price_subtotal field is made editable in the view. Requirement 1.1: price_subtotal field should be editable in vendor bill form """ # Parse the view architecture arch = etree.fromstring(self.view.arch) # Find the xpath that modifies price_subtotal xpath_elements = arch.xpath("//xpath[@expr=\"//field[@name='invoice_line_ids']/tree//field[@name='price_subtotal']\"]") self.assertTrue( len(xpath_elements) > 0, "XPath for price_subtotal field should exist in view" ) # Check that readonly attribute is set to 0 xpath_element = xpath_elements[0] attributes = xpath_element.xpath(".//attribute[@name='readonly']") self.assertTrue( len(attributes) > 0, "Readonly attribute should be set for price_subtotal" ) self.assertEqual( attributes[0].text, '0', "price_subtotal readonly should be set to 0 (editable)" ) def test_price_total_field_editable(self): """ Test that price_total field is made editable in the view. Requirement 2.1: price_total field should be editable in vendor bill form """ # Parse the view architecture arch = etree.fromstring(self.view.arch) # Find the xpath that modifies price_total xpath_elements = arch.xpath("//xpath[@expr=\"//field[@name='invoice_line_ids']/tree//field[@name='price_total']\"]") self.assertTrue( len(xpath_elements) > 0, "XPath for price_total field should exist in view" ) # Check that readonly attribute is set to 0 xpath_element = xpath_elements[0] attributes = xpath_element.xpath(".//attribute[@name='readonly']") self.assertTrue( len(attributes) > 0, "Readonly attribute should be set for price_total" ) self.assertEqual( attributes[0].text, '0', "price_total readonly should be set to 0 (editable)" ) def test_fields_readonly_when_not_draft(self): """ Test that fields have attrs to make them readonly when state is not draft. Requirement 5.2: Fields should be readonly in non-draft states """ # Parse the view architecture arch = etree.fromstring(self.view.arch) # Check price_subtotal attrs xpath_subtotal = arch.xpath("//xpath[@expr=\"//field[@name='invoice_line_ids']/tree//field[@name='price_subtotal']\"]") self.assertTrue(len(xpath_subtotal) > 0, "price_subtotal xpath should exist") attrs_subtotal = xpath_subtotal[0].xpath(".//attribute[@name='attrs']") self.assertTrue( len(attrs_subtotal) > 0, "Attrs attribute should be set for price_subtotal to control readonly state" ) # Verify the attrs contain readonly condition attrs_text = attrs_subtotal[0].text self.assertIn( 'readonly', attrs_text, "Attrs should contain readonly condition for price_subtotal" ) self.assertIn( 'draft', attrs_text, "Attrs should reference draft state for price_subtotal" ) # Check price_total attrs xpath_total = arch.xpath("//xpath[@expr=\"//field[@name='invoice_line_ids']/tree//field[@name='price_total']\"]") self.assertTrue(len(xpath_total) > 0, "price_total xpath should exist") attrs_total = xpath_total[0].xpath(".//attribute[@name='attrs']") self.assertTrue( len(attrs_total) > 0, "Attrs attribute should be set for price_total to control readonly state" ) # Verify the attrs contain readonly condition attrs_text = attrs_total[0].text self.assertIn( 'readonly', attrs_text, "Attrs should contain readonly condition for price_total" ) self.assertIn( 'draft', attrs_text, "Attrs should reference draft state for price_total" ) def test_view_applies_to_vendor_bills(self): """ Test that the view modifications apply to vendor bill context. Requirement 1.1, 2.1: Fields should be editable in vendor bill form """ # Create a vendor partner vendor = self.env['res.partner'].create({ 'name': 'Test Vendor', 'supplier_rank': 1, }) # Create a vendor bill vendor_bill = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': vendor.id, 'invoice_date': '2024-01-01', }) # Get the view for this record view_id = self.env['ir.ui.view'].search([ ('model', '=', 'account.move'), ('type', '=', 'form'), ('inherit_id', '=', self.base_view.id), ], limit=1) self.assertTrue( view_id, "View should be available for vendor bills" ) def test_fields_editable_in_draft_state(self): """ Test that fields are editable when vendor bill is in draft state. Requirement 1.1, 2.1: Fields should be editable in draft vendor bills """ # Create a vendor partner vendor = self.env['res.partner'].create({ 'name': 'Test Vendor', 'supplier_rank': 1, }) # Create a product product = self.env['res.product'].create({ 'name': 'Test Product', 'type': 'service', 'list_price': 100.0, }) # Create a vendor bill in draft state vendor_bill = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': vendor.id, 'invoice_date': '2024-01-01', 'state': 'draft', }) # Create a line line = self.env['account.move.line'].create({ 'move_id': vendor_bill.id, 'product_id': product.id, 'name': 'Test Line', 'quantity': 10.0, 'price_unit': 100.0, }) # Verify the bill is in draft state self.assertEqual( vendor_bill.state, 'draft', "Vendor bill should be in draft state" ) # Test that we can modify price_subtotal (this would fail if readonly) line.price_subtotal = 1200.0 line._onchange_price_subtotal() # If we got here without error, the field is editable self.assertAlmostEqual( line.price_unit, 120.0, places=2, msg="price_unit should be recalculated when price_subtotal is modified" ) def test_fields_readonly_in_posted_state(self): """ Test that fields behavior is appropriate when vendor bill is posted. Requirement 5.2: Fields should respect state-based readonly conditions Note: This test verifies the attrs configuration exists. The actual readonly enforcement happens at the UI level based on the attrs domain. """ # Parse the view architecture arch = etree.fromstring(self.view.arch) # Verify both fields have attrs that reference parent.state xpath_subtotal = arch.xpath("//xpath[@expr=\"//field[@name='invoice_line_ids']/tree//field[@name='price_subtotal']\"]") attrs_subtotal = xpath_subtotal[0].xpath(".//attribute[@name='attrs']") attrs_text_subtotal = attrs_subtotal[0].text # The attrs should make the field readonly when state != 'draft' self.assertIn( 'parent.state', attrs_text_subtotal, "Attrs should reference parent.state for price_subtotal" ) xpath_total = arch.xpath("//xpath[@expr=\"//field[@name='invoice_line_ids']/tree//field[@name='price_total']\"]") attrs_total = xpath_total[0].xpath(".//attribute[@name='attrs']") attrs_text_total = attrs_total[0].text self.assertIn( 'parent.state', attrs_text_total, "Attrs should reference parent.state for price_total" )