Fee currency
This commit is contained in:
@@ -39,7 +39,7 @@ class Fee(ModelSQL,ModelView):
|
||||
shipment_in = fields.Many2One('stock.shipment.in')
|
||||
shipment_out = fields.Many2One('stock.shipment.out')
|
||||
shipment_internal = fields.Many2One('stock.shipment.internal')
|
||||
currency = fields.Many2One('currency.currency',"Currency")
|
||||
currency = fields.Many2One('currency.currency',"Currency", required=True)
|
||||
supplier = fields.Many2One('party.party',"Supplier", required=True)
|
||||
type = fields.Selection([
|
||||
('budgeted', 'Budgeted'),
|
||||
@@ -125,21 +125,21 @@ class Fee(ModelSQL,ModelView):
|
||||
logger.info("ON_CHANGE_WITH_LINE:%s",line)
|
||||
if not line:
|
||||
line = self.sale_line
|
||||
if line:
|
||||
if line.lots:
|
||||
qt = sum([e.get_current_quantity_converted(0,self.unit) for e in line.lots])
|
||||
qt_ = sum([e.get_current_quantity_converted(0) for e in line.lots])
|
||||
unit = line.lots[0].lot_unit
|
||||
logger.info("ON_CHANGE_WITH_QT0:%s",qt)
|
||||
if line:
|
||||
if line.lots:
|
||||
qt = sum([e.get_current_quantity_converted(0,self.unit) for e in line.lots])
|
||||
qt_ = sum([e.get_current_quantity_converted(0) for e in line.lots])
|
||||
unit = line.lots[0].lot_unit or line.lots[0].lot_unit_line
|
||||
logger.info("ON_CHANGE_WITH_QT0:%s",qt)
|
||||
logger.info("ON_CHANGE_WITH_SI:%s",self.shipment_in)
|
||||
if self.shipment_in:
|
||||
Lot = Pool().get('lot.lot')
|
||||
lots = Lot.search([('lot_shipment_in','=',self.shipment_in.id)])
|
||||
logger.info("ON_CHANGE_WITH_LOTS:%s",lots)
|
||||
if lots:
|
||||
qt = sum([e.get_current_quantity_converted(0,self.unit) for e in lots])
|
||||
qt_ = sum([e.get_current_quantity_converted(0) for e in lots])
|
||||
unit = lots[0].lot_unit
|
||||
if lots:
|
||||
qt = sum([e.get_current_quantity_converted(0,self.unit) for e in lots])
|
||||
qt_ = sum([e.get_current_quantity_converted(0) for e in lots])
|
||||
unit = lots[0].lot_unit or lots[0].lot_unit_line
|
||||
if not qt:
|
||||
logger.info("ON_CHANGE_WITH_QT1:%s",qt)
|
||||
LotQt = Pool().get('lot.qt')
|
||||
|
||||
@@ -2571,8 +2571,8 @@ class LotUnmatch(Wizard):
|
||||
def end(self):
|
||||
return 'reload'
|
||||
|
||||
class LotUnship(Wizard):
|
||||
"Unship"
|
||||
class LotUnship(Wizard):
|
||||
"Unlink transport"
|
||||
__name__ = "lot.unship"
|
||||
|
||||
start = StateTransition()
|
||||
|
||||
@@ -344,7 +344,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</record>
|
||||
|
||||
<record model="ir.action.wizard" id="act_unship">
|
||||
<field name="name">🚢 Unship</field>
|
||||
<field name="name">🚢 Unlink transport</field>
|
||||
<field name="wiz_name">lot.unship</field>
|
||||
<field name="model">lot.report</field>
|
||||
</record>
|
||||
|
||||
@@ -1296,7 +1296,8 @@ class ShipmentIn(metaclass=PoolMeta):
|
||||
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
|
||||
s.laytime_allowed = round(s.quantity / s.pumping_rate_o,2)
|
||||
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
|
||||
@@ -1333,9 +1334,11 @@ class ShipmentIn(metaclass=PoolMeta):
|
||||
keys = [m.lot.lot_chunk_key for m in self.incoming_moves if m.lot]
|
||||
return ",".join(map(str, keys)) if keys else None
|
||||
|
||||
def get_quantity(self,name=None):
|
||||
if self.incoming_moves:
|
||||
return sum([(e.quantity if e.quantity else 0) for e in self.incoming_moves])
|
||||
def get_quantity(self,name=None):
|
||||
if self.incoming_moves:
|
||||
return sum([(e.quantity if e.quantity else 0) for e in self.incoming_moves])
|
||||
if self.lotqt:
|
||||
return sum([(e.lot_quantity if e.lot_quantity else 0) for e in self.lotqt])
|
||||
|
||||
def get_bales(self,name=None):
|
||||
Lot = Pool().get('lot.lot')
|
||||
@@ -1626,9 +1629,9 @@ class StatementOfFacts(ModelSQL, ModelView):
|
||||
|
||||
notes = fields.Text('Remarks / Notes')
|
||||
|
||||
def get_qt(self,name):
|
||||
if self.shipment.incoming_moves:
|
||||
return sum([e.quantity for e in self.shipment.incoming_moves])
|
||||
def get_qt(self,name):
|
||||
if self.shipment:
|
||||
return self.shipment.get_quantity()
|
||||
|
||||
class SoFEvent(ModelSQL, ModelView):
|
||||
"Event from Statement of Facts"
|
||||
|
||||
@@ -11,6 +11,8 @@ from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
from trytond.exceptions import UserError
|
||||
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 stock as stock_module
|
||||
from trytond.modules.purchase_trade import fee as fee_module
|
||||
from trytond.modules.purchase_trade.service import ContractFactory
|
||||
|
||||
|
||||
@@ -812,6 +814,57 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
|
||||
self.assertEqual(report.get_shipment_type(None), 'dropship')
|
||||
|
||||
@with_transaction()
|
||||
def test_shipment_in_quantity_uses_lotqt_without_physical_moves(self):
|
||||
'shipment quantity falls back to linked open lots before physical moves'
|
||||
ShipmentIn = Pool().get('stock.shipment.in')
|
||||
shipment = ShipmentIn()
|
||||
shipment.incoming_moves = []
|
||||
shipment.lotqt = [
|
||||
Mock(lot_quantity=Decimal('100.50')),
|
||||
Mock(lot_quantity=Decimal('20.25')),
|
||||
]
|
||||
|
||||
self.assertEqual(shipment.get_quantity(), Decimal('120.75'))
|
||||
|
||||
def test_sof_quantity_uses_shipment_quantity_fallback(self):
|
||||
'demurrage quantity uses shipment quantity including linked open lots'
|
||||
sof = stock_module.StatementOfFacts()
|
||||
sof.shipment = Mock(get_quantity=Mock(return_value=Decimal('442')))
|
||||
|
||||
self.assertEqual(sof.get_qt(None), Decimal('442'))
|
||||
|
||||
def test_fee_ppack_auto_uses_lot_unit_line_when_lot_unit_missing(self):
|
||||
'per packing auto quantity falls back to lot_unit_line'
|
||||
bale = Mock(factor=Decimal('1'))
|
||||
pack = Mock(factor=Decimal('10'))
|
||||
lot = Mock(lot_unit=None, lot_unit_line=bale)
|
||||
lot.get_current_quantity_converted.side_effect = (
|
||||
lambda state=0, unit=None: Decimal('25')
|
||||
if unit else Decimal('250'))
|
||||
fee = fee_module.Fee()
|
||||
fee.line = Mock(lots=[lot])
|
||||
fee.sale_line = None
|
||||
fee.shipment_in = None
|
||||
fee.mode = 'ppack'
|
||||
fee.auto_calculation = True
|
||||
fee.unit = pack
|
||||
|
||||
self.assertEqual(fee.on_change_with_quantity(), Decimal('25'))
|
||||
|
||||
def test_fee_currency_is_required(self):
|
||||
'fee currency is required for every fee mode'
|
||||
self.assertTrue(fee_module.Fee.currency.required)
|
||||
|
||||
def test_valuation_rejects_fee_without_currency_cleanly(self):
|
||||
'pnl generation reports a missing fee currency before currency conversion'
|
||||
fee = Mock(currency=None, product=Mock(name='Maritime freight'))
|
||||
|
||||
with self.assertRaises(UserError) as cm:
|
||||
valuation_module.ValuationBase._check_fee_required_fields(fee)
|
||||
|
||||
self.assertIn('Currency is required on fee', str(cm.exception))
|
||||
|
||||
def test_purchase_line_default_pricing_rule_comes_from_configuration(self):
|
||||
'purchase line pricing_rule defaults to the purchase_trade singleton value'
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
|
||||
@@ -723,6 +723,15 @@ class ValuationBase(ModelSQL):
|
||||
2)
|
||||
return Decimal(0)
|
||||
return cls._fee_amount_or_zero(fee)
|
||||
|
||||
@staticmethod
|
||||
def _check_fee_required_fields(fee):
|
||||
if not getattr(fee, 'currency', None):
|
||||
product = getattr(getattr(fee, 'product', None), 'rec_name', None)
|
||||
product = product or getattr(getattr(fee, 'product', None), 'name', None)
|
||||
if product:
|
||||
raise UserError("Currency is required on fee %s." % product)
|
||||
raise UserError("Currency is required on fee.")
|
||||
|
||||
@classmethod
|
||||
def create_pnl_fee_from_line(cls, line):
|
||||
@@ -758,6 +767,7 @@ class ValuationBase(ModelSQL):
|
||||
or e.fee.is_effective_fee_lot(lot))
|
||||
]
|
||||
for sf in cls.group_fees_by_type_supplier(line, fees):
|
||||
cls._check_fee_required_fields(sf)
|
||||
sign = -1 if sf.p_r == 'pay' else 1
|
||||
qty = round(lot.get_current_quantity_converted(), 5)
|
||||
if sf.mode == 'ppack' and getattr(lot, 'lot_qt', None):
|
||||
@@ -825,6 +835,7 @@ class ValuationBase(ModelSQL):
|
||||
or e.fee.is_effective_fee_lot(lot))
|
||||
]
|
||||
for sf in cls.group_fees_by_type_supplier(sale_line, fees):
|
||||
cls._check_fee_required_fields(sf)
|
||||
sign = -1 if sf.p_r == 'pay' else 1
|
||||
qty = round(lot.get_current_quantity_converted(), 5)
|
||||
if sf.mode == 'ppack' and getattr(lot, 'lot_qt', None):
|
||||
|
||||
Reference in New Issue
Block a user