Commit d496e223 authored by Sebastien Robin's avatar Sebastien Robin

Inventory: add lowest_value_test for getInventoryAssetPrice

parent 169711ad
......@@ -9,7 +9,7 @@ during each period.
Then perform a weighted average over all periods.
*/
set @total_asset_price=0, @total_quantity=0
set @total_asset_price=0.0, @total_quantity=0.0
<dtml-var sql_delimiter>
select
byperiod.*,
......@@ -45,19 +45,28 @@ order by d_year<dtml-if "'Monthly' in valuation_method">, d_month</dtml-if>
Very similar to (Monthly)WeightedAverage except that we do not have to
split the timeframe / fold movements and simply perform a weighted average
on all single movements.
Parameter lowest_value_test compares latest price and Moving average price and takes the
smallest one.
*/
set @total_asset_price=0, @total_quantity=0
SET @total_asset_price=0, @total_quantity=0, @latest_price=null, @running_total_asset_price=0
<dtml-var sql_delimiter>
select
(@incoming_total_price:=IF(quantity>0, total_price, 0)) as incoming_total_price,
@latest_price:=IF(quantity>0, total_price/quantity, @latest_price) as latest_price,
@unit_price:=((@total_asset_price+@incoming_total_price)/(@total_quantity+GREATEST(0, quantity))) as unit_price,
(@total_asset_price:=
@total_asset_price +
(@running_total_asset_price:=
@running_total_asset_price +
@incoming_total_price +
LEAST(0, quantity) * @unit_price) as total_asset_price,
(@total_quantity:=@total_quantity+quantity) as dummy
LEAST(0, quantity) * @unit_price) as running_total_asset_price,
(@total_quantity:=@total_quantity+quantity) as dummy,
<dtml-if "lowest_value_test">
(@total_asset_price:=LEAST(@running_total_asset_price, @total_quantity*@latest_price)) as total_asset_price
<dtml-else>
(@total_asset_price:= @running_total_asset_price) as total_asset_price
</dtml-if>
from
stock, catalog
where
......@@ -93,6 +102,9 @@ Thus, each movement has a value of:
if @unbalanced_output is initialized to @total_output_quantity and reduced by
quantity at each step:
unbalanced_output=max(0, unbalanced_output-quantity)
Parameter lowest_value_test compares latest price and FIFO price and takes the
smallest one.
*/
SET
@unbalanced_output:=
......@@ -105,15 +117,26 @@ SET
AND
<dtml-var where_expression>
),0),
@total_asset_price=0
@total_asset_price=0.0,
@running_total_asset_price=0.0,
@running_quantity=0.0
<dtml-var sql_delimiter>
SELECT
(@total_asset_price:=@total_asset_price +
(@running_quantity:=@running_quantity +
GREATEST(0, quantity-@unbalanced_output)
) AS running_quantity,
(@running_total_asset_price:=@running_total_asset_price +
GREATEST(0, (quantity-@unbalanced_output) * total_price/quantity)
) AS total_asset_price,
(@unbalanced_output:=GREATEST(0, @unbalanced_output-quantity)) as dummy
) AS running_total_asset_price,
(@unbalanced_output:=GREATEST(0, @unbalanced_output-quantity)) as dummy,
<dtml-if "lowest_value_test">
(@total_asset_price:=LEAST(@running_total_asset_price,
@running_quantity * total_price/quantity)) as total_asset_price
<dtml-else>
(@total_asset_price:= @running_total_asset_price) as total_asset_price
</dtml-if>
FROM
stock, catalog
......@@ -148,15 +171,26 @@ until we reach an incoming movement. Then:
movement got out of inventory between t=current and T=END. These items are not
present in the final inventory and can be discarded.
@unbalanced_inventory=@unbalanced_inventory - quantity
Parameter lowest_value_test compares latest price and FILO price and takes the
smallest one.
*/
SET @unbalanced_output=0, @total_asset_price=0
SET @unbalanced_output=0.0, @total_asset_price=0.0, @running_total_asset_price=0.0, @latest_price=null, @running_quantity=0.0
<dtml-var sql_delimiter>
SELECT
(@total_asset_price:=@total_asset_price +
(IF(quantity <= 0, @latest_price, @latest_price:=IFNULL(@latest_price, total_price/quantity))) as dummy_latest_price,
(@running_total_asset_price:=@running_total_asset_price +
IF(quantity <= 0, 0,
total_price/quantity * GREATEST(0, quantity-@unbalanced_output))) as total_asset_price,
(@unbalanced_output:=GREATEST(0, @unbalanced_output-quantity)) as dummy
total_price/quantity * GREATEST(0, quantity-@unbalanced_output))) as running_total_asset_price,
(@unbalanced_output:=GREATEST(0, @unbalanced_output-quantity)) as dummy,
(@running_quantity:=@running_quantity + quantity) as running_quantity,
<dtml-if "lowest_value_test">
(@total_asset_price:=LEAST(@running_total_asset_price, @running_quantity*@latest_price)) as total_asset_price
<dtml-else>
(@total_asset_price:= @running_total_asset_price) as total_asset_price
</dtml-if>
FROM
stock, catalog
WHERE
......
......@@ -9,7 +9,9 @@
<item>
<key> <string>arguments_src</string> </key>
<value> <string>where_expression\n
valuation_method</string> </value>
valuation_method\n
lowest_value_test\n
</string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
......
......@@ -1821,6 +1821,7 @@ class SimulationTool(BaseTool):
def getInventoryAssetPrice(self, src__=0,
simulation_period='',
valuation_method=None,
lowest_value_test=False,
**kw):
"""
Same thing as getInventory but returns an asset
......@@ -1836,6 +1837,10 @@ class SimulationTool(BaseTool):
MovingAverage
When using a specific valuation method, a resource_uid is expected
as well as one of (section_uid or node_uid).
Parameter lowest_value_price compares latest price with price
calculated with valuation_method and takes the smallest one. This
is useful for accountants to avoid overestimating the price of a stock.
"""
if valuation_method is None:
method = getattr(self,'get%sInventoryList' % simulation_period)
......@@ -1857,6 +1862,9 @@ class SimulationTool(BaseTool):
if valuation_method not in ('Fifo', 'Filo', 'WeightedAverage',
'MonthlyWeightedAverage', 'MovingAverage'):
raise ValueError("Invalid valuation method: %s" % valuation_method)
if lowest_value_test and valuation_method not in ('Fifo', 'Filo',
'MovingAverage'):
raise NotImplementedError('lowest_value_test not implemented')
assert 'node_uid' in kw or 'section_uid' in kw
sql_kw = self._generateSQLKeywordDict(**kw)
......@@ -1868,6 +1876,7 @@ class SimulationTool(BaseTool):
result = self.Resource_zGetAssetPrice(
valuation_method=valuation_method,
lowest_value_test=lowest_value_test,
src__=src__,
**sql_kw)
......@@ -1875,7 +1884,10 @@ class SimulationTool(BaseTool):
return result
if len(result) > 0:
return result[-1].total_asset_price
asset_price = result[-1].total_asset_price
if asset_price:
asset_price = float(asset_price)
return asset_price
security.declareProtected(Permissions.AccessContentsInformation,
'getCurrentInventoryAssetPrice')
......
......@@ -1516,18 +1516,20 @@ class TestInventoryList(InventoryAPITestCase):
def test_inventory_asset_price(self):
# examples from http://accountinginfo.com/study/inventory/inventory-120.htm
# # total quantity
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),
(1, "Beginning Inventory", -700, 10), # 700
(3, "Purchase", -100, 12), # 800
(8, "Sale", 500, None), # 300
(15, "Purchase", -600, 14), # 900
(19, "Purchase", -200, 15), # 1100
(25, "Sale", 400, None), # 700
(27, "Sale", 100, None), # 600
]
resource = self.getProductModule().newContent(
title='My resource',
portal_type='Product')
def makeMovementList(movement_list):
for m in movement_list:
self._makeMovement(resource_value=resource,
source_value=self.node,
......@@ -1537,19 +1539,51 @@ class TestInventoryList(InventoryAPITestCase):
quantity=m[2],
price=m[3],
)
makeMovementList(movement_list)
simulation_tool = self.getSimulationTool()
def valuate(method):
def valuate(method, lowest_value_test=False):
self.portal.person_module.log(simulation_tool.getInventoryAssetPrice(
src__=1,
valuation_method=method,
resource_uid=resource.getUid(),
node_uid=self.node.getUid(),
lowest_value_test=lowest_value_test))
r = simulation_tool.getInventoryAssetPrice(
valuation_method=method,
resource_uid=resource.getUid(),
node_uid=self.node.getUid())
node_uid=self.node.getUid(),
lowest_value_test=lowest_value_test)
return round(r)
self.assertEqual(7895, valuate("MovingAverage"))
self.assertEqual(7200, valuate("Filo"))
self.assertEqual(8600, valuate("Fifo"))
# latest purchase price is 15, total quantity is 600
# average price of 13.15, thus lowest value test change nothing
self.assertEqual(7895, valuate("MovingAverage", lowest_value_test=True))
# average price of 12.15, thus lowest value test change nothing
self.assertEqual(7200, valuate("Filo", lowest_value_test=True))
# average price of 14.33, thus lowest value test change nothing
self.assertEqual(8600, valuate("Fifo", lowest_value_test=True))
movement_list = [
(28, "Purchase", -100, 11), # 700
(29, "Sale", 100, None), # 600
]
makeMovementList(movement_list)
self.assertEqual(7710, valuate("MovingAverage"))
self.assertEqual(7200, valuate("Filo"))
self.assertEqual(8300, valuate("Fifo"))
self.assertEqual(7710, valuate("MovingAverage"))
self.assertEqual(7200, valuate("Filo"))
# latest purchase price is 11, total quantity is 600
# average price of 12.85, thus lowest value test change value
self.assertEqual(6600, valuate("MovingAverage", lowest_value_test=True))
# average price of 12, thus lowest value test change value
self.assertEqual(6600, valuate("Filo", lowest_value_test=True))
# average price of 13.83, thus lowest value test change value
self.assertEqual(6600, valuate("Fifo", lowest_value_test=True))
def test_weighted_average_asset_price(self):
def h(quantity, total_price):
......
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