Demurrage

This commit is contained in:
2026-06-03 08:17:49 +02:00
parent 4e7911b0ac
commit 671251d23e
11 changed files with 106 additions and 17 deletions

View File

@@ -31,6 +31,8 @@ canonique seulement quand elle est reprise dans une page thematique.
</li>
<li style="margin:0.38rem 0;">Les contrats Sale et Purchase ont un onglet <code>Charter Conditions</code> au niveau entete et ligne. Une ligne sans conditions propres herite des conditions de l&#x27;entete; des conditions de ligne remplacent l&#x27;heritage.
</li>
<li style="margin:0.38rem 0;">Les conditions creees depuis une Sale ou une Sale Line sont limitees au role <code>Customer</code>; celles creees depuis une Purchase ou Purchase Line sont limitees au role <code>Supplier</code>. Les conditions creees depuis une Charter Party ne peuvent pas utiliser les roles <code>Supplier</code> ou <code>Customer</code> afin d&#x27;eviter la confusion entre conditions armateur/charte et conditions contractuelles achat/vente.
</li>
<li style="margin:0.38rem 0;">Les shipments et SOF exposent les conditions Owner, Supplier et Customer retrouvees depuis le charter party et les lots/lignes physiques.
</li>
<li style="margin:0.38rem 0;">Le calcul SOF privilegie la condition appliquee: laytime allowed, turn time, pumping rate, rate demurrage et rate despatch. A defaut, il garde les anciens champs legacy demurrage/pumping.

View File

@@ -23,6 +23,12 @@ canonique seulement quand elle est reprise dans une page thematique.
- Les contrats Sale et Purchase ont un onglet `Charter Conditions` au
niveau entete et ligne. Une ligne sans conditions propres herite des
conditions de l'entete; des conditions de ligne remplacent l'heritage.
- Les conditions creees depuis une Sale ou une Sale Line sont limitees au
role `Customer`; celles creees depuis une Purchase ou Purchase Line sont
limitees au role `Supplier`. Les conditions creees depuis une
Charter Party ne peuvent pas utiliser les roles `Supplier` ou `Customer`
afin d'eviter la confusion entre conditions armateur/charte et conditions
contractuelles achat/vente.
- Les shipments et SOF exposent les conditions Owner, Supplier et Customer
retrouvees depuis le charter party et les lots/lignes physiques.
- Le calcul SOF privilegie la condition appliquee: laytime allowed,

View File

@@ -481,15 +481,7 @@ class CharterCondition(ModelSQL, ModelView):
"Charter Condition"
__name__ = 'charter.condition'
name = fields.Char("Name")
charter_party = fields.Many2One(
'stock.charter.party', "Charter Party", ondelete='CASCADE')
purchase = fields.Many2One(
'purchase.purchase', "Purchase", ondelete='CASCADE')
purchase_line = fields.Many2One(
'purchase.line', "Purchase Line", ondelete='CASCADE')
party = fields.Many2One('party.party', "Party")
party_role = fields.Selection([
_party_role_selection = [
(None, ''),
('owner', 'Owner'),
('charterer', 'Charterer'),
@@ -499,7 +491,18 @@ class CharterCondition(ModelSQL, ModelView):
('agent', 'Agent'),
('terminal', 'Terminal'),
('other', 'Other'),
], "Party Role")
]
name = fields.Char("Name")
charter_party = fields.Many2One(
'stock.charter.party', "Charter Party", ondelete='CASCADE')
purchase = fields.Many2One(
'purchase.purchase', "Purchase", ondelete='CASCADE')
purchase_line = fields.Many2One(
'purchase.line', "Purchase Line", ondelete='CASCADE')
party = fields.Many2One('party.party', "Party")
party_role = fields.Selection(
'get_party_roles', "Party Role")
responsibility = fields.Selection([
(None, ''),
('ours', 'Ours'),
@@ -546,6 +549,45 @@ class CharterCondition(ModelSQL, ModelView):
def default_all_time_saved():
return False
@staticmethod
def default_party_role():
role_context = Transaction().context.get('charter_condition_role')
if role_context in {'purchase', 'purchase_line', 'supplier'}:
return 'supplier'
if role_context in {'sale', 'sale_line', 'customer'}:
return 'customer'
if role_context == 'charter_party':
return 'owner'
@fields.depends(
'charter_party', 'purchase', 'purchase_line', 'sale', 'sale_line')
def get_party_roles(self):
role_context = Transaction().context.get('charter_condition_role')
if (role_context in {'purchase', 'purchase_line', 'supplier'}
or getattr(self, 'purchase', None)
or getattr(self, 'purchase_line', None)):
return [(None, ''), ('supplier', 'Supplier')]
if (role_context in {'sale', 'sale_line', 'customer'}
or getattr(self, 'sale', None)
or getattr(self, 'sale_line', None)):
return [(None, ''), ('customer', 'Customer')]
if role_context == 'charter_party' or getattr(
self, 'charter_party', None):
return [
item for item in self._party_role_selection
if item[0] not in {'supplier', 'customer'}]
return self._party_role_selection
@classmethod
def validate(cls, conditions):
super().validate(conditions)
for condition in conditions:
allowed_roles = {role for role, _ in condition.get_party_roles()}
if condition.party_role not in allowed_roles:
raise UserError(
"This charter condition party role is not allowed "
"for its source.")
class CharterParty(ModelSQL, ModelView):
"Charter Party"

View File

@@ -9,6 +9,7 @@ from trytond.pool import Pool
from trytond.pyson import Eval
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
from trytond.exceptions import UserError
from trytond.transaction import Transaction
from trytond.modules.purchase_trade import valuation as valuation_module
from trytond.modules.purchase_trade import lot as lot_module
from trytond.modules.purchase_trade import purchase as purchase_module
@@ -150,6 +151,44 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(rate.basis, 'per_day')
@with_transaction()
def test_charter_condition_role_defaults_from_context(self):
'charter condition role defaults to the business source context'
Condition = Pool().get('charter.condition')
with Transaction().set_context(charter_condition_role='sale'):
self.assertEqual(Condition.default_party_role(), 'customer')
condition = Condition()
self.assertEqual(
condition.get_party_roles(),
[(None, ''), ('customer', 'Customer')])
with Transaction().set_context(charter_condition_role='purchase'):
self.assertEqual(Condition.default_party_role(), 'supplier')
condition = Condition()
self.assertEqual(
condition.get_party_roles(),
[(None, ''), ('supplier', 'Supplier')])
with Transaction().set_context(charter_condition_role='charter_party'):
self.assertEqual(Condition.default_party_role(), 'owner')
condition = Condition()
roles = {role for role, _ in condition.get_party_roles()}
self.assertNotIn('supplier', roles)
self.assertNotIn('customer', roles)
@with_transaction()
def test_charter_condition_role_rejects_invalid_source_role(self):
'charter condition role is constrained by its source'
Condition = Pool().get('charter.condition')
condition = Condition()
condition.name = 'Invalid supplier condition on sale'
condition.party_role = 'supplier'
with Transaction().set_context(charter_condition_role='sale'):
with self.assertRaises(UserError):
Condition.validate([condition])
@with_transaction()
def test_sof_calculation_reads_applied_condition_rates(self):
'sof demurrage calculation uses the selected charter condition rates'

View File

@@ -44,7 +44,7 @@
<field name="laycan_to"/>
</page>
<page string="Conditions" id="conditions">
<field name="conditions" colspan="4" mode="tree,form" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
<field name="conditions" colspan="4" mode="tree,form" context="{'charter_condition_role': 'charter_party'}" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
</page>
<page string="Legal" col="4" id="legal">
<label name="governing_law"/>

View File

@@ -56,7 +56,7 @@ this repository contains the full copyright notices and license terms. -->
</xpath>
<xpath expr="/form/notebook/page[@id='purchase']" position="after">
<page string="Charter Conditions" id="charter_conditions">
<field name="charter_conditions" colspan="4" mode="tree,form" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
<field name="charter_conditions" colspan="4" mode="tree,form" context="{'charter_condition_role': 'purchase'}" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
</page>
<page string="Contract Clauses" col="4" id="contract_clauses">
<label name="contract_template"/>

View File

@@ -69,7 +69,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="premium_decomposition" colspan="4" mode="tree,form" view_ids="purchase_trade.premium_composition_view_tree,purchase_trade.premium_composition_view_form"/>
</page>
<page string="Charter Conditions" id="charter_conditions">
<field name="charter_conditions" colspan="4" mode="tree,form" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
<field name="charter_conditions" colspan="4" mode="tree,form" context="{'charter_condition_role': 'purchase_line'}" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
</page>
</xpath>
<xpath expr="/form/notebook/page[@id='taxes']" position="before">

View File

@@ -56,7 +56,7 @@ this repository contains the full copyright notices and license terms. -->
</xpath>
<xpath expr="/form/notebook/page[@id='sale']" position="after">
<page string="Charter Conditions" id="charter_conditions">
<field name="charter_conditions" colspan="4" mode="tree,form" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
<field name="charter_conditions" colspan="4" mode="tree,form" context="{'charter_condition_role': 'sale'}" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
</page>
<page string="Contract Clauses" col="4" id="contract_clauses">
<label name="contract_template"/>

View File

@@ -69,7 +69,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="premium_decomposition" colspan="4" mode="tree,form" view_ids="purchase_trade.premium_composition_view_tree,purchase_trade.premium_composition_view_form"/>
</page>
<page string="Charter Conditions" id="charter_conditions">
<field name="charter_conditions" colspan="4" mode="tree,form" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
<field name="charter_conditions" colspan="4" mode="tree,form" context="{'charter_condition_role': 'sale_line'}" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
</page>
</xpath>
<xpath expr="/form/notebook/page[@id='taxes']" position="before">

View File

@@ -123,7 +123,7 @@ this repository contains the full copyright notices and license terms. -->
</page>
</notebook>
<newline/>
<field name="sof" colspan="4" mode="form,tree" view_ids="purchase_trade.view_form_sof,purchase_trade.view_list_sof"/>
<field name="sof" colspan="4" mode="tree,form" view_ids="purchase_trade.view_list_sof,purchase_trade.view_form_sof"/>
<newline/>
<button name="compute" string="compute"/>
</page>

View File

@@ -38,7 +38,7 @@
<label name="sailing_time"/>
<field name="sailing_time"/>
<newline/>
<field name="sof_events" colspan="4" mode="tree" view_ids="purchase_trade.view_sof_event_tree"/>
<field name="sof_events" colspan="4" mode="tree,form" view_ids="purchase_trade.view_sof_event_tree,purchase_trade.view_sof_event_form"/>
</page>
<page string="Calculation" id="calcul">