Commit d05c9406 authored by Jérome Perrin's avatar Jérome Perrin

Inventory API now supports a precision= keyword argument to round values to a...

Inventory API now supports a precision= keyword argument to round values to a given precision. In that case, it will return the sum of rounded values; sum(round(stock.quantity, precision))



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@11764 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent da2cb872
......@@ -451,7 +451,8 @@ class SimulationTool (BaseTool):
if kw.get('group_by_mirror_node',0):
group_by_expression_list.append('%s.mirror_node_uid' % table)
if len(group_by_expression_list):
group_by_expression_list.append('%s.resource_uid' % table) # Always group by resource
# Always group by resource
group_by_expression_list.append('%s.resource_uid' % table)
sql_kw['group_by_expression'] = ', '.join(group_by_expression_list)
sql_kw.update(self.portal_catalog.buildSQLQuery(**new_kw))
......@@ -463,7 +464,8 @@ class SimulationTool (BaseTool):
'getInventory')
def getInventory(self, src__=0, ignore_variation=0, standardise=0,
omit_simulation=0, omit_input=0, omit_output=0,
selection_domain=None, selection_report=None, **kw):
selection_domain=None, selection_report=None,
precision=None, **kw):
"""
Returns an inventory of a single or multiple resources on a single or
multiple nodes as a single float value
......@@ -513,8 +515,8 @@ class SimulationTool (BaseTool):
section_filter - only take rows in stock table which section_uid
matches section_filter
mirror_section_filter - only take rows in stock table which mirror_section_uid
matches mirror_section_filter
mirror_section_filter - only take rows in stock table which
mirror_section_uid matches mirror_section_filter
variation_text - only take rows in stock table with specified
variation_text.
......@@ -568,6 +570,8 @@ class SimulationTool (BaseTool):
group_by_sub_variation - (useless on getInventory, but useful on
getInventoryList)
precision - the precision used to round quantities and prices.
**kw - if we want extended selection with more keywords (but
bad performance) check what we can do with
buildSqlQuery
......@@ -591,7 +595,7 @@ class SimulationTool (BaseTool):
standardise=standardise, omit_simulation=omit_simulation,
omit_input=omit_input, omit_output=omit_output,
selection_domain=selection_domain, selection_report=selection_report,
**sql_kw)
precision=precision, **sql_kw)
if src__:
return result
......@@ -640,7 +644,8 @@ class SimulationTool (BaseTool):
'getInventoryList')
def getInventoryList(self, src__=0, ignore_variation=0, standardise=0,
omit_simulation=0, omit_input=0, omit_output=0,
selection_domain=None, selection_report=None, **kw):
selection_domain=None, selection_report=None,
precision=None, **kw):
"""
Returns a list of inventories for a single or multiple
resources on a single or multiple nodes, grouped by resource,
......@@ -656,7 +661,8 @@ class SimulationTool (BaseTool):
standardise=standardise, omit_simulation=omit_simulation,
omit_input=omit_input, omit_output=omit_output,
selection_domain=selection_domain,
selection_report=selection_report, **sql_kw)
selection_report=selection_report, precision=precision,
**sql_kw)
security.declareProtected(Permissions.AccessContentsInformation,
'getCurrentInventoryList')
......@@ -694,7 +700,8 @@ class SimulationTool (BaseTool):
'getInventoryStat')
def getInventoryStat(self, src__=0, ignore_variation=0, standardise=0,
omit_simulation=0, omit_input=0, omit_output=0,
selection_domain=None, selection_report=None, **kw):
selection_domain=None, selection_report=None,
precision=None, **kw):
"""
getInventoryStat is the pending to getInventoryList in order to
provide statistics on getInventoryList lines in ListBox such as:
......@@ -708,7 +715,8 @@ class SimulationTool (BaseTool):
standardise=standardise, omit_simulation=omit_simulation,
omit_input=omit_input, omit_output=omit_output,
selection_domain=selection_domain,
selection_report=selection_report, **sql_kw)
selection_report=selection_report,
precision=precision, **sql_kw)
return result
security.declareProtected(Permissions.AccessContentsInformation,
......@@ -786,16 +794,10 @@ class SimulationTool (BaseTool):
def getInventoryAssetPrice(self, src__=0, ignore_variation=0,
standardise=0, omit_simulation=0, omit_input=0,
omit_output=0, selection_domain=None,
selection_report=None, **kw):
selection_report=None, precision=None, **kw):
"""
Same thing as getInventory but returns an asset
price rather than an inventory.
TODO:
- Make sure getInventoryAssetPrice API can
support precision defition (ie. calculate the
sum of rounded values)
"""
sql_kw = self._generateSQLKeywordDict(**kw)
result = self.Resource_zGetInventory(
......@@ -803,7 +805,7 @@ class SimulationTool (BaseTool):
standardise=standardise, omit_simulation=omit_simulation,
omit_input=omit_input, omit_output=omit_output,
selection_domain=selection_domain, selection_report=selection_report,
**sql_kw)
precision=precision, **sql_kw)
if src__ :
return result
......@@ -853,7 +855,7 @@ class SimulationTool (BaseTool):
def getInventoryHistoryList(self, src__=0, ignore_variation=0,
standardise=0, omit_simulation=0, omit_input=0,
omit_output=0, selection_domain=None,
selection_report=None, **kw):
selection_report=None, precision=None, **kw):
"""
Returns a time based serie of inventory values
for a single or a group of resource, node, section, etc. This is useful
......@@ -869,7 +871,8 @@ class SimulationTool (BaseTool):
standardise=standardise, omit_simulation=omit_simulation,
omit_input=omit_input, omit_output=omit_output,
selection_domain=selection_domain,
selection_report=selection_report, **sql_kw)
selection_report=selection_report, precision=precision,
**sql_kw)
security.declareProtected(Permissions.AccessContentsInformation,
'getInventoryHistoryChart')
......@@ -877,7 +880,7 @@ class SimulationTool (BaseTool):
standardise=0, omit_simulation=0,
omit_input=0, omit_output=0,
selection_domain=None,
selection_report=None, **kw):
selection_report=None, precision=None, **kw):
"""
getInventoryHistoryChart is the pensing to getInventoryHistoryList
to ease the rendering of time based graphs which show the evolution
......@@ -892,7 +895,8 @@ class SimulationTool (BaseTool):
standardise=standardise, omit_simulation=omit_simulation,
omit_input=omit_input, omit_output=omit_output,
selection_domain=selection_domain,
selection_report=selection_report, **sql_kw)
selection_report=selection_report, precision=precision,
**sql_kw)
security.declareProtected(Permissions.AccessContentsInformation,
'getMovementHistoryList')
......@@ -901,7 +905,7 @@ class SimulationTool (BaseTool):
omit_input=0, omit_output=0,
selection_domain=None, selection_report=None,
initial_running_total_quantity=0,
initial_running_total_price=0,
initial_running_total_price=0, precision=None,
**kw):
"""Returns a list of movements which modify the inventory
for a single or a group of resource, node, section, etc.
......@@ -921,14 +925,14 @@ class SimulationTool (BaseTool):
initial_running_total_quantity,
initial_running_total_price=
initial_running_total_price,
**sql_kw)
precision=precision, **sql_kw)
security.declareProtected(Permissions.AccessContentsInformation,
'getMovementHistoryStat')
def getMovementHistoryStat(self, src__=0, ignore_variation=0,
standardise=0, omit_simulation=0, omit_input=0,
omit_output=0, selection_domain=None,
selection_report=None, **kw):
selection_report=None, precision=None, **kw):
"""
getMovementHistoryStat is the pending to getMovementHistoryList
for ListBox stat
......@@ -938,7 +942,7 @@ class SimulationTool (BaseTool):
ignore_variation=ignore_variation, standardise=standardise,
omit_simulation=omit_simulation, omit_input=omit_input,
omit_output=omit_output, selection_domain=selection_domain,
selection_report=selection_report, **sql_kw)
selection_report=selection_report, precision=precision, **sql_kw)
security.declareProtected(Permissions.AccessContentsInformation, 'getNextNegativeInventoryDate')
def getNextNegativeInventoryDate(self, src__=0,
......
......@@ -101,6 +101,12 @@
</dictionary>
</value>
</item>
<item>
<key> <string>precision</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>section_filtered</string> </key>
<value>
......@@ -138,20 +144,21 @@
<key> <string>_keys</string> </key>
<value>
<list>
<string>from_table_list</string>
<string>where_expression</string>
<string>order_by_expression</string>
<string>selection_domain</string>
<string>selection_report</string>
<string>ignore_variation</string>
<string>standardize</string>
<string>omit_simulation</string>
<string>omit_input</string>
<string>omit_output</string>
<string>section_filtered</string>
<string>input_simulation_state</string>
<string>output_simulation_state</string>
<string>group_by_expression</string>
<string>from_table_list</string>
<string>where_expression</string>
<string>order_by_expression</string>
<string>selection_domain</string>
<string>selection_report</string>
<string>ignore_variation</string>
<string>standardize</string>
<string>omit_simulation</string>
<string>omit_input</string>
<string>omit_output</string>
<string>section_filtered</string>
<string>input_simulation_state</string>
<string>output_simulation_state</string>
<string>group_by_expression</string>
<string>precision</string>
</list>
</value>
</item>
......@@ -181,7 +188,8 @@ omit_output\r\n
section_filtered\r\n
input_simulation_state:list\r\n
output_simulation_state:list\r\n
group_by_expression</string> </value>
group_by_expression\r\n
precision</string> </value>
</item>
<item>
<key> <string>cache_time_</string> </key>
......@@ -222,9 +230,15 @@ group_by_expression</string> </value>
<value> <string encoding="cdata"><![CDATA[
SELECT\n
SUM(ROUND(stock.quantity,2)) AS inventory,\n
SUM(ROUND(stock.quantity,2)) AS total_quantity,\n
SUM(ROUND(stock.total_price,2)) AS total_price,\n
<dtml-if expr="precision is not None">\n
SUM(ROUND(stock.quantity, <dtml-var precision>)) AS inventory,\n
SUM(ROUND(stock.quantity, <dtml-var precision>)) AS total_quantity,\n
SUM(ROUND(stock.total_price, <dtml-var precision>)) AS total_price,\n
<dtml-else>\n
SUM(stock.quantity) AS inventory,\n
SUM(stock.quantity) AS total_quantity,\n
SUM(stock.total_price) AS total_price,\n
</dtml-if>\n
COUNT(DISTINCT node.title) AS node_title,\n
COUNT(DISTINCT node.relative_url) AS node_relative_url,\n
COUNT(DISTINCT section.title) AS section_title,\n
......@@ -353,9 +367,15 @@ ORDER BY\n
<value> <string encoding="cdata"><![CDATA[
SELECT\n
SUM(ROUND(stock.quantity,2)) AS inventory,\n
SUM(ROUND(stock.quantity,2)) AS total_quantity,\n
SUM(ROUND(stock.total_price,2)) AS total_price,\n
<dtml-if expr="precision is not None">\n
SUM(ROUND(stock.quantity, <dtml-var precision>)) AS inventory,\n
SUM(ROUND(stock.quantity, <dtml-var precision>)) AS total_quantity,\n
SUM(ROUND(stock.total_price, <dtml-var precision>)) AS total_price,\n
<dtml-else>\n
SUM(stock.quantity) AS inventory,\n
SUM(stock.quantity) AS total_quantity,\n
SUM(stock.total_price) AS total_price,\n
</dtml-if>\n
COUNT(DISTINCT node.title) AS node_title,\n
COUNT(DISTINCT node.relative_url) AS node_relative_url,\n
COUNT(DISTINCT section.title) AS section_title,\n
......
......@@ -113,6 +113,12 @@
</dictionary>
</value>
</item>
<item>
<key> <string>precision</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>section_filtered</string> </key>
<value>
......@@ -150,22 +156,23 @@
<key> <string>_keys</string> </key>
<value>
<list>
<string>from_table_list</string>
<string>where_expression</string>
<string>order_by_expression</string>
<string>group_by_expression</string>
<string>selection_domain</string>
<string>selection_report</string>
<string>ignore_variation</string>
<string>standardize</string>
<string>omit_simulation</string>
<string>omit_input</string>
<string>omit_output</string>
<string>section_filtered</string>
<string>initial_running_total_quantity</string>
<string>initial_running_total_price</string>
<string>input_simulation_state</string>
<string>output_simulation_state</string>
<string>from_table_list</string>
<string>where_expression</string>
<string>order_by_expression</string>
<string>group_by_expression</string>
<string>selection_domain</string>
<string>selection_report</string>
<string>ignore_variation</string>
<string>standardize</string>
<string>omit_simulation</string>
<string>omit_input</string>
<string>omit_output</string>
<string>section_filtered</string>
<string>initial_running_total_quantity</string>
<string>initial_running_total_price</string>
<string>input_simulation_state</string>
<string>output_simulation_state</string>
<string>precision</string>
</list>
</value>
</item>
......@@ -606,7 +613,8 @@ section_filtered\r\n
initial_running_total_quantity\r\n
initial_running_total_price\r\n
input_simulation_state:list\r\n
output_simulation_state:list</string> </value>
output_simulation_state:list\r\n
precision</string> </value>
</item>
<item>
<key> <string>cache_time_</string> </key>
......@@ -662,8 +670,13 @@ SELECT\n
catalog.uid as uid,\n
catalog.relative_url as relative_url,\n
stock.date AS date,\n
<dtml-if expr="precision is not None">\n
ROUND(stock.quantity, <dtml-var precision>) AS total_quantity,\n
ROUND(stock.total_price, <dtml-var precision>) AS total_price,\n
<dtml-else>\n
stock.quantity AS total_quantity,\n
stock.total_price AS total_price,\n
</dtml-if>\n
stock.variation_text AS variation_text,\n
stock.simulation_state AS simulation_state,\n
stock.mirror_section_uid AS mirror_section_uid,\n
......@@ -814,8 +827,13 @@ SELECT\n
catalog.uid as uid,\n
catalog.relative_url as relative_url,\n
stock.date AS date,\n
<dtml-if expr="precision is not None">\n
ROUND(stock.quantity, <dtml-var precision>) AS total_quantity,\n
ROUND(stock.total_price, <dtml-var precision>) AS total_price,\n
<dtml-else>\n
stock.quantity AS total_quantity,\n
stock.total_price AS total_price,\n
</dtml-if>\n
stock.variation_text AS variation_text,\n
stock.simulation_state AS simulation_state,\n
stock.mirror_section_uid AS mirror_section_uid,\n
......
220
\ No newline at end of file
221
\ No newline at end of file
......@@ -73,7 +73,7 @@ class InventoryAPITestCase(ERP5TypeTestCase):
self.portal = self.getPortal()
if not hasattr(self.portal, 'testing_folder'):
self.portal.newContent(portal_type='Folder',
id='testing_folder')
id='testing_folder')
self.folder = self.portal.testing_folder
self.section = self._makeOrganisation(title='Section')
......@@ -472,6 +472,31 @@ class TestInventory(InventoryAPITestCase):
self.assertEquals(getInventory(
section_uid=self.section.getUid()), 100)
def testPrecision(self):
# getInventory supports a precision= argument to specify the precision to
# round
getInventory = self.getSimulationTool().getInventory
getInventoryAssetPrice = self.getSimulationTool().getInventoryAssetPrice
self._makeMovement( quantity=0.1234, price=1 )
self.assertAlmostEquals(0.123,
getInventory(precision=3, node_uid=self.node.getUid()),
places=3)
self.assertAlmostEquals(0.123,
getInventoryAssetPrice(precision=3, node_uid=self.node.getUid()),
places=3)
def testPrecisionAndFloatRoundingIssues(self):
# sum([0.1] * 10) != 1.0 but this is not a problem here
getInventory = self.getSimulationTool().getInventory
getInventoryAssetPrice = self.getSimulationTool().getInventoryAssetPrice
self._makeMovement( quantity=1, price=1 )
for i in range(10):
self._makeMovement( quantity=-0.1, price=1 )
self.assertEquals(0, getInventory(precision=2, node_uid=self.node.getUid()))
self.assertEquals(0, getInventoryAssetPrice(precision=2,
node_uid=self.node.getUid()))
class TestInventoryList(InventoryAPITestCase):
"""Tests getInventoryList methods.
"""
......@@ -927,6 +952,44 @@ class TestMovementHistoryList(InventoryAPITestCase):
node_uid=self.node.getUid(),)
self.assertEquals(2, len(mvt_history_list))
self.assertEquals(0, sum([r.total_quantity for r in mvt_history_list]))
def testPrecision(self):
# getMovementHistoryList supports a precision= argument to specify the
# precision to round
getMovementHistoryList = self.getSimulationTool().getMovementHistoryList
self._makeMovement( quantity=0.1234, price=1 )
mvt_history_list = getMovementHistoryList(
precision=2,
node_uid=self.node.getUid())
self.assertEquals(1, len(mvt_history_list))
self.assertEquals(0.12, mvt_history_list[0].running_total_quantity)
self.assertEquals(0.12, mvt_history_list[0].running_total_price)
self.assertEquals(0.12, mvt_history_list[0].total_quantity)
self.assertEquals(0.12, mvt_history_list[0].total_price)
mvt_history_list = getMovementHistoryList(
precision=3,
node_uid=self.node.getUid())
self.assertEquals(1, len(mvt_history_list))
self.assertEquals(0.123, mvt_history_list[0].running_total_quantity)
self.assertEquals(0.123, mvt_history_list[0].running_total_price)
self.assertEquals(0.123, mvt_history_list[0].total_quantity)
self.assertEquals(0.123, mvt_history_list[0].total_price)
def testPrecisionAndFloatRoundingIssues(self):
# sum([0.1] * 10) != 1.0 but this is not a problem here
getMovementHistoryList = self.getSimulationTool().getMovementHistoryList
date = DateTime()
self._makeMovement( quantity=1, price=1, start_date=date )
for i in range(10):
self._makeMovement( quantity=-0.1, price=1, start_date=date+i )
mvt_history_list = getMovementHistoryList(
precision=2,
node_uid=self.node.getUid(),
sort_on=[['stock.date', 'ASC']])
self.assertEquals(11, len(mvt_history_list))
self.assertEquals(0, mvt_history_list[-1].running_total_quantity)
self.assertEquals(0, mvt_history_list[-1].running_total_price)
class TestInventoryStat(InventoryAPITestCase):
"""Tests Inventory Stat methods.
......
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