Commit 59a0cc6b authored by Nicolas Dumazet's avatar Nicolas Dumazet

extend getInventoryAssetPrice to compute asset prices

Fifo, Filo, WeightedAverage, MonthlyWeightedAverage and MovingAverage methods
are available


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@36624 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent f0f8aa03
...@@ -1209,6 +1209,7 @@ class SimulationTool(BaseTool): ...@@ -1209,6 +1209,7 @@ class SimulationTool(BaseTool):
date = inventory_date_line_dict['date'] date = inventory_date_line_dict['date']
non_date_value_dict = dict([(k, v) for k, v \ non_date_value_dict = dict([(k, v) for k, v \
in inventory_date_line_dict.iteritems() if k != 'date']) in inventory_date_line_dict.iteritems() if k != 'date'])
equal_date_query_list.append( equal_date_query_list.append(
ComplexQuery( ComplexQuery(
ComplexQuery(operator='AND', ComplexQuery(operator='AND',
...@@ -1579,25 +1580,60 @@ class SimulationTool(BaseTool): ...@@ -1579,25 +1580,60 @@ class SimulationTool(BaseTool):
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getInventoryAssetPrice') 'getInventoryAssetPrice')
def getInventoryAssetPrice(self, src__=0, def getInventoryAssetPrice(self, src__=0,
simulation_period='', **kw): simulation_period='',
valuation_method=None,
**kw):
""" """
Same thing as getInventory but returns an asset Same thing as getInventory but returns an asset
price rather than an inventory. price rather than an inventory.
If valuation method is None, returns the sum of total prices.
Else it should be a string, in:
Filo
Fifo
WeightedAverage
MonthlyWeightedAverage
MovingAverage
When using a specific valuation method, a resource_uid is expected
as well as one of (section_uid or node_uid).
""" """
if valuation_method is None:
method = getattr(self,'get%sInventoryList' % simulation_period) method = getattr(self,'get%sInventoryList' % simulation_period)
kw['ignore_group_by'] = 1 kw['ignore_group_by'] = 1
result = method( src__=src__, inventory_list=0, **kw) result = method( src__=src__, inventory_list=0, **kw)
if src__ : if src__ :
return result return result
if len(result) == 0:
return 0.0
total_result = 0.0 total_result = 0.0
if len(result) > 0:
for result_line in result: for result_line in result:
if result_line.total_price is not None: if result_line.total_price is not None:
total_result += result_line.total_price total_result += result_line.total_price
return total_result return total_result
if valuation_method not in ('Fifo', 'Filo', 'WeightedAverage',
'MonthlyWeightedAverage', 'MovingAverage'):
raise ValueError("Invalid valuation method: %s" % valuation_method)
assert 'node_uid' in kw or 'section_uid' in kw
sql_kw = self._generateSQLKeywordDict(**kw)
if 'section_uid' in kw:
# ignore internal movements
sql_kw['where_expression'] += ' AND ' \
'stock.section_uid!=stock.mirror_section_uid'
result = self.Resource_zGetAssetPrice(
valuation_method=valuation_method,
**sql_kw)
if len(result) > 0:
return result[-1].total_asset_price
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getCurrentInventoryAssetPrice') 'getCurrentInventoryAssetPrice')
def getCurrentInventoryAssetPrice(self, **kw): def getCurrentInventoryAssetPrice(self, **kw):
......
...@@ -1038,6 +1038,114 @@ class TestInventoryList(InventoryAPITestCase): ...@@ -1038,6 +1038,114 @@ class TestInventoryList(InventoryAPITestCase):
checkInventory(line=3, type='Future', source=1, quantity=-9) checkInventory(line=3, type='Future', source=1, quantity=-9)
checkInventory(line=3, type='Future', destination=1, quantity=9) checkInventory(line=3, type='Future', destination=1, quantity=9)
def test_inventory_asset_price(self):
# examples from http://accountinginfo.com/study/inventory/inventory-120.htm
movement_list = [
(1, "Beginning Inventory", -700, 10),
(3, "Purchase", -100, 12),
(8, "Sale", 500, None),
(15, "Purchase", -600, 14),
(19, "Purchase", -200, 15),
(25, "Sale", 400, None),
(27, "Sale", 100, None),
]
resource = self.getProductModule().newContent(
title='My resource',
portal_type='Product')
for m in movement_list:
self._makeMovement(resource_value=resource,
source_value=self.node,
destination_value=self.mirror_node,
start_date=DateTime('2000/1/%d 12:00 UTC' % m[0]),
title=m[1],
quantity=m[2],
price=m[3],
)
self._safeTic()
simulation_tool = self.getSimulationTool()
def valuate(method):
r = simulation_tool.getInventoryAssetPrice(
valuation_method=method,
resource_uid=resource.getUid(),
node_uid=self.node.getUid())
return round(r)
self.assertEquals(7895, valuate("MovingAverage"))
self.assertEquals(7200, valuate("Filo"))
self.assertEquals(8600, valuate("Fifo"))
def test_weighted_average_asset_price(self):
def h(quantity, total_price):
"""
A small helper. Returns a dictionary
"""
d = dict(quantity=quantity,
price=float(total_price)/quantity,
total_price=total_price)
return d
# one item per month:
# - movement_list: quantity is negative, it's incoming/purchase
# - after: quantity, total_price, and expected unit_price
# Data was extracted from existing ledger books
data = {
'2009/11':
dict(movement_list=[h(566, 259208),], after=h(566, 259208),),
'2009/12':
dict(movement_list=[h(600, 291600), h(-1135, 536164), ],
after=h(31, 14644)),
'2010/01':
dict(movement_list=[h(1200, 583200), ], after=h(1231, 597844)),
'2010/02':
dict(movement_list=[h(200, 97200), h(-1265, 614417), ],
after=h(166, 80627)),
'2010/03':
dict(movement_list=[], after=h(166, 80627)),
'2010/04':
dict(movement_list=[h(600, 291600), h(-680, 330437), ],
after=h(86, 41791)),
'2010/05':
dict(movement_list=[], after=h(86, 41791)),
'2010/06':
dict(movement_list=[], after=h(86, 41791)),
'2010/07':
dict(movement_list=[], after=h(86, 41791)),
'2010/08':
dict(movement_list=[h(4400, 2032800), h(-4364, 2018170), ],
after=h(122, 56420)),
'2010/09':
dict(movement_list=[], after=h(122, 56420)),
'2010/10':
dict(movement_list=[], after=h(122, 56420)),
'2010/11':
dict(movement_list=[h(1400, 646800), h(-1357, 626984), h(4, 1848)],
after=h(169, 78084)),
}
resource = self._makeProduct(title="Product for weighted average test")
resource_uid = resource.getUid()
# create all movements
for month, value in data.iteritems():
for mov in value['movement_list']:
d = DateTime('%s/15 15:00 UTC' % month)
self._makeMovement(start_date=d, resource_uid=resource_uid, **mov)
self._safeTic()
# and check
for cur in sorted(data)[1:]:
# month+1
to_date = DateTime("%s/1" % cur) + 31
result = self.getSimulationTool().getInventoryAssetPrice(
valuation_method="MonthlyWeightedAverage",
to_date=to_date,
resource_uid=resource.getUid(),
node_uid=self.node.getUid())
self.assertTrue(result is not None)
self.assertEquals(data[cur]['after']['total_price'], round(result))
class TestMovementHistoryList(InventoryAPITestCase): class TestMovementHistoryList(InventoryAPITestCase):
"""Tests Movement history list methods. """Tests Movement history list methods.
...@@ -2460,6 +2568,7 @@ class TestInventoryDocument(InventoryAPITestCase): ...@@ -2460,6 +2568,7 @@ class TestInventoryDocument(InventoryAPITestCase):
optimisation__=False, optimisation__=False,
mirror_uid=self.mirror_node.getUid())]) mirror_uid=self.mirror_node.getUid())])
class BaseTestUnitConversion(InventoryAPITestCase): class BaseTestUnitConversion(InventoryAPITestCase):
QUANTITY_UNIT_DICT = {} QUANTITY_UNIT_DICT = {}
METRIC_TYPE_CATEGORY_LIST = () METRIC_TYPE_CATEGORY_LIST = ()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment