Add manual pricing
This commit is contained in:
@@ -325,20 +325,20 @@ class Component(ModelSQL, ModelView):
|
||||
|
||||
super(Component, cls).delete(components)
|
||||
|
||||
class Pricing(ModelSQL,ModelView):
|
||||
"Pricing"
|
||||
__name__ = 'pricing.pricing'
|
||||
|
||||
pricing_date = fields.Date("Date")
|
||||
price_component = fields.Many2One('pricing.component', "Component")#, domain=[('id', 'in', Eval('line.price_components'))], ondelete='CASCADE')
|
||||
quantity = fields.Numeric("Qt",digits='unit')
|
||||
settl_price = fields.Numeric("Settl. price",digits='unit')
|
||||
fixed_qt = fields.Numeric("Fixed qt",digits='unit',readonly=True)
|
||||
fixed_qt_price = fields.Numeric("Fixed qt price",digits='unit',readonly=True)
|
||||
unfixed_qt = fields.Numeric("Unfixed qt",digits='unit',readonly=True)
|
||||
unfixed_qt_price = fields.Numeric("Unfixed qt price",digits='unit',readonly=True)
|
||||
eod_price = fields.Numeric("EOD price",digits='unit',readonly=True)
|
||||
last = fields.Boolean("Last")
|
||||
class Pricing(ModelSQL,ModelView):
|
||||
"Pricing"
|
||||
__name__ = 'pricing.pricing'
|
||||
|
||||
pricing_date = fields.Date("Date")
|
||||
price_component = fields.Many2One('pricing.component', "Component")#, domain=[('id', 'in', Eval('line.price_components'))], ondelete='CASCADE')
|
||||
quantity = fields.Numeric("Qt",digits='unit')
|
||||
settl_price = fields.Numeric("Settl. price",digits='unit')
|
||||
fixed_qt = fields.Numeric("Fixed qt",digits='unit')
|
||||
fixed_qt_price = fields.Numeric("Fixed qt price",digits='unit')
|
||||
unfixed_qt = fields.Numeric("Unfixed qt",digits='unit')
|
||||
unfixed_qt_price = fields.Numeric("Unfixed qt price",digits='unit')
|
||||
eod_price = fields.Numeric("EOD price",digits='unit',readonly=True)
|
||||
last = fields.Boolean("Last")
|
||||
|
||||
@classmethod
|
||||
def default_fixed_qt(cls):
|
||||
@@ -364,13 +364,86 @@ class Pricing(ModelSQL,ModelView):
|
||||
def default_settl_price(cls):
|
||||
return Decimal(0)
|
||||
|
||||
@classmethod
|
||||
def default_eod_price(cls):
|
||||
return Decimal(0)
|
||||
|
||||
def get_fixed_price(self):
|
||||
price = Decimal(0)
|
||||
Pricing = Pool().get('pricing.pricing')
|
||||
@classmethod
|
||||
def default_eod_price(cls):
|
||||
return Decimal(0)
|
||||
|
||||
@staticmethod
|
||||
def _weighted_average_price(fixed_qt, fixed_price, unfixed_qt, unfixed_price):
|
||||
fixed_qt = Decimal(str(fixed_qt or 0))
|
||||
fixed_price = Decimal(str(fixed_price or 0))
|
||||
unfixed_qt = Decimal(str(unfixed_qt or 0))
|
||||
unfixed_price = Decimal(str(unfixed_price or 0))
|
||||
total_qty = fixed_qt + unfixed_qt
|
||||
if total_qty == 0:
|
||||
return Decimal(0)
|
||||
return round(
|
||||
((fixed_qt * fixed_price) + (unfixed_qt * unfixed_price)) / total_qty,
|
||||
4,
|
||||
)
|
||||
|
||||
def compute_eod_price(self):
|
||||
if getattr(self, 'sale_line', None) and hasattr(self, 'get_eod_price_sale'):
|
||||
return self.get_eod_price_sale()
|
||||
if getattr(self, 'line', None) and hasattr(self, 'get_eod_price_purchase'):
|
||||
return self.get_eod_price_purchase()
|
||||
return self._weighted_average_price(
|
||||
self.fixed_qt,
|
||||
self.fixed_qt_price,
|
||||
self.unfixed_qt,
|
||||
self.unfixed_qt_price,
|
||||
)
|
||||
|
||||
@fields.depends('fixed_qt', 'fixed_qt_price', 'unfixed_qt', 'unfixed_qt_price')
|
||||
def on_change_fixed_qt(self):
|
||||
self.eod_price = self.compute_eod_price()
|
||||
|
||||
@fields.depends('fixed_qt', 'fixed_qt_price', 'unfixed_qt', 'unfixed_qt_price')
|
||||
def on_change_fixed_qt_price(self):
|
||||
self.eod_price = self.compute_eod_price()
|
||||
|
||||
@fields.depends('fixed_qt', 'fixed_qt_price', 'unfixed_qt', 'unfixed_qt_price')
|
||||
def on_change_unfixed_qt(self):
|
||||
self.eod_price = self.compute_eod_price()
|
||||
|
||||
@fields.depends('fixed_qt', 'fixed_qt_price', 'unfixed_qt', 'unfixed_qt_price')
|
||||
def on_change_unfixed_qt_price(self):
|
||||
self.eod_price = self.compute_eod_price()
|
||||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
records = super(Pricing, cls).create(vlist)
|
||||
cls._sync_eod_price(records)
|
||||
return records
|
||||
|
||||
@classmethod
|
||||
def write(cls, *args):
|
||||
super(Pricing, cls).write(*args)
|
||||
if Transaction().context.get('skip_pricing_eod_sync'):
|
||||
return
|
||||
records = []
|
||||
actions = iter(args)
|
||||
for record_set, values in zip(actions, actions):
|
||||
if values:
|
||||
records.extend(record_set)
|
||||
cls._sync_eod_price(records)
|
||||
|
||||
@classmethod
|
||||
def _sync_eod_price(cls, records):
|
||||
if not records:
|
||||
return
|
||||
with Transaction().set_context(skip_pricing_eod_sync=True):
|
||||
for record in records:
|
||||
eod_price = record.compute_eod_price()
|
||||
if Decimal(str(record.eod_price or 0)) == Decimal(str(eod_price or 0)):
|
||||
continue
|
||||
super(Pricing, cls).write([record], {
|
||||
'eod_price': eod_price,
|
||||
})
|
||||
|
||||
def get_fixed_price(self):
|
||||
price = Decimal(0)
|
||||
Pricing = Pool().get('pricing.pricing')
|
||||
pricings = Pricing.search(['price_component','=',self.price_component.id],order=[('pricing_date', 'ASC')])
|
||||
if pricings:
|
||||
cumul_qt = Decimal(0)
|
||||
|
||||
@@ -155,14 +155,17 @@ class Pricing(metaclass=PoolMeta):
|
||||
line = fields.Many2One('purchase.line',"Lines")
|
||||
unit = fields.Function(fields.Many2One('product.uom',"Unit"),'get_unit_purchase')
|
||||
|
||||
def get_unit_purchase(self,name):
|
||||
if self.line:
|
||||
return self.line.unit
|
||||
|
||||
def get_eod_price_purchase(self):
|
||||
if self.line:
|
||||
return round((self.fixed_qt * self.fixed_qt_price + self.unfixed_qt * self.unfixed_qt_price)/Decimal(self.line.quantity_theorical),4)
|
||||
return Decimal(0)
|
||||
def get_unit_purchase(self,name):
|
||||
if self.line:
|
||||
return self.line.unit
|
||||
|
||||
def get_eod_price_purchase(self):
|
||||
return self._weighted_average_price(
|
||||
self.fixed_qt,
|
||||
self.fixed_qt_price,
|
||||
self.unfixed_qt,
|
||||
self.unfixed_qt_price,
|
||||
)
|
||||
|
||||
class Summary(ModelSQL,ModelView):
|
||||
"Pricing summary"
|
||||
|
||||
@@ -119,14 +119,17 @@ class Pricing(metaclass=PoolMeta):
|
||||
sale_line = fields.Many2One('sale.line',"Lines")
|
||||
unit = fields.Function(fields.Many2One('product.uom',"Unit"),'get_unit_sale')
|
||||
|
||||
def get_unit_sale(self,name):
|
||||
if self.sale_line:
|
||||
return self.sale_line.unit
|
||||
|
||||
def get_eod_price_sale(self):
|
||||
if self.sale_line:
|
||||
return round((self.fixed_qt * self.fixed_qt_price + self.unfixed_qt * self.unfixed_qt_price)/Decimal(self.sale_line.quantity),4)
|
||||
return Decimal(0)
|
||||
def get_unit_sale(self,name):
|
||||
if self.sale_line:
|
||||
return self.sale_line.unit
|
||||
|
||||
def get_eod_price_sale(self):
|
||||
return self._weighted_average_price(
|
||||
self.fixed_qt,
|
||||
self.fixed_qt_price,
|
||||
self.unfixed_qt,
|
||||
self.unfixed_qt_price,
|
||||
)
|
||||
|
||||
class Summary(ModelSQL,ModelView):
|
||||
"Pricing summary"
|
||||
|
||||
@@ -246,6 +246,50 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertEqual(pricing_model.save.call_args_list[0].args[0][0].unfixed_qt, Decimal('10'))
|
||||
self.assertEqual(pricing_model.save.call_args_list[1].args[0][0].unfixed_qt, Decimal('12'))
|
||||
|
||||
def test_pricing_manual_fields_are_editable_except_eod(self):
|
||||
'manual pricing rows can edit fixed and unfixed values while eod stays computed'
|
||||
Pricing = Pool().get('pricing.pricing')
|
||||
|
||||
self.assertFalse(Pricing.fixed_qt.readonly)
|
||||
self.assertFalse(Pricing.fixed_qt_price.readonly)
|
||||
self.assertFalse(Pricing.unfixed_qt.readonly)
|
||||
self.assertFalse(Pricing.unfixed_qt_price.readonly)
|
||||
self.assertTrue(Pricing.eod_price.readonly)
|
||||
|
||||
def test_pricing_eod_uses_weighted_average_for_manual_rows(self):
|
||||
'manual pricing eod uses the weighted average of fixed and unfixed legs'
|
||||
Pricing = Pool().get('pricing.pricing')
|
||||
|
||||
pricing = Pricing()
|
||||
pricing.fixed_qt = Decimal('4')
|
||||
pricing.fixed_qt_price = Decimal('100')
|
||||
pricing.unfixed_qt = Decimal('6')
|
||||
pricing.unfixed_qt_price = Decimal('110')
|
||||
|
||||
self.assertEqual(pricing.compute_eod_price(), Decimal('106.0000'))
|
||||
|
||||
def test_sale_and_purchase_eod_use_same_weighted_formula(self):
|
||||
'auto sale/purchase eod helpers use the same weighted average formula'
|
||||
Pricing = Pool().get('pricing.pricing')
|
||||
|
||||
sale_pricing = Pricing()
|
||||
sale_pricing.sale_line = Mock(quantity=Decimal('999'))
|
||||
sale_pricing.fixed_qt = Decimal('4')
|
||||
sale_pricing.fixed_qt_price = Decimal('100')
|
||||
sale_pricing.unfixed_qt = Decimal('6')
|
||||
sale_pricing.unfixed_qt_price = Decimal('110')
|
||||
|
||||
purchase_pricing = Pricing()
|
||||
purchase_pricing.line = Mock(quantity_theorical=Decimal('999'))
|
||||
purchase_pricing.fixed_qt = Decimal('4')
|
||||
purchase_pricing.fixed_qt_price = Decimal('100')
|
||||
purchase_pricing.unfixed_qt = Decimal('6')
|
||||
purchase_pricing.unfixed_qt_price = Decimal('110')
|
||||
|
||||
self.assertEqual(sale_pricing.get_eod_price_sale(), Decimal('106.0000'))
|
||||
self.assertEqual(
|
||||
purchase_pricing.get_eod_price_purchase(), Decimal('106.0000'))
|
||||
|
||||
def test_sale_and_purchase_trader_operator_domains_use_explicit_categories(self):
|
||||
'sale and purchase trader/operator fields are filtered by TRADER/OPERATOR categories'
|
||||
Sale = Pool().get('sale.sale')
|
||||
|
||||
Reference in New Issue
Block a user