[IMP] product_pricelist_total_margin: Add global_margin_type for min/max limits

- Add global_margin_type field in res.config.settings
  * Options: 'markup' (default) or 'margin' (commercial margin)
  * Determines how global min/max percentages are interpreted
- Refactor _apply_global_margin_limits():
  * Now receives price instead of margin percentage
  * Calculates min/max prices based on global_margin_type
  * Returns adjusted price instead of adjusted margin
  * Supports both markup and commercial margin formulas
- Update _compute_price() to apply limits after price calculation
- Update res_config_settings_views.xml to show global_margin_type selector
- Update help texts with examples for both calculation methods
- Add 2 new tests to validate global limits with commercial margin type:
  * test_global_minimum_with_commercial_margin_type
  * test_global_maximum_with_commercial_margin_type
- All 13 tests passing (11 existing + 2 new)

Example with global min 25%:
- Markup: Min price = Cost × 1.25
- Commercial Margin: Min price = Cost / 0.75 (ensures 25% margin on PVP)
This commit is contained in:
snt 2026-02-21 19:16:16 +01:00
parent 07cc0eb517
commit 449bb75bb6
4 changed files with 180 additions and 25 deletions

View file

@ -411,3 +411,97 @@ class TestTotalMargin(TransactionCase):
places=4,
msg=f"Commercial margin should be 20%, got {commercial_margin * 100}%",
)
def test_global_minimum_with_commercial_margin_type(self):
"""Test global minimum margin with commercial margin calculation."""
# Set global minimum to 25% and type to commercial margin
self.env["ir.config_parameter"].sudo().set_param(
"product_pricelist_total_margin.min_percent", "25.0"
)
self.env["ir.config_parameter"].sudo().set_param(
"product_pricelist_total_margin.global_margin_type", "margin"
)
# Enable total margin (calculated: -5% + 25% = 20%, below min 25%)
self.item_chained.use_total_margin = True
self.item_chained.margin_type = "markup" # Item uses markup
price = self.pricelist_chained._get_product_price(
product=self.product,
quantity=1.0,
)
# Expected: Global limit is interpreted as commercial margin
# Min 25% commercial margin: PVP = Cost / (1 - 0.25) = 4.68 / 0.75 = 6.24
# Item calculates 20% markup: 4.68 * 1.20 = 5.616, but global min forces 6.24
expected_price = 4.68 / 0.75
self.assertAlmostEqual(
price,
expected_price,
places=2,
msg=f"Global minimum (commercial margin 25%) should give {expected_price}, got {price}",
)
# Verify the actual commercial margin is 25%
commercial_margin = (price - 4.68) / price
self.assertAlmostEqual(
commercial_margin,
0.25,
places=4,
msg=f"Commercial margin should be 25%, got {commercial_margin * 100}%",
)
# Clean up
self.env["ir.config_parameter"].sudo().set_param(
"product_pricelist_total_margin.min_percent", "0.0"
)
self.env["ir.config_parameter"].sudo().set_param(
"product_pricelist_total_margin.global_margin_type", "markup"
)
def test_global_maximum_with_commercial_margin_type(self):
"""Test global maximum margin with commercial margin calculation."""
# Set global maximum to 15% and type to commercial margin
self.env["ir.config_parameter"].sudo().set_param(
"product_pricelist_total_margin.max_percent", "15.0"
)
self.env["ir.config_parameter"].sudo().set_param(
"product_pricelist_total_margin.global_margin_type", "margin"
)
# Enable total margin (calculated: -5% + 25% = 20%, above max 15%)
self.item_chained.use_total_margin = True
self.item_chained.margin_type = "margin" # Item uses commercial margin
price = self.pricelist_chained._get_product_price(
product=self.product,
quantity=1.0,
)
# Expected: Global limit is interpreted as commercial margin
# Max 15% commercial margin: PVP = Cost / (1 - 0.15) = 4.68 / 0.85 = 5.506
# Item calculates 20% margin: 4.68 / 0.80 = 5.85, but global max limits to 5.506
expected_price = 4.68 / 0.85
self.assertAlmostEqual(
price,
expected_price,
places=2,
msg=f"Global maximum (commercial margin 15%) should give {expected_price}, got {price}",
)
# Verify the actual commercial margin is 15%
commercial_margin = (price - 4.68) / price
self.assertAlmostEqual(
commercial_margin,
0.15,
places=4,
msg=f"Commercial margin should be 15%, got {commercial_margin * 100}%",
)
# Clean up
self.env["ir.config_parameter"].sudo().set_param(
"product_pricelist_total_margin.max_percent", "0.0"
)
self.env["ir.config_parameter"].sudo().set_param(
"product_pricelist_total_margin.global_margin_type", "markup"
)