Fee rules

This commit is contained in:
2026-05-25 14:17:08 +02:00
parent 12727e53cb
commit c6d7b0da2b
5 changed files with 62 additions and 7 deletions

View File

@@ -992,6 +992,8 @@ class FeeRule(ModelSQL, ModelView):
auto_apply = fields.Boolean("Auto Apply")
party = fields.Many2One('party.party', "Counterparty")
party_category = fields.Many2One(
'party.category', "Counterparty Category")
line_product = fields.Many2One('product.product', "Line Product")
incoterm_code = fields.Char("Incoterm Code")
from_location = fields.Many2One('stock.location', "From Location")
@@ -1054,6 +1056,24 @@ class FeeRule(ModelSQL, ModelView):
left and right
and getattr(left, 'id', left) == getattr(right, 'id', right))
@classmethod
def _category_matches(cls, category, target):
while target:
if cls._same_record(category, target):
return True
target = getattr(target, 'parent', None)
return False
@classmethod
def _party_has_category(cls, party, category):
if not category:
return True
if not party:
return False
return any(
cls._category_matches(category, party_category)
for party_category in (getattr(party, 'categories', []) or []))
@classmethod
def _rules_for_lines(cls, apply_on, auto_only=False):
domain = [
@@ -1077,6 +1097,9 @@ class FeeRule(ModelSQL, ModelView):
if any(rule_value and not self._same_record(rule_value, actual)
for rule_value, actual in checks):
return False
if not self._party_has_category(
getattr(contract, 'party', None), self.party_category):
return False
if self.incoterm_code:
incoterm = getattr(contract, 'incoterm', None)
if (getattr(incoterm, 'code', '') or '').upper() != (

View File

@@ -148,16 +148,20 @@ this repository contains the full copyright notices and license terms. -->
</record>
<menuitem
name="Fee Report"
parent="purchase_trade.menu_ctrm_other"
action="act_fee_report_form"
sequence="20"
id="menu_fee_report_form"/>
name="Fees"
sequence="99"
id="menu_fees"/>
<menuitem
name="Fee Rules"
parent="purchase_trade.menu_ctrm_other"
parent="menu_fees"
action="act_fee_rule_form"
sequence="21"
sequence="10"
id="menu_fee_rule_form"/>
<menuitem
name="Fee Report"
parent="menu_fees"
action="act_fee_report_form"
sequence="20"
id="menu_fee_report_form"/>
</data>
</tryton>

View File

@@ -1812,6 +1812,31 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertTrue(rule._matches_line(line, 'purchase_line'))
def test_fee_rule_matches_counterparty_category_parent(self):
'fee rule can match a counterparty by parent category'
parent_category = Mock(id=1, parent=None)
child_category = Mock(id=2, parent=parent_category)
party = Mock(id=3, categories=[child_category])
contract = Mock(
party=party,
incoterm=None,
from_location=None,
to_location=None,
plan=None,
)
line = Mock(product=None, purchase=contract, sale=None)
rule = fee_module.FeeRule()
rule.apply_on = 'purchase_line'
rule.party = None
rule.party_category = parent_category
rule.line_product = None
rule.incoterm_code = None
rule.from_location = None
rule.to_location = None
rule.execution_plan = None
self.assertTrue(rule._matches_line(line, 'purchase_line'))
def test_fee_rule_builds_budgeted_sale_fee_values(self):
'fee rule builds budgeted fee values for a sale line'
currency = Mock(id=1)

View File

@@ -12,6 +12,8 @@
<field name="apply_on"/>
<label name="party"/>
<field name="party"/>
<label name="party_category"/>
<field name="party_category"/>
<label name="line_product"/>
<field name="line_product"/>
<label name="incoterm_code"/>

View File

@@ -5,6 +5,7 @@
<field name="apply_on"/>
<field name="auto_apply"/>
<field name="party"/>
<field name="party_category"/>
<field name="line_product"/>
<field name="incoterm_code"/>
<field name="from_location"/>