Charter party
This commit is contained in:
@@ -1576,28 +1576,30 @@ class ShipmentIn(metaclass=PoolMeta):
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
def compute(cls, shipments):
|
||||
Sof = Pool().get('sof.statement')
|
||||
for sh in shipments:
|
||||
if sh.sof:
|
||||
for s in sh.sof:
|
||||
if s.laytime_clause_hour:
|
||||
t = s.laytime_clause_hour # instance de datetime.time
|
||||
delta = datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second)
|
||||
s.laytime_commenced = s.notice_of_readiness_time + delta
|
||||
Sof = Pool().get('sof.statement')
|
||||
for sh in shipments:
|
||||
if sh.sof:
|
||||
for s in sh.sof:
|
||||
if s.notice_of_readiness_time:
|
||||
s.laytime_commenced = (
|
||||
s.notice_of_readiness_time
|
||||
+ s._applied_turn_delta())
|
||||
quantity = float(s.quantity or sh.get_quantity() or 0)
|
||||
s.laytime_allowed = round(quantity / s.pumping_rate_o,2)
|
||||
s.laytime_completed = s.hoses_disconnected
|
||||
#s.laytime_completed = s.laytime_commenced + datetime.timedelta(hours=s.laytime_allowed)
|
||||
total_time = (s.laytime_completed - s.laytime_commenced).total_seconds() / 3600.0
|
||||
logger.info("COMPUTE_DEDUCTION3:%s",total_time)
|
||||
s.actual_time_used = round(total_time,2)
|
||||
s.deductions = round(sum([e.duration for e in s.sof_events if e.deductible == True]),2)
|
||||
effective_time = round(total_time - s.deductions,2)
|
||||
s.laytime_used = effective_time
|
||||
s.laytime_balance = round(s.laytime_allowed - s.laytime_used,2)
|
||||
penalty = Decimal(s.laytime_balance) * (s.demurrage_rate_o / 24)
|
||||
s.compensation_amount = round(penalty, 2)
|
||||
Sof.save([s])
|
||||
s.laytime_allowed = s._applied_laytime_allowed(quantity)
|
||||
s.laytime_completed = s.hoses_disconnected
|
||||
#s.laytime_completed = s.laytime_commenced + datetime.timedelta(hours=s.laytime_allowed)
|
||||
if not s.laytime_completed or s.laytime_allowed is None:
|
||||
Sof.save([s])
|
||||
continue
|
||||
total_time = (s.laytime_completed - s.laytime_commenced).total_seconds() / 3600.0
|
||||
logger.info("COMPUTE_DEDUCTION3:%s",total_time)
|
||||
s.actual_time_used = round(total_time,2)
|
||||
s.deductions = round(sum([e.duration for e in s.sof_events if e.deductible == True]),2)
|
||||
effective_time = round(total_time - s.deductions,2)
|
||||
s.laytime_used = effective_time
|
||||
s.laytime_balance = round(s.laytime_allowed - s.laytime_used,2)
|
||||
s.compensation_amount = s._applied_compensation_amount()
|
||||
Sof.save([s])
|
||||
|
||||
def get_vessel_url(self):
|
||||
return f"https://www.vesselfinder.com/en/vessels/VOS-TRAVELLER-IMO-{self.vessel.vessel_imo}"
|
||||
@@ -1944,6 +1946,8 @@ class StatementOfFacts(ModelSQL, ModelView):
|
||||
timebar_day = fields.Integer("TimeBar")
|
||||
timebar_warn = fields.Integer("Warning TimeBar")
|
||||
charter_party = fields.Many2One('stock.charter.party', "Charter Party")
|
||||
applied_condition = fields.Many2One(
|
||||
'charter.condition', "Applied Condition")
|
||||
owner_charter_conditions = fields.Function(
|
||||
fields.One2Many('charter.condition', '', "Owner Conditions"),
|
||||
'get_owner_charter_conditions')
|
||||
@@ -1978,15 +1982,27 @@ class StatementOfFacts(ModelSQL, ModelView):
|
||||
('despatch', 'Despatch'),
|
||||
(None, 'No Compensation'),
|
||||
], 'Compensation Type')
|
||||
compensation_amount = fields.Numeric('Compensation Amount ($)', readonly=True, digits=(16, 2))
|
||||
compensation_amount = fields.Numeric('Compensation Amount ($)', readonly=True, digits=(16, 2))
|
||||
quantity = fields.Function(fields.Float("Quantity loaded"),'get_qt')
|
||||
|
||||
notes = fields.Text('Remarks / Notes')
|
||||
|
||||
@fields.depends('shipment', 'charter_party')
|
||||
@fields.depends('shipment', 'charter_party', 'applied_condition')
|
||||
def on_change_shipment(self):
|
||||
if self.shipment and self.shipment.charter_party and not self.charter_party:
|
||||
self.charter_party = self.shipment.charter_party
|
||||
self._set_default_applied_condition()
|
||||
|
||||
@fields.depends('charter_party', 'applied_condition')
|
||||
def on_change_charter_party(self):
|
||||
self._set_default_applied_condition()
|
||||
|
||||
def _set_default_applied_condition(self):
|
||||
if self.applied_condition:
|
||||
return
|
||||
conditions = getattr(self.charter_party, 'conditions', None) or []
|
||||
if len(conditions) == 1:
|
||||
self.applied_condition = conditions[0]
|
||||
|
||||
def get_owner_charter_conditions(self, name=None):
|
||||
if self.charter_party:
|
||||
@@ -2005,6 +2021,76 @@ class StatementOfFacts(ModelSQL, ModelView):
|
||||
return self.shipment.get_sale_charter_conditions(name)
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def _rate_category(rate):
|
||||
rate_type = getattr(rate, 'rate_type', None)
|
||||
category = getattr(rate_type, 'category', None)
|
||||
if category:
|
||||
return category
|
||||
label = ' '.join(filter(None, [
|
||||
getattr(rate_type, 'name', None),
|
||||
getattr(rate, 'description', None),
|
||||
])).lower()
|
||||
for category in ('demurrage', 'despatch', 'pumping'):
|
||||
if category in label:
|
||||
return category
|
||||
|
||||
def _condition_rate(self, category):
|
||||
condition = getattr(self, 'applied_condition', None)
|
||||
for rate in getattr(condition, 'rates', None) or []:
|
||||
if self._rate_category(rate) == category and rate.rate is not None:
|
||||
return Decimal(str(rate.rate))
|
||||
|
||||
def _applied_laytime_allowed(self, quantity):
|
||||
condition = getattr(self, 'applied_condition', None)
|
||||
if condition and condition.laytime_allowed is not None:
|
||||
allowed = Decimal(str(condition.laytime_allowed))
|
||||
if condition.laytime_unit in {
|
||||
'days', 'wwd', 'wwd_shex', 'wwd_shinc'}:
|
||||
allowed *= Decimal(24)
|
||||
return float(allowed)
|
||||
|
||||
pumping_rate = self._condition_rate('pumping')
|
||||
if pumping_rate:
|
||||
return round(float(Decimal(str(quantity or 0)) / pumping_rate), 2)
|
||||
|
||||
legacy_pumping = self.pumping_rate_o or self.pumping_rate
|
||||
if legacy_pumping:
|
||||
return round(float(quantity or 0) / float(legacy_pumping), 2)
|
||||
|
||||
def _applied_turn_delta(self):
|
||||
condition = getattr(self, 'applied_condition', None)
|
||||
if condition and condition.turn_time is not None:
|
||||
hours = Decimal(str(condition.turn_time))
|
||||
if condition.turn_time_unit == 'days':
|
||||
hours *= Decimal(24)
|
||||
return datetime.timedelta(hours=float(hours))
|
||||
|
||||
if self.laytime_clause_hour:
|
||||
t = self.laytime_clause_hour
|
||||
elif self.laytime_clause_hour_o:
|
||||
t = self.laytime_clause_hour_o
|
||||
else:
|
||||
return datetime.timedelta(0)
|
||||
return datetime.timedelta(
|
||||
hours=t.hour, minutes=t.minute, seconds=t.second)
|
||||
|
||||
def _applied_compensation_amount(self):
|
||||
balance = Decimal(str(self.laytime_balance or 0))
|
||||
if balance < 0:
|
||||
self.compensation_type = 'demurrage'
|
||||
rate = self._condition_rate('demurrage')
|
||||
if rate is None:
|
||||
rate = Decimal(str(self.demurrage_rate_o or self.demurrage_rate or 0))
|
||||
return round(balance * rate / Decimal(24), 2)
|
||||
if balance > 0:
|
||||
rate = self._condition_rate('despatch')
|
||||
if rate is not None:
|
||||
self.compensation_type = 'despatch'
|
||||
return round(balance * rate / Decimal(24), 2)
|
||||
self.compensation_type = None
|
||||
return Decimal(0)
|
||||
|
||||
def get_qt(self,name):
|
||||
if self.shipment:
|
||||
return self.shipment.get_quantity()
|
||||
|
||||
@@ -150,6 +150,30 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
|
||||
self.assertEqual(rate.basis, 'per_day')
|
||||
|
||||
@with_transaction()
|
||||
def test_sof_calculation_reads_applied_condition_rates(self):
|
||||
'sof demurrage calculation uses the selected charter condition rates'
|
||||
Sof = Pool().get('sof.statement')
|
||||
sof = Sof()
|
||||
sof.applied_condition = Mock(
|
||||
laytime_allowed=None,
|
||||
turn_time=Decimal('6'),
|
||||
turn_time_unit='hours',
|
||||
rates=[
|
||||
Mock(rate=Decimal('300'), rate_type=Mock(category='pumping')),
|
||||
Mock(rate=Decimal('24000'), rate_type=Mock(category='demurrage')),
|
||||
],
|
||||
)
|
||||
sof.laytime_balance = -Decimal('12')
|
||||
|
||||
self.assertEqual(
|
||||
sof._applied_turn_delta(), datetime.timedelta(hours=6))
|
||||
self.assertEqual(
|
||||
sof._applied_laytime_allowed(Decimal('600')), 2.0)
|
||||
self.assertEqual(
|
||||
sof._applied_compensation_amount(), Decimal('-12000.00'))
|
||||
self.assertEqual(sof.compensation_type, 'demurrage')
|
||||
|
||||
@with_transaction()
|
||||
def test_add_physical_lot_defaults_hidden_premium_and_chunk_key(self):
|
||||
'add physical lot works when hidden tree fields are not loaded'
|
||||
|
||||
@@ -4,17 +4,8 @@
|
||||
<label name="charter_party"/>
|
||||
<field name="charter_party"/>
|
||||
<newline/>
|
||||
<notebook colspan="4">
|
||||
<page string="Owner" id="owner_conditions">
|
||||
<field name="owner_charter_conditions" colspan="4" mode="tree,form" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
|
||||
</page>
|
||||
<page string="Supplier" id="supplier_conditions">
|
||||
<field name="purchase_charter_conditions" colspan="4" mode="tree,form" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
|
||||
</page>
|
||||
<page string="Customer" id="customer_conditions">
|
||||
<field name="sale_charter_conditions" colspan="4" mode="tree,form" view_ids="purchase_trade.charter_condition_view_tree,purchase_trade.charter_condition_view_form"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<label name="applied_condition"/>
|
||||
<field name="applied_condition"/>
|
||||
<newline/>
|
||||
<separator string="TimeBar" id="tb"/>
|
||||
<newline/>
|
||||
|
||||
Reference in New Issue
Block a user