[18.0][MIG] account_invoice_triple_discount_readonly: Port to Odoo 18.0
* Update module version to 18.0.1.0.0 * Remove view modifications as they are handled by parent modules * Add comprehensive test suite (34 tests): - Test triple discount mixin write() method behavior - Test account.move.line with triple discounts - Test purchase.order.line with triple discounts * Add oca_dependencies.txt with required OCA repositories * Fix write() method to handle explicit discounts correctly
This commit is contained in:
parent
67554c95f5
commit
123aabb775
10 changed files with 602 additions and 77 deletions
|
|
@ -2,17 +2,12 @@
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
{
|
{
|
||||||
"name": "Account Invoice Triple Discount Readonly",
|
"name": "Account Invoice Triple Discount Readonly",
|
||||||
"version": "16.0.1.0.0",
|
"version": "18.0.1.0.0",
|
||||||
"summary": "Make total discount readonly and fix discount2/discount3 write issue",
|
"summary": "Make total discount readonly and fix discount2/discount3 write issue",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"author": "Criptomart",
|
"author": "Criptomart",
|
||||||
"website": "https://github.com/OCA/account-invoicing",
|
"website": "https://github.com/OCA/account-invoicing",
|
||||||
"depends": ["account_invoice_triple_discount", "purchase_triple_discount"],
|
"depends": ["account_invoice_triple_discount", "purchase_triple_discount"],
|
||||||
"data": [
|
"data": [],
|
||||||
"views/product_supplierinfo_view.xml",
|
|
||||||
"views/res_partner_view.xml",
|
|
||||||
"views/purchase_order_view.xml",
|
|
||||||
"views/account_move_view.xml",
|
|
||||||
],
|
|
||||||
"installable": True,
|
"installable": True,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
# OCA Dependencies for account_invoice_triple_discount_readonly
|
||||||
|
# Format: repository_name branch
|
||||||
|
|
||||||
|
account-invoicing https://github.com/OCA/account-invoicing.git 18.0
|
||||||
|
purchase-workflow https://github.com/OCA/purchase-workflow.git 18.0
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from . import test_triple_discount_mixin
|
||||||
|
from . import test_account_move
|
||||||
|
from . import test_purchase_order
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
# Copyright (C) 2025: Criptomart (https://criptomart.net)
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
|
from odoo.tests import tagged
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
|
@tagged("post_install", "-at_install")
|
||||||
|
class TestAccountMove(TransactionCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
# Create a partner
|
||||||
|
cls.partner = cls.env["res.partner"].create({
|
||||||
|
"name": "Test Customer",
|
||||||
|
"email": "customer@test.com",
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create a product
|
||||||
|
cls.product = cls.env["product.product"].create({
|
||||||
|
"name": "Test Product Invoice",
|
||||||
|
"type": "consu",
|
||||||
|
"list_price": 200.0,
|
||||||
|
"standard_price": 100.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create tax
|
||||||
|
cls.tax = cls.env["account.tax"].create({
|
||||||
|
"name": "Test Tax 10%",
|
||||||
|
"amount": 10.0,
|
||||||
|
"amount_type": "percent",
|
||||||
|
"type_tax_use": "sale",
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create an invoice
|
||||||
|
cls.invoice = cls.env["account.move"].create({
|
||||||
|
"move_type": "out_invoice",
|
||||||
|
"partner_id": cls.partner.id,
|
||||||
|
"invoice_date": "2026-01-01",
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create invoice line
|
||||||
|
cls.invoice_line = cls.env["account.move.line"].create({
|
||||||
|
"move_id": cls.invoice.id,
|
||||||
|
"product_id": cls.product.id,
|
||||||
|
"quantity": 5,
|
||||||
|
"price_unit": 200.0,
|
||||||
|
"discount1": 10.0,
|
||||||
|
"discount2": 5.0,
|
||||||
|
"discount3": 2.0,
|
||||||
|
"tax_ids": [(6, 0, [cls.tax.id])],
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_invoice_line_discount_readonly(self):
|
||||||
|
"""Test that discount field is readonly in invoice lines"""
|
||||||
|
field = self.invoice_line._fields["discount"]
|
||||||
|
self.assertTrue(field.readonly, "Discount field should be readonly in invoice lines")
|
||||||
|
|
||||||
|
def test_invoice_line_write_with_explicit_discounts(self):
|
||||||
|
"""Test writing invoice line with explicit discounts"""
|
||||||
|
self.invoice_line.write({
|
||||||
|
"discount": 30.0, # Should be ignored
|
||||||
|
"discount1": 15.0,
|
||||||
|
"discount2": 10.0,
|
||||||
|
"discount3": 5.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(self.invoice_line.discount1, 15.0)
|
||||||
|
self.assertEqual(self.invoice_line.discount2, 10.0)
|
||||||
|
self.assertEqual(self.invoice_line.discount3, 5.0)
|
||||||
|
|
||||||
|
def test_invoice_line_legacy_discount(self):
|
||||||
|
"""Test legacy discount behavior in invoice lines"""
|
||||||
|
self.invoice_line.write({
|
||||||
|
"discount": 20.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Should map to discount1 and reset others
|
||||||
|
self.assertEqual(self.invoice_line.discount1, 20.0)
|
||||||
|
self.assertEqual(self.invoice_line.discount2, 0.0)
|
||||||
|
self.assertEqual(self.invoice_line.discount3, 0.0)
|
||||||
|
|
||||||
|
def test_invoice_line_price_calculation(self):
|
||||||
|
"""Test that price subtotal is calculated correctly with triple discount"""
|
||||||
|
self.invoice_line.write({
|
||||||
|
"discount1": 10.0,
|
||||||
|
"discount2": 5.0,
|
||||||
|
"discount3": 0.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Base: 5 * 200 = 1000
|
||||||
|
# After 10% discount: 900
|
||||||
|
# After 5% discount: 855
|
||||||
|
expected_subtotal = 5 * 200 * 0.9 * 0.95
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
self.invoice_line.price_subtotal, expected_subtotal, places=2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_multiple_invoice_lines(self):
|
||||||
|
"""Test multiple invoice lines with different discounts"""
|
||||||
|
line2 = self.env["account.move.line"].create({
|
||||||
|
"move_id": self.invoice.id,
|
||||||
|
"product_id": self.product.id,
|
||||||
|
"quantity": 3,
|
||||||
|
"price_unit": 150.0,
|
||||||
|
"discount1": 20.0,
|
||||||
|
"discount2": 10.0,
|
||||||
|
"discount3": 5.0,
|
||||||
|
"tax_ids": [(6, 0, [self.tax.id])],
|
||||||
|
})
|
||||||
|
|
||||||
|
# Verify both lines have correct discounts
|
||||||
|
self.assertEqual(self.invoice_line.discount1, 10.0)
|
||||||
|
self.assertEqual(line2.discount1, 20.0)
|
||||||
|
self.assertEqual(line2.discount2, 10.0)
|
||||||
|
self.assertEqual(line2.discount3, 5.0)
|
||||||
|
|
||||||
|
def test_invoice_line_update_quantity(self):
|
||||||
|
"""Test updating quantity doesn't affect discounts"""
|
||||||
|
initial_discount1 = self.invoice_line.discount1
|
||||||
|
initial_discount2 = self.invoice_line.discount2
|
||||||
|
|
||||||
|
self.invoice_line.write({
|
||||||
|
"quantity": 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Discounts should remain unchanged
|
||||||
|
self.assertEqual(self.invoice_line.discount1, initial_discount1)
|
||||||
|
self.assertEqual(self.invoice_line.discount2, initial_discount2)
|
||||||
|
# Quantity should be updated
|
||||||
|
self.assertEqual(self.invoice_line.quantity, 10)
|
||||||
|
|
||||||
|
def test_invoice_line_update_price(self):
|
||||||
|
"""Test updating price doesn't affect discounts"""
|
||||||
|
initial_discount1 = self.invoice_line.discount1
|
||||||
|
|
||||||
|
self.invoice_line.write({
|
||||||
|
"price_unit": 250.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Discount should remain unchanged
|
||||||
|
self.assertEqual(self.invoice_line.discount1, initial_discount1)
|
||||||
|
# Price should be updated
|
||||||
|
self.assertEqual(self.invoice_line.price_unit, 250.0)
|
||||||
|
|
||||||
|
def test_invoice_with_zero_discounts(self):
|
||||||
|
"""Test invoice line with all zero discounts"""
|
||||||
|
self.invoice_line.write({
|
||||||
|
"discount1": 0.0,
|
||||||
|
"discount2": 0.0,
|
||||||
|
"discount3": 0.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# All discounts should be zero
|
||||||
|
self.assertEqual(self.invoice_line.discount, 0.0)
|
||||||
|
self.assertEqual(self.invoice_line.discount1, 0.0)
|
||||||
|
self.assertEqual(self.invoice_line.discount2, 0.0)
|
||||||
|
self.assertEqual(self.invoice_line.discount3, 0.0)
|
||||||
|
|
||||||
|
# Subtotal should be quantity * price
|
||||||
|
expected = 5 * 200
|
||||||
|
self.assertEqual(self.invoice_line.price_subtotal, expected)
|
||||||
|
|
||||||
|
def test_invoice_line_combined_operations(self):
|
||||||
|
"""Test combined operations on invoice line"""
|
||||||
|
# Update multiple fields at once
|
||||||
|
self.invoice_line.write({
|
||||||
|
"quantity": 8,
|
||||||
|
"price_unit": 180.0,
|
||||||
|
"discount1": 12.0,
|
||||||
|
"discount2": 6.0,
|
||||||
|
"discount3": 0.0, # Reset discount3 explicitly
|
||||||
|
})
|
||||||
|
|
||||||
|
# All fields should be updated correctly
|
||||||
|
self.assertEqual(self.invoice_line.quantity, 8)
|
||||||
|
self.assertEqual(self.invoice_line.price_unit, 180.0)
|
||||||
|
self.assertEqual(self.invoice_line.discount1, 12.0)
|
||||||
|
self.assertEqual(self.invoice_line.discount2, 6.0)
|
||||||
|
self.assertEqual(self.invoice_line.discount3, 0.0)
|
||||||
|
|
||||||
|
# Calculate expected subtotal: 8 * 180 * (1-0.12) * (1-0.06)
|
||||||
|
expected = 8 * 180 * 0.88 * 0.94
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
self.invoice_line.price_subtotal, expected, places=2
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
# Copyright (C) 2025: Criptomart (https://criptomart.net)
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
|
from odoo.tests import tagged
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
|
@tagged("post_install", "-at_install")
|
||||||
|
class TestPurchaseOrder(TransactionCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
# Create a supplier
|
||||||
|
cls.supplier = cls.env["res.partner"].create({
|
||||||
|
"name": "Test Supplier",
|
||||||
|
"email": "supplier@test.com",
|
||||||
|
"supplier_rank": 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create a product
|
||||||
|
cls.product = cls.env["product.product"].create({
|
||||||
|
"name": "Test Product PO",
|
||||||
|
"type": "product",
|
||||||
|
"list_price": 150.0,
|
||||||
|
"standard_price": 80.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create a purchase order
|
||||||
|
cls.purchase_order = cls.env["purchase.order"].create({
|
||||||
|
"partner_id": cls.supplier.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create purchase order line
|
||||||
|
cls.po_line = cls.env["purchase.order.line"].create({
|
||||||
|
"order_id": cls.purchase_order.id,
|
||||||
|
"product_id": cls.product.id,
|
||||||
|
"product_qty": 10,
|
||||||
|
"price_unit": 150.0,
|
||||||
|
"discount1": 10.0,
|
||||||
|
"discount2": 5.0,
|
||||||
|
"discount3": 2.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_po_line_discount_readonly(self):
|
||||||
|
"""Test that discount field is readonly in PO lines"""
|
||||||
|
field = self.po_line._fields["discount"]
|
||||||
|
self.assertTrue(field.readonly, "Discount field should be readonly in PO lines")
|
||||||
|
|
||||||
|
def test_po_line_write_with_explicit_discounts(self):
|
||||||
|
"""Test writing PO line with explicit discounts"""
|
||||||
|
self.po_line.write({
|
||||||
|
"discount": 25.0, # Should be ignored
|
||||||
|
"discount1": 12.0,
|
||||||
|
"discount2": 8.0,
|
||||||
|
"discount3": 4.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(self.po_line.discount1, 12.0)
|
||||||
|
self.assertEqual(self.po_line.discount2, 8.0)
|
||||||
|
self.assertEqual(self.po_line.discount3, 4.0)
|
||||||
|
|
||||||
|
def test_po_line_legacy_discount(self):
|
||||||
|
"""Test legacy discount behavior in PO lines"""
|
||||||
|
self.po_line.write({
|
||||||
|
"discount": 18.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Should map to discount1 and reset others
|
||||||
|
self.assertEqual(self.po_line.discount1, 18.0)
|
||||||
|
self.assertEqual(self.po_line.discount2, 0.0)
|
||||||
|
self.assertEqual(self.po_line.discount3, 0.0)
|
||||||
|
|
||||||
|
def test_po_line_price_calculation(self):
|
||||||
|
"""Test that price subtotal is calculated correctly with triple discount"""
|
||||||
|
self.po_line.write({
|
||||||
|
"discount1": 15.0,
|
||||||
|
"discount2": 10.0,
|
||||||
|
"discount3": 5.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Base: 10 * 150 = 1500
|
||||||
|
# After 15% discount: 1275
|
||||||
|
# After 10% discount: 1147.5
|
||||||
|
# After 5% discount: 1090.125
|
||||||
|
expected_subtotal = 10 * 150 * 0.85 * 0.90 * 0.95
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
self.po_line.price_subtotal, expected_subtotal, places=2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_multiple_po_lines(self):
|
||||||
|
"""Test multiple PO lines with different discounts"""
|
||||||
|
line2 = self.env["purchase.order.line"].create({
|
||||||
|
"order_id": self.purchase_order.id,
|
||||||
|
"product_id": self.product.id,
|
||||||
|
"product_qty": 5,
|
||||||
|
"price_unit": 120.0,
|
||||||
|
"discount1": 20.0,
|
||||||
|
"discount2": 15.0,
|
||||||
|
"discount3": 10.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Verify both lines have correct discounts
|
||||||
|
self.assertEqual(self.po_line.discount1, 15.0)
|
||||||
|
self.assertEqual(line2.discount1, 20.0)
|
||||||
|
self.assertEqual(line2.discount2, 15.0)
|
||||||
|
self.assertEqual(line2.discount3, 10.0)
|
||||||
|
|
||||||
|
def test_po_line_update_quantity(self):
|
||||||
|
"""Test updating quantity doesn't affect discounts"""
|
||||||
|
initial_discount1 = self.po_line.discount1
|
||||||
|
initial_discount2 = self.po_line.discount2
|
||||||
|
|
||||||
|
self.po_line.write({
|
||||||
|
"product_qty": 20,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Discounts should remain unchanged
|
||||||
|
self.assertEqual(self.po_line.discount1, initial_discount1)
|
||||||
|
self.assertEqual(self.po_line.discount2, initial_discount2)
|
||||||
|
# Quantity should be updated
|
||||||
|
self.assertEqual(self.po_line.product_qty, 20)
|
||||||
|
|
||||||
|
def test_po_line_update_price(self):
|
||||||
|
"""Test updating price doesn't affect discounts"""
|
||||||
|
initial_discount1 = self.po_line.discount1
|
||||||
|
|
||||||
|
self.po_line.write({
|
||||||
|
"price_unit": 200.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Discount should remain unchanged
|
||||||
|
self.assertEqual(self.po_line.discount1, initial_discount1)
|
||||||
|
# Price should be updated
|
||||||
|
self.assertEqual(self.po_line.price_unit, 200.0)
|
||||||
|
|
||||||
|
def test_po_with_zero_discounts(self):
|
||||||
|
"""Test PO line with all zero discounts"""
|
||||||
|
self.po_line.write({
|
||||||
|
"discount1": 0.0,
|
||||||
|
"discount2": 0.0,
|
||||||
|
"discount3": 0.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# All discounts should be zero
|
||||||
|
self.assertEqual(self.po_line.discount, 0.0)
|
||||||
|
self.assertEqual(self.po_line.discount1, 0.0)
|
||||||
|
self.assertEqual(self.po_line.discount2, 0.0)
|
||||||
|
self.assertEqual(self.po_line.discount3, 0.0)
|
||||||
|
|
||||||
|
# Subtotal should be quantity * price
|
||||||
|
expected = 10 * 150
|
||||||
|
self.assertEqual(self.po_line.price_subtotal, expected)
|
||||||
|
|
||||||
|
def test_po_line_combined_operations(self):
|
||||||
|
"""Test combined operations on PO line"""
|
||||||
|
# Update multiple fields at once
|
||||||
|
self.po_line.write({
|
||||||
|
"product_qty": 15,
|
||||||
|
"price_unit": 175.0,
|
||||||
|
"discount1": 18.0,
|
||||||
|
"discount2": 12.0,
|
||||||
|
"discount3": 6.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# All fields should be updated correctly
|
||||||
|
self.assertEqual(self.po_line.product_qty, 15)
|
||||||
|
self.assertEqual(self.po_line.price_unit, 175.0)
|
||||||
|
self.assertEqual(self.po_line.discount1, 18.0)
|
||||||
|
self.assertEqual(self.po_line.discount2, 12.0)
|
||||||
|
self.assertEqual(self.po_line.discount3, 6.0)
|
||||||
|
|
||||||
|
# Calculate expected subtotal
|
||||||
|
expected = 15 * 175 * 0.82 * 0.88 * 0.94
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
self.po_line.price_subtotal, expected, places=2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_po_confirm_with_discounts(self):
|
||||||
|
"""Test confirming PO doesn't alter discounts"""
|
||||||
|
self.po_line.write({
|
||||||
|
"discount1": 10.0,
|
||||||
|
"discount2": 5.0,
|
||||||
|
"discount3": 2.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Confirm the purchase order
|
||||||
|
self.purchase_order.button_confirm()
|
||||||
|
|
||||||
|
# Discounts should remain unchanged after confirmation
|
||||||
|
self.assertEqual(self.po_line.discount1, 10.0)
|
||||||
|
self.assertEqual(self.po_line.discount2, 5.0)
|
||||||
|
self.assertEqual(self.po_line.discount3, 2.0)
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
# Copyright (C) 2025: Criptomart (https://criptomart.net)
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
|
from odoo.tests import tagged
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
|
@tagged("post_install", "-at_install")
|
||||||
|
class TestTripleDiscountMixin(TransactionCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
# Create a partner
|
||||||
|
cls.partner = cls.env["res.partner"].create({
|
||||||
|
"name": "Test Partner",
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create a product
|
||||||
|
cls.product = cls.env["product.product"].create({
|
||||||
|
"name": "Test Product",
|
||||||
|
"type": "product",
|
||||||
|
"list_price": 100.0,
|
||||||
|
"standard_price": 50.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create a purchase order
|
||||||
|
cls.purchase_order = cls.env["purchase.order"].create({
|
||||||
|
"partner_id": cls.partner.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create a purchase order line
|
||||||
|
cls.po_line = cls.env["purchase.order.line"].create({
|
||||||
|
"order_id": cls.purchase_order.id,
|
||||||
|
"product_id": cls.product.id,
|
||||||
|
"product_qty": 10,
|
||||||
|
"price_unit": 100.0,
|
||||||
|
"discount1": 10.0,
|
||||||
|
"discount2": 5.0,
|
||||||
|
"discount3": 2.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_discount_field_is_readonly(self):
|
||||||
|
"""Test that the discount field is readonly"""
|
||||||
|
field = self.po_line._fields["discount"]
|
||||||
|
self.assertTrue(field.readonly, "Discount field should be readonly")
|
||||||
|
|
||||||
|
def test_write_with_explicit_discounts(self):
|
||||||
|
"""Test writing with explicit discount1, discount2, discount3"""
|
||||||
|
# Write with explicit discounts
|
||||||
|
self.po_line.write({
|
||||||
|
"discount": 20.0, # This should be ignored
|
||||||
|
"discount1": 15.0,
|
||||||
|
"discount2": 10.0,
|
||||||
|
"discount3": 5.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Verify explicit discounts were applied
|
||||||
|
self.assertEqual(self.po_line.discount1, 15.0)
|
||||||
|
self.assertEqual(self.po_line.discount2, 10.0)
|
||||||
|
self.assertEqual(self.po_line.discount3, 5.0)
|
||||||
|
|
||||||
|
# The computed discount field should reflect the combined discounts
|
||||||
|
# Formula: 100 - (100 * (1 - 0.15) * (1 - 0.10) * (1 - 0.05))
|
||||||
|
expected_discount = 100 - (100 * 0.85 * 0.90 * 0.95)
|
||||||
|
self.assertAlmostEqual(self.po_line.discount, expected_discount, places=2)
|
||||||
|
|
||||||
|
def test_write_only_discount1(self):
|
||||||
|
"""Test writing only discount1 explicitly"""
|
||||||
|
self.po_line.write({
|
||||||
|
"discount": 25.0, # This should be ignored
|
||||||
|
"discount1": 20.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Only discount1 should change
|
||||||
|
self.assertEqual(self.po_line.discount1, 20.0)
|
||||||
|
# Others should remain unchanged
|
||||||
|
self.assertEqual(self.po_line.discount2, 5.0)
|
||||||
|
self.assertEqual(self.po_line.discount3, 2.0)
|
||||||
|
|
||||||
|
def test_write_only_discount2(self):
|
||||||
|
"""Test writing only discount2 explicitly"""
|
||||||
|
self.po_line.write({
|
||||||
|
"discount": 30.0, # This should be ignored
|
||||||
|
"discount2": 12.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Only discount2 should change
|
||||||
|
self.assertEqual(self.po_line.discount2, 12.0)
|
||||||
|
# Others should remain unchanged from previous test
|
||||||
|
self.assertEqual(self.po_line.discount1, 20.0)
|
||||||
|
self.assertEqual(self.po_line.discount3, 2.0)
|
||||||
|
|
||||||
|
def test_write_only_discount3(self):
|
||||||
|
"""Test writing only discount3 explicitly"""
|
||||||
|
self.po_line.write({
|
||||||
|
"discount": 35.0, # This should be ignored
|
||||||
|
"discount3": 8.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Only discount3 should change
|
||||||
|
self.assertEqual(self.po_line.discount3, 8.0)
|
||||||
|
# Others should remain unchanged from previous tests
|
||||||
|
self.assertEqual(self.po_line.discount1, 20.0)
|
||||||
|
self.assertEqual(self.po_line.discount2, 12.0)
|
||||||
|
|
||||||
|
def test_write_legacy_discount_only(self):
|
||||||
|
"""Test legacy behavior: writing only discount field"""
|
||||||
|
# Reset to known state first
|
||||||
|
self.po_line.write({
|
||||||
|
"discount1": 10.0,
|
||||||
|
"discount2": 5.0,
|
||||||
|
"discount3": 2.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Write only discount (legacy behavior)
|
||||||
|
self.po_line.write({
|
||||||
|
"discount": 25.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Should map to discount1 and reset others
|
||||||
|
self.assertEqual(self.po_line.discount1, 25.0)
|
||||||
|
self.assertEqual(self.po_line.discount2, 0.0)
|
||||||
|
self.assertEqual(self.po_line.discount3, 0.0)
|
||||||
|
|
||||||
|
def test_write_multiple_times(self):
|
||||||
|
"""Test writing multiple times to ensure consistency"""
|
||||||
|
# First write
|
||||||
|
self.po_line.write({
|
||||||
|
"discount1": 10.0,
|
||||||
|
"discount2": 10.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(self.po_line.discount1, 10.0)
|
||||||
|
self.assertEqual(self.po_line.discount2, 10.0)
|
||||||
|
|
||||||
|
# Second write
|
||||||
|
self.po_line.write({
|
||||||
|
"discount": 5.0,
|
||||||
|
"discount3": 5.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# discount3 should change, others remain
|
||||||
|
self.assertEqual(self.po_line.discount1, 10.0)
|
||||||
|
self.assertEqual(self.po_line.discount2, 10.0)
|
||||||
|
self.assertEqual(self.po_line.discount3, 5.0)
|
||||||
|
|
||||||
|
def test_write_zero_discounts(self):
|
||||||
|
"""Test writing zero discounts"""
|
||||||
|
self.po_line.write({
|
||||||
|
"discount1": 0.0,
|
||||||
|
"discount2": 0.0,
|
||||||
|
"discount3": 0.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(self.po_line.discount1, 0.0)
|
||||||
|
self.assertEqual(self.po_line.discount2, 0.0)
|
||||||
|
self.assertEqual(self.po_line.discount3, 0.0)
|
||||||
|
self.assertEqual(self.po_line.discount, 0.0)
|
||||||
|
|
||||||
|
def test_write_combined_scenario(self):
|
||||||
|
"""Test a realistic combined scenario"""
|
||||||
|
# Initial state
|
||||||
|
self.po_line.write({
|
||||||
|
"discount1": 15.0,
|
||||||
|
"discount2": 5.0,
|
||||||
|
"discount3": 0.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# User tries to update discount field (should be ignored if explicit discounts present)
|
||||||
|
self.po_line.write({
|
||||||
|
"discount": 50.0,
|
||||||
|
"discount1": 20.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# discount1 should be updated, others unchanged
|
||||||
|
self.assertEqual(self.po_line.discount1, 20.0)
|
||||||
|
self.assertEqual(self.po_line.discount2, 5.0)
|
||||||
|
self.assertEqual(self.po_line.discount3, 0.0)
|
||||||
|
|
||||||
|
def test_discount_calculation_accuracy(self):
|
||||||
|
"""Test that discount calculation is accurate"""
|
||||||
|
self.po_line.write({
|
||||||
|
"discount1": 10.0,
|
||||||
|
"discount2": 10.0,
|
||||||
|
"discount3": 10.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Combined discount: 100 - (100 * 0.9 * 0.9 * 0.9) = 27.1
|
||||||
|
expected = 100 - (100 * 0.9 * 0.9 * 0.9)
|
||||||
|
self.assertAlmostEqual(self.po_line.discount, expected, places=2)
|
||||||
|
|
||||||
|
def test_write_without_discount_field(self):
|
||||||
|
"""Test writing other fields without touching discount fields"""
|
||||||
|
initial_discount1 = self.po_line.discount1
|
||||||
|
|
||||||
|
# Write other fields
|
||||||
|
self.po_line.write({
|
||||||
|
"product_qty": 20,
|
||||||
|
"price_unit": 150.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Discounts should remain unchanged
|
||||||
|
self.assertEqual(self.po_line.discount1, initial_discount1)
|
||||||
|
# But other fields should be updated
|
||||||
|
self.assertEqual(self.po_line.product_qty, 20)
|
||||||
|
self.assertEqual(self.po_line.price_unit, 150.0)
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<!-- Make discount field readonly in invoice lines -->
|
<!-- Make discount field readonly in invoice lines form view -->
|
||||||
<record id="invoice_triple_discount_readonly" model="ir.ui.view">
|
<record id="invoice_triple_discount_readonly" model="ir.ui.view">
|
||||||
<field name="name">account.invoice.triple.discount.readonly</field>
|
<field name="name">account.invoice.triple.discount.readonly</field>
|
||||||
<field name="model">account.move</field>
|
<field name="model">account.move</field>
|
||||||
|
|
@ -10,12 +10,7 @@
|
||||||
ref="account_invoice_triple_discount.invoice_triple_discount_form_view"
|
ref="account_invoice_triple_discount.invoice_triple_discount_form_view"
|
||||||
/>
|
/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath
|
<!-- Make the total discount field readonly in form view to force users to use discount1/2/3 -->
|
||||||
expr="//field[@name='invoice_line_ids']//tree//field[@name='discount']"
|
|
||||||
position="attributes"
|
|
||||||
>
|
|
||||||
<attribute name="readonly">1</attribute>
|
|
||||||
</xpath>
|
|
||||||
<xpath
|
<xpath
|
||||||
expr="//field[@name='invoice_line_ids']//form//field[@name='discount']"
|
expr="//field[@name='invoice_line_ids']//form//field[@name='discount']"
|
||||||
position="attributes"
|
position="attributes"
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<!-- Make discount field readonly in supplier info form view -->
|
<!-- No need to modify product.supplierinfo views as purchase_triple_discount already hides discount field -->
|
||||||
<record model="ir.ui.view" id="product_supplierinfo_form_view_readonly_discount">
|
|
||||||
<field name="model">product.supplierinfo</field>
|
|
||||||
<field
|
|
||||||
name="inherit_id"
|
|
||||||
ref="purchase_triple_discount.product_supplierinfo_form_view"
|
|
||||||
/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//field[@name='discount']" position="attributes">
|
|
||||||
<attribute name="invisible">1</attribute>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Make discount field readonly in supplier info tree view -->
|
|
||||||
<record model="ir.ui.view" id="product_supplierinfo_tree_view_readonly_discount">
|
|
||||||
<field name="model">product.supplierinfo</field>
|
|
||||||
<field
|
|
||||||
name="inherit_id"
|
|
||||||
ref="purchase_triple_discount.product_supplierinfo_tree_view"
|
|
||||||
/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//field[@name='discount']" position="attributes">
|
|
||||||
<attribute name="invisible">1</attribute>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<!-- Make discount field readonly in purchase order lines -->
|
<!-- No need to modify purchase.order views as purchase_triple_discount already hides discount field -->
|
||||||
<record id="purchase_order_triple_discount_readonly" model="ir.ui.view">
|
|
||||||
<field name="name">purchase.order.triple.discount.readonly</field>
|
|
||||||
<field name="model">purchase.order</field>
|
|
||||||
<field
|
|
||||||
name="inherit_id"
|
|
||||||
ref="purchase_triple_discount.purchase_order_triple_discount_form_view"
|
|
||||||
/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath
|
|
||||||
expr="//field[@name='order_line']//tree//field[@name='discount']"
|
|
||||||
position="attributes"
|
|
||||||
>
|
|
||||||
<attribute name="readonly">1</attribute>
|
|
||||||
</xpath>
|
|
||||||
<xpath
|
|
||||||
expr="//field[@name='order_line']//form//field[@name='discount']"
|
|
||||||
position="attributes"
|
|
||||||
>
|
|
||||||
<attribute name="readonly">1</attribute>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<!-- Make default_supplierinfo_discount readonly in partner form view -->
|
<!-- No need to modify partner view as purchase_triple_discount already uses discount1/2/3 fields -->
|
||||||
<record model="ir.ui.view" id="res_partner_form_view_readonly_discount">
|
|
||||||
<field name="model">res.partner</field>
|
|
||||||
<field
|
|
||||||
name="inherit_id"
|
|
||||||
ref="purchase_triple_discount.res_partner_form_view"
|
|
||||||
/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<field name="default_supplierinfo_discount" position="attributes">
|
|
||||||
<attribute name="invisible">1</attribute>
|
|
||||||
</field>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue