Commit cc05c028 authored by Nicolas Dumazet's avatar Nicolas Dumazet

improve Quantity Unit Conversions (1/2)


* Deprecate usage of "quantity" float field in quantity_unit category
* Introduce Quantity Unit Conversion Module instead for this behavior:
     Quantity Unit Conversion Definitions can be used to define site-wide
     quantity_units
* Let Resources define their own children Definitions:
     - adds a new tab in Product view
     - the definitions defined at a Resource level override the "global"
       definitions made in the Units Module

* Definitions are indexed in a special catalog table, similarly to Measures.
* Add an interaction workflow to take care of two reindexation needs:
   - when a local (resource-level) Definition is modified/created, the Product
     itself needs to be reindexed. This step is quite similar to
     measure_interaction_workflow
   - when a global (site-wide) Definition is modified, all Products need
     reindexing.

* SimulationTool API was cleaned up and unified.
   - getConvertedInventoryList is deprecated
   - quantity_unit and metric_type parameters are added to getInventory

* testResource is updated to use Unit Definitions instead of quantity fields
  of quantity_unit's


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@31858 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent d48da780
...@@ -33,12 +33,6 @@ from Products.ERP5Type.XMLMatrix import XMLMatrix ...@@ -33,12 +33,6 @@ from Products.ERP5Type.XMLMatrix import XMLMatrix
from Products.ERP5.Variated import Variated from Products.ERP5.Variated import Variated
def getQuantity(quantity_unit_value):
quantity = quantity_unit_value.getProperty('quantity')
if quantity is not None:
return float(quantity)
class Measure(XMLMatrix): class Measure(XMLMatrix):
""" """
A Measure A Measure
...@@ -139,7 +133,7 @@ class Measure(XMLMatrix): ...@@ -139,7 +133,7 @@ class Measure(XMLMatrix):
metric_type = self.getMetricType() metric_type = self.getMetricType()
if quantity_unit is not None and metric_type and \ if quantity_unit is not None and metric_type and \
quantity_unit.getParentId() == metric_type.split('/', 1)[0]: quantity_unit.getParentId() == metric_type.split('/', 1)[0]:
return getQuantity(quantity_unit) return self.getQuantityUnitDefinitionRatio(quantity_unit)
security.declareProtected(AccessContentsInformation, 'getConvertedQuantity') security.declareProtected(AccessContentsInformation, 'getConvertedQuantity')
def getConvertedQuantity(self, variation_list=()): def getConvertedQuantity(self, variation_list=()):
...@@ -168,19 +162,27 @@ class Measure(XMLMatrix): ...@@ -168,19 +162,27 @@ class Measure(XMLMatrix):
# Cataloging. # Cataloging.
security.declareProtected(AccessContentsInformation, 'asCatalogRowList') security.declareProtected(AccessContentsInformation, 'asCatalogRowList')
def asCatalogRowList(self): def asCatalogRowList(self, quantity_unit_definition_dict):
""" """
Returns the list of rows to insert in the measure table of the catalog. Returns the list of rows to insert in the measure table of the catalog.
Called by Resource.getMeasureRowList. Called by Resource.getMeasureRowList.
""" """
quantity_unit = self.getConvertedQuantityUnit() quantity_unit = self.getQuantityUnitValue()
if not quantity_unit: metric_type = self.getMetricType()
if quantity_unit is None or not metric_type or \
quantity_unit.getParentId() != metric_type.split('/', 1)[0]:
return () return ()
quantity_unit_uid = quantity_unit.getUid()
uid = self.getUid() uid = self.getUid()
resource = self.getResourceValue() resource = self.getResourceValue()
resource_uid = resource.getUid() resource_uid = resource.getUid()
metric_type_uid = self.getMetricTypeUid() metric_type_uid = self.getMetricTypeUid()
quantity = self.getQuantity() quantity = self.getQuantity()
try:
quantity_unit = quantity_unit_definition_dict[quantity_unit_uid][1]
except KeyError:
return ()
# The only purpose of the defining a default measure explicitly is to # The only purpose of the defining a default measure explicitly is to
# set a specific metric_type for the management unit. # set a specific metric_type for the management unit.
...@@ -196,8 +198,12 @@ class Measure(XMLMatrix): ...@@ -196,8 +198,12 @@ class Measure(XMLMatrix):
# so we simply return 1 row. # so we simply return 1 row.
if quantity is not None: if quantity is not None:
quantity *= quantity_unit quantity *= quantity_unit
if (not default or quantity == management_unit_uid = resource.getQuantityUnitUid()
getQuantity(resource.getQuantityUnitValue())): management_unit_quantity = None
if management_unit_uid is not None:
management_unit_quantity = quantity_unit_definition_dict\
[management_unit_uid][1]
if (not default or quantity == management_unit_quantity):
return (uid, resource_uid, '^', metric_type_uid, quantity), return (uid, resource_uid, '^', metric_type_uid, quantity),
return () return ()
......
##############################################################################
#
# Copyright (c) 2008 Nexedi SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.Permissions import AccessContentsInformation
from Products.ERP5Type.XMLObject import XMLObject
class QuantityUnitConversionDefinition(XMLObject):
"""
Quantity Unit Conversion Definition
"""
meta_type = 'ERP5 Quantity Unit Conversion Definition'
portal_type = 'Quantity Unit Conversion Definition'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(AccessContentsInformation)
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.CategoryCore
, PropertySheet.Amount
)
security.declareProtected(Permissions.AccessContentsInformation,
'getTitle')
def getTitle(self):
"""
Set a title that includes the Quantity Unit
"""
default_title = self._baseGetTitle()
quantity_unit = self.getQuantityUnitValue()
if quantity_unit is not None:
# only the last part is relevant
return "%s Definition" % quantity_unit.getTitle()
return default_title
##############################################################################
#
# Copyright (c) 2008 Nexedi SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.Permissions import AccessContentsInformation
from Products.ERP5Type.XMLObject import XMLObject
class QuantityUnitConversionGroup(XMLObject):
"""
Quantity Unit Conversion Group defined per-product.
"""
meta_type = 'ERP5 Quantity Unit Conversion Group'
portal_type = 'Quantity Unit Conversion Group'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(AccessContentsInformation)
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.CategoryCore
, PropertySheet.Amount
)
security.declareProtected(Permissions.AccessContentsInformation,
'getTitle')
def getTitle(self):
"""
Set a title that includes the Quantity Unit
"""
default_title = self._baseGetTitle()
quantity_unit = self.getQuantityUnitValue()
if quantity_unit is not None:
return "%s Conversion Group" % quantity_unit.getParentValue().getTitle()
return default_title
...@@ -794,12 +794,12 @@ class Resource(XMLMatrix, Variated): ...@@ -794,12 +794,12 @@ class Resource(XMLMatrix, Variated):
if management_unit == quantity_unit: if management_unit == quantity_unit:
return 1.0 return 1.0
traverse = self.portal_categories['quantity_unit'].unrestrictedTraverse traverse = self.portal_categories['quantity_unit'].unrestrictedTraverse
quantity = float(traverse(quantity_unit).getProperty('quantity')) quantity = self.getQuantityUnitDefinitionRatio(traverse(quantity_unit))
if quantity_unit.split('/', 1)[0] != management_unit.split('/', 1)[0]: if quantity_unit.split('/', 1)[0] != management_unit.split('/', 1)[0]:
measure = self.getDefaultMeasure(quantity_unit) measure = self.getDefaultMeasure(quantity_unit)
quantity /= measure.getConvertedQuantity(variation_list) quantity /= measure.getConvertedQuantity(variation_list)
else: else:
quantity /= float(traverse(management_unit).getProperty('quantity')) quantity /= self.getQuantityUnitDefinitionRatio(traverse(management_unit))
return quantity return quantity
# Unit conversion # Unit conversion
...@@ -852,6 +852,88 @@ class Resource(XMLMatrix, Variated): ...@@ -852,6 +852,88 @@ class Resource(XMLMatrix, Variated):
if len(result) == 1: if len(result) == 1:
return result[0] return result[0]
def _getGlobalQuantityUnitDefinitionDict(self):
# XXX this info could be cached, as it is the same for all Resources
result = {}
module = self.getPortalObject().quantity_unit_conversion_module
for definition_list in module.objectValues(portal_type= \
'Quantity Unit Conversion Group'):
standard_quantity_unit_uid = definition_list.getQuantityUnitUid()
if standard_quantity_unit_uid is None:
continue
result[standard_quantity_unit_uid] = (None, 1.0)
for definition in definition_list.objectValues(portal_type= \
'Quantity Unit Conversion Definition'):
unit_uid = definition.getQuantityUnitUid()
if unit_uid is None:
continue
quantity = definition.getQuantity()
if not quantity:
continue
result[unit_uid] = (definition.getUid(), quantity)
return result
def _getQuantityUnitDefinitionDict(self):
"""
Returns a dictionary representing the Unit Definitions that hold
for the current resource.
Keys: quantity_unit categories uids.
Values: tuple (unit_definition_uid, quantity)
* unit_definition_uid can be None if the quantity_unit is defined
as a standard_quantity_unit (no Unit Conversion Definition defines
it, its definition comes from a Unit Conversion Group)
* quantity is a float, an amount, expressed in the
standard_quantity_unit for the base category of the quantity_unit.
For example, if mass/g is the global standard quantity_unit, all
definitions for mass/* will be expressed in grams.
"""
global_definition_dict = self._getGlobalQuantityUnitDefinitionDict()
result = global_definition_dict.copy()
for definition_list in self.objectValues(portal_type= \
'Quantity Unit Conversion Group'):
standard_quantity_unit_uid = definition_list.getQuantityUnitUid()
if standard_quantity_unit_uid is None:
continue
reference_ratio = global_definition_dict[standard_quantity_unit_uid][1]
for definition in definition_list.objectValues(portal_type= \
'Quantity Unit Conversion Definition'):
unit_uid = definition.getQuantityUnitUid()
if unit_uid is None:
continue
quantity = definition.getQuantity()
if not quantity:
continue
result[unit_uid] = (definition.getUid(), quantity*reference_ratio)
return result
security.declareProtected(Permissions.AccessContentsInformation,
'getQuantityUnitConversionDefinitionRowList')
def getQuantityUnitConversionDefinitionRowList(self):
"""
Returns a list rows to insert in the quantity_unit_conversion table.
Used by z_catalog_quantity_unit_conversion_list.
"""
# XXX If one wanted to add variation-specific Unit Conversion Definitions
# he could use an approach very similar to the one used for Measure.
# Just add a variation VARCHAR column in quantity_unit_conversion table
# (defaulting as "^"). The column would contain the REGEX describing the
# variation, exactly as Measure.
# Resource_zGetInventoryList would then need expansion to match the
# product variation vs the quantity_unit_conversion REGEX.
uid = self.getUid()
row_list = []
for unit_uid, value in self._getQuantityUnitDefinitionDict().iteritems():
definition_uid, quantity = value
row_list.append((definition_uid, uid, unit_uid, quantity))
return row_list
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getMeasureRowList') 'getMeasureRowList')
def getMeasureRowList(self): def getMeasureRowList(self):
...@@ -863,6 +945,8 @@ class Resource(XMLMatrix, Variated): ...@@ -863,6 +945,8 @@ class Resource(XMLMatrix, Variated):
if quantity_unit_value is None: if quantity_unit_value is None:
return () return ()
quantity_unit_definition_dict = self._getQuantityUnitDefinitionDict()
metric_type_map = {} # duplicate metric_type are not valid metric_type_map = {} # duplicate metric_type are not valid
for measure in self.getMeasureList(): for measure in self.getMeasureList():
...@@ -875,7 +959,7 @@ class Resource(XMLMatrix, Variated): ...@@ -875,7 +959,7 @@ class Resource(XMLMatrix, Variated):
insert_list = [] insert_list = []
for measure in metric_type_map.itervalues(): for measure in metric_type_map.itervalues():
if measure is not None: if measure is not None:
insert_list += measure.asCatalogRowList() insert_list += measure.asCatalogRowList(quantity_unit_definition_dict)
quantity_unit = quantity_unit_value.getCategoryRelativeUrl() quantity_unit = quantity_unit_value.getCategoryRelativeUrl()
if self.getDefaultMeasure(quantity_unit) is None: if self.getDefaultMeasure(quantity_unit) is None:
...@@ -884,7 +968,9 @@ class Resource(XMLMatrix, Variated): ...@@ -884,7 +968,9 @@ class Resource(XMLMatrix, Variated):
# At this point, we know there is no default measure and we must add # At this point, we know there is no default measure and we must add
# a row for the management unit, with the resource's uid as uid, and # a row for the management unit, with the resource's uid as uid, and
# a generic metric_type. # a generic metric_type.
quantity = quantity_unit_value.getProperty('quantity') quantity_unit_uid = quantity_unit_value.getUid()
quantity = quantity_unit_definition_dict[quantity_unit_uid][1]
metric_type_uid = self.getPortalObject().portal_categories \ metric_type_uid = self.getPortalObject().portal_categories \
.getCategoryUid(metric_type, 'metric_type') .getCategoryUid(metric_type, 'metric_type')
if quantity and metric_type_uid: if quantity and metric_type_uid:
...@@ -892,3 +978,28 @@ class Resource(XMLMatrix, Variated): ...@@ -892,3 +978,28 @@ class Resource(XMLMatrix, Variated):
insert_list += (uid, uid, '^', metric_type_uid, float(quantity)), insert_list += (uid, uid, '^', metric_type_uid, float(quantity)),
return insert_list return insert_list
def getQuantityUnitDefinitionRatio(self, quantity_unit_value):
"""
get the ratio used to define the quantity unit quantity_unit_value.
If the Resource has a local Quantity Unit conversion Definition,
return the ratio from that Definition.
If not, fetch a Definition in the Global Module.
"""
portal = self.getPortalObject()
quantity_unit_uid = quantity_unit_value.getUid()
ratio = None
deprecated_quantity = quantity_unit_value.getProperty('quantity')
if deprecated_quantity is not None:
warn('quantity field of quantity_unit categories is deprecated.' \
' Please use Quantity Unit Conversion Definitions instead and' \
' reset the value of this field.', DeprecationWarning)
ratio = float(deprecated_quantity)
query = self.ResourceModule_zGetQuantityUnitDefinitionRatio(
quantity_unit_uid=quantity_unit_uid,
resource_uid=self.getUid())
return query[0].quantity
...@@ -54,6 +54,8 @@ from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery ...@@ -54,6 +54,8 @@ from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery
from Shared.DC.ZRDB.Results import Results from Shared.DC.ZRDB.Results import Results
from Products.ERP5Type.Utils import mergeZRDBResults from Products.ERP5Type.Utils import mergeZRDBResults
from warnings import warn
class SimulationTool(BaseTool): class SimulationTool(BaseTool):
""" """
The SimulationTool implements the ERP5 The SimulationTool implements the ERP5
...@@ -868,6 +870,10 @@ class SimulationTool(BaseTool): ...@@ -868,6 +870,10 @@ class SimulationTool(BaseTool):
precision - the precision used to round quantities and prices. precision - the precision used to round quantities and prices.
metric_type - convert the results to a specific metric_type
quantity_unit - display results using this specific quantity unit
**kw - if we want extended selection with more keywords (but **kw - if we want extended selection with more keywords (but
bad performance) check what we can do with bad performance) check what we can do with
buildSQLQuery buildSQLQuery
...@@ -893,9 +899,14 @@ class SimulationTool(BaseTool): ...@@ -893,9 +899,14 @@ class SimulationTool(BaseTool):
if len(result) > 0: if len(result) > 0:
if len(result) != 1: if len(result) != 1:
raise ValueError, 'Sorry we must have only one' raise ValueError, 'Sorry we must have only one'
inventory = result[0].total_quantity result = result[0]
if inventory is not None:
total_result = inventory if hasattr(result, "converted_quantity"):
total_result = result.converted_quantity
else:
inventory = result.total_quantity
if inventory is not None:
total_result = inventory
return total_result return total_result
...@@ -983,7 +994,7 @@ class SimulationTool(BaseTool): ...@@ -983,7 +994,7 @@ class SimulationTool(BaseTool):
selection_domain=None, selection_report=None, selection_domain=None, selection_report=None,
statistic=0, inventory_list=1, statistic=0, inventory_list=1,
precision=None, connection_id=None, precision=None, connection_id=None,
quantity_unit=None, **kw): **kw):
""" """
Returns a list of inventories for a single or multiple Returns a list of inventories for a single or multiple
resources on a single or multiple nodes, grouped by resource, resources on a single or multiple nodes, grouped by resource,
...@@ -1018,6 +1029,34 @@ class SimulationTool(BaseTool): ...@@ -1018,6 +1029,34 @@ class SimulationTool(BaseTool):
on individual nodes/categories/payments. on individual nodes/categories/payments.
- -
""" """
getCategory = self.getPortalObject().portal_categories.getCategoryUid
result_column_id_dict = {}
quantity_unit = kw.pop('quantity_unit', None)
quantity_unit_uid = None
if quantity_unit is not None:
if isinstance(quantity_unit, str):
quantity_unit_uid = getCategory(quantity_unit, 'quantity_unit')
if quantity_unit_uid is not None:
result_column_id_dict['converted_quantity'] = None
elif isinstance(quantity_unit, int) or isinstance(quantity_unit, float):
# quantity_unit used to be a numerical parameter..
raise ValueError('Numeric values for quantity_unit are not supported')
convert_quantity_result = False
metric_type = kw.pop('metric_type', None)
if metric_type is not None:
metric_type_uid = getCategory(metric_type, 'metric_type')
if metric_type_uid is not None:
convert_quantity_result = True
kw['metric_type_uid'] = Query(
metric_type_uid=metric_type_uid,
table_alias_list=(("measure", "measure"),))
if src__: if src__:
sql_source_list = [] sql_source_list = []
# If no group at all, give a default sort group by # If no group at all, give a default sort group by
...@@ -1181,7 +1220,9 @@ class SimulationTool(BaseTool): ...@@ -1181,7 +1220,9 @@ class SimulationTool(BaseTool):
selection_domain=selection_domain, selection_domain=selection_domain,
selection_report=selection_report, precision=precision, selection_report=selection_report, precision=precision,
inventory_list=inventory_list, inventory_list=inventory_list,
statistic=statistic, quantity_unit=quantity_unit, statistic=statistic,
quantity_unit_uid=quantity_unit_uid,
convert_quantity_result=convert_quantity_result,
**inventory_stock_sql_kw) **inventory_stock_sql_kw)
# Get delta inventory # Get delta inventory
delta_inventory_line_list = self.Resource_zGetInventoryList( delta_inventory_line_list = self.Resource_zGetInventoryList(
...@@ -1191,7 +1232,9 @@ class SimulationTool(BaseTool): ...@@ -1191,7 +1232,9 @@ class SimulationTool(BaseTool):
selection_domain=selection_domain, selection_domain=selection_domain,
selection_report=selection_report, precision=precision, selection_report=selection_report, precision=precision,
inventory_list=inventory_list, inventory_list=inventory_list,
statistic=statistic, quantity_unit=quantity_unit, statistic=statistic,
quantity_unit_uid=quantity_unit_uid,
convert_quantity_result=convert_quantity_result,
**stock_sql_kw) **stock_sql_kw)
# Match & add initial and delta inventories # Match & add initial and delta inventories
if src__: if src__:
...@@ -1217,12 +1260,9 @@ class SimulationTool(BaseTool): ...@@ -1217,12 +1260,9 @@ class SimulationTool(BaseTool):
No group by criterion, regroup everything. No group by criterion, regroup everything.
""" """
return 'dummy_key' return 'dummy_key'
result_column_id_dict = {}
result_column_id_dict['inventory'] = None result_column_id_dict['inventory'] = None
result_column_id_dict['total_quantity'] = None result_column_id_dict['total_quantity'] = None
result_column_id_dict['total_price'] = None result_column_id_dict['total_price'] = None
if quantity_unit:
result_column_id_dict['converted_quantity'] = None
def addLineValues(line_a=None, line_b=None): def addLineValues(line_a=None, line_b=None):
""" """
Addition columns of 2 lines and return a line with same Addition columns of 2 lines and return a line with same
...@@ -1308,7 +1348,9 @@ class SimulationTool(BaseTool): ...@@ -1308,7 +1348,9 @@ class SimulationTool(BaseTool):
selection_domain=selection_domain, selection_domain=selection_domain,
selection_report=selection_report, precision=precision, selection_report=selection_report, precision=precision,
inventory_list=inventory_list, connection_id=connection_id, inventory_list=inventory_list, connection_id=connection_id,
statistic=statistic, quantity_unit=quantity_unit, statistic=statistic,
quantity_unit_uid=quantity_unit_uid,
convert_quantity_result=convert_quantity_result,
**stock_sql_kw) **stock_sql_kw)
if src__: if src__:
sql_source_list.append(result) sql_source_list.append(result)
...@@ -1318,28 +1360,22 @@ class SimulationTool(BaseTool): ...@@ -1318,28 +1360,22 @@ class SimulationTool(BaseTool):
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getConvertedInventoryList') 'getConvertedInventoryList')
def getConvertedInventoryList(self, metric_type, quantity_unit=1, def getConvertedInventoryList(self, simulation_period='', **kw):
simulation_period='', **kw):
""" """
Return list of inventory with a 'converted_quantity' additional column, Return list of inventory with a 'converted_quantity' additional column,
which contains the sum of measurements for the specified metric type, which contains the sum of measurements for the specified metric type,
expressed in the 'quantity_unit' unit. expressed in the 'quantity_unit' unit.
metric_type - category relative url metric_type - category
quantity_unit - int, float or category relative url quantity_unit - category
""" """
getCategory = self.getPortalObject().portal_categories.getCategoryValue
kw['metric_type_uid'] = Query(
metric_type_uid=getCategory(metric_type, 'metric_type').getUid(),
table_alias_list=(("measure", "measure"),))
if isinstance(quantity_unit, str): warn('getConvertedInventoryList is Deprecated, use ' \
quantity_unit = float(getCategory(quantity_unit, 'quantity_unit') 'getInventory instead.', DeprecationWarning)
.getProperty('quantity'))
method = getattr(self,'get%sInventoryList' % simulation_period) method = getattr(self,'get%sInventoryList' % simulation_period)
return method(quantity_unit=quantity_unit, **kw)
return method(**kw)
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getAllInventoryList') 'getAllInventoryList')
......
...@@ -31,6 +31,12 @@ ...@@ -31,6 +31,12 @@
<key> <string>_data</string> </key> <key> <string>_data</string> </key>
<value> <value>
<dictionary> <dictionary>
<item>
<key> <string>convert_quantity_result</string> </key>
<value>
<dictionary/>
</value>
</item>
<item> <item>
<key> <string>from_table_list</string> </key> <key> <string>from_table_list</string> </key>
<value> <value>
...@@ -113,7 +119,7 @@ ...@@ -113,7 +119,7 @@
</value> </value>
</item> </item>
<item> <item>
<key> <string>quantity_unit</string> </key> <key> <string>quantity_unit_uid</string> </key>
<value> <value>
<dictionary/> <dictionary/>
</value> </value>
...@@ -189,7 +195,8 @@ ...@@ -189,7 +195,8 @@
<string>precision</string> <string>precision</string>
<string>inventory_list</string> <string>inventory_list</string>
<string>statistic</string> <string>statistic</string>
<string>quantity_unit</string> <string>convert_quantity_result</string>
<string>quantity_unit_uid</string>
<string>stock_table_id</string> <string>stock_table_id</string>
</list> </list>
</value> </value>
...@@ -224,7 +231,8 @@ output_simulation_state:list\r\n ...@@ -224,7 +231,8 @@ output_simulation_state:list\r\n
precision\r\n precision\r\n
inventory_list\r\n inventory_list\r\n
statistic\r\n statistic\r\n
quantity_unit\r\n convert_quantity_result\r\n
quantity_unit_uid\r\n
stock_table_id=stock</string> </value> stock_table_id=stock</string> </value>
</item> </item>
<item> <item>
...@@ -269,18 +277,20 @@ SELECT\n ...@@ -269,18 +277,20 @@ SELECT\n
<dtml-if expr="precision is not None">\n <dtml-if expr="precision is not None">\n
SUM(ROUND(<dtml-var stock_table_id>.quantity, <dtml-var precision>)) AS inventory,\n SUM(ROUND(<dtml-var stock_table_id>.quantity, <dtml-var precision>)) AS inventory,\n
SUM(ROUND(<dtml-var stock_table_id>.quantity, <dtml-var precision>)) AS total_quantity,\n SUM(ROUND(<dtml-var stock_table_id>.quantity, <dtml-var precision>)) AS total_quantity,\n
<dtml-if quantity_unit>\n <dtml-if convert_quantity_result>\n
SUM(ROUND(<dtml-var stock_table_id>.quantity * measure.quantity, <dtml-var precision>))\n SUM(ROUND(<dtml-var stock_table_id>.quantity * measure.quantity \n
/ <dtml-sqlvar quantity_unit type=float> AS converted_quantity,\n <dtml-if quantity_unit_uid> / quantity_unit_conversion.quantity</dtml-if>, <dtml-var precision>))\n
</dtml-if>\n AS converted_quantity,\n
</dtml-if>\n
SUM(ROUND(<dtml-var stock_table_id>.total_price, <dtml-var precision>)) AS total_price\n SUM(ROUND(<dtml-var stock_table_id>.total_price, <dtml-var precision>)) AS total_price\n
<dtml-else>\n <dtml-else>\n
SUM(<dtml-var stock_table_id>.quantity) AS inventory,\n SUM(<dtml-var stock_table_id>.quantity) AS inventory,\n
SUM(<dtml-var stock_table_id>.quantity) AS total_quantity,\n SUM(<dtml-var stock_table_id>.quantity) AS total_quantity,\n
<dtml-if quantity_unit>\n <dtml-if convert_quantity_result>\n
ROUND(SUM(<dtml-var stock_table_id>.quantity * measure.quantity)\n ROUND(SUM(<dtml-var stock_table_id>.quantity * measure.quantity\n
/ <dtml-sqlvar quantity_unit type=float>, 12) AS converted_quantity,\n <dtml-if quantity_unit_uid> / quantity_unit_conversion.quantity</dtml-if>), 12)\n
</dtml-if>\n AS converted_quantity,\n
</dtml-if>\n
SUM(<dtml-var stock_table_id>.total_price) AS total_price\n SUM(<dtml-var stock_table_id>.total_price) AS total_price\n
</dtml-if>\n </dtml-if>\n
<dtml-if inventory_list>\n <dtml-if inventory_list>\n
...@@ -324,6 +334,11 @@ FROM\n ...@@ -324,6 +334,11 @@ FROM\n
catalog, <dtml-var stock_table_id>\n catalog, <dtml-var stock_table_id>\n
<dtml-if section_filtered> INNER <dtml-else> LEFT </dtml-if> \n <dtml-if section_filtered> INNER <dtml-else> LEFT </dtml-if> \n
JOIN catalog AS section ON (section.uid = <dtml-var stock_table_id>.section_uid)\n JOIN catalog AS section ON (section.uid = <dtml-var stock_table_id>.section_uid)\n
<dtml-if quantity_unit_uid>\n
LEFT JOIN quantity_unit_conversion ON \n
(quantity_unit_conversion.resource_uid = <dtml-var stock_table_id>.resource_uid\n
AND quantity_unit_conversion.quantity_unit_uid = <dtml-sqlvar quantity_unit_uid type=int>)\n
</dtml-if>\n
<dtml-in prefix="table" expr="from_table_list"> \n <dtml-in prefix="table" expr="from_table_list"> \n
<dtml-if expr="table_key not in (\'catalog\', stock_table_id)">\n <dtml-if expr="table_key not in (\'catalog\', stock_table_id)">\n
, <dtml-var table_item> AS <dtml-var table_key>\n , <dtml-var table_item> AS <dtml-var table_key>\n
...@@ -353,7 +368,7 @@ WHERE\n ...@@ -353,7 +368,7 @@ WHERE\n
AND <dtml-var "portal_selections.buildSQLExpressionFromDomainSelection(selection_report, strict_membership=1)">\n AND <dtml-var "portal_selections.buildSQLExpressionFromDomainSelection(selection_report, strict_membership=1)">\n
</dtml-if>\n </dtml-if>\n
\n \n
<dtml-if quantity_unit>\n <dtml-if convert_quantity_result>\n
AND concat(<dtml-var stock_table_id>.variation_text,\'\\n\') REGEXP measure.variation\n AND concat(<dtml-var stock_table_id>.variation_text,\'\\n\') REGEXP measure.variation\n
</dtml-if>\n </dtml-if>\n
\n \n
...@@ -410,18 +425,20 @@ SELECT\n ...@@ -410,18 +425,20 @@ SELECT\n
<dtml-if expr="precision is not None">\n <dtml-if expr="precision is not None">\n
SUM(ROUND(<dtml-var stock_table_id>.quantity, <dtml-var precision>)) AS inventory,\n SUM(ROUND(<dtml-var stock_table_id>.quantity, <dtml-var precision>)) AS inventory,\n
SUM(ROUND(<dtml-var stock_table_id>.quantity, <dtml-var precision>)) AS total_quantity,\n SUM(ROUND(<dtml-var stock_table_id>.quantity, <dtml-var precision>)) AS total_quantity,\n
<dtml-if quantity_unit>\n <dtml-if convert_quantity_result>\n
SUM(ROUND(<dtml-var stock_table_id>.quantity * measure.quantity, <dtml-var precision>))\n SUM(ROUND(<dtml-var stock_table_id>.quantity * measure.quantity \n
/ <dtml-sqlvar quantity_unit type=float> AS converted_quantity,\n <dtml-if quantity_unit_uid> / quantity_unit_conversion.quantity</dtml-if>, <dtml-var precision>))\n
</dtml-if>\n AS converted_quantity,\n
</dtml-if>\n
SUM(ROUND(<dtml-var stock_table_id>.total_price, <dtml-var precision>)) AS total_price\n SUM(ROUND(<dtml-var stock_table_id>.total_price, <dtml-var precision>)) AS total_price\n
<dtml-else>\n <dtml-else>\n
SUM(<dtml-var stock_table_id>.quantity) AS inventory,\n SUM(<dtml-var stock_table_id>.quantity) AS inventory,\n
SUM(<dtml-var stock_table_id>.quantity) AS total_quantity,\n SUM(<dtml-var stock_table_id>.quantity) AS total_quantity,\n
<dtml-if quantity_unit>\n <dtml-if convert_quantity_result>\n
ROUND(SUM(<dtml-var stock_table_id>.quantity * measure.quantity)\n ROUND(SUM(<dtml-var stock_table_id>.quantity * measure.quantity\n
/ <dtml-sqlvar quantity_unit type=float>, 12) AS converted_quantity,\n <dtml-if quantity_unit_uid> / quantity_unit_conversion.quantity</dtml-if>), 12)\n
</dtml-if>\n AS converted_quantity,\n
</dtml-if>\n
SUM(<dtml-var stock_table_id>.total_price) AS total_price\n SUM(<dtml-var stock_table_id>.total_price) AS total_price\n
</dtml-if>\n </dtml-if>\n
<dtml-if inventory_list>\n <dtml-if inventory_list>\n
...@@ -465,6 +482,11 @@ FROM\n ...@@ -465,6 +482,11 @@ FROM\n
catalog, <dtml-var stock_table_id>\n catalog, <dtml-var stock_table_id>\n
<dtml-if section_filtered> INNER <dtml-else> LEFT </dtml-if> \n <dtml-if section_filtered> INNER <dtml-else> LEFT </dtml-if> \n
JOIN catalog AS section ON (section.uid = <dtml-var stock_table_id>.section_uid)\n JOIN catalog AS section ON (section.uid = <dtml-var stock_table_id>.section_uid)\n
<dtml-if quantity_unit_uid>\n
LEFT JOIN quantity_unit_conversion ON \n
(quantity_unit_conversion.resource_uid = <dtml-var stock_table_id>.resource_uid\n
AND quantity_unit_conversion.quantity_unit_uid = <dtml-sqlvar quantity_unit_uid type=int>)\n
</dtml-if>\n
<dtml-in prefix="table" expr="from_table_list"> \n <dtml-in prefix="table" expr="from_table_list"> \n
<dtml-if expr="table_key not in (\'catalog\', stock_table_id)">\n <dtml-if expr="table_key not in (\'catalog\', stock_table_id)">\n
, <dtml-var table_item> AS <dtml-var table_key>\n , <dtml-var table_item> AS <dtml-var table_key>\n
...@@ -494,7 +516,7 @@ WHERE\n ...@@ -494,7 +516,7 @@ WHERE\n
AND <dtml-var "portal_selections.buildSQLExpressionFromDomainSelection(selection_report, strict_membership=1)">\n AND <dtml-var "portal_selections.buildSQLExpressionFromDomainSelection(selection_report, strict_membership=1)">\n
</dtml-if>\n </dtml-if>\n
\n \n
<dtml-if quantity_unit>\n <dtml-if convert_quantity_result>\n
AND concat(<dtml-var stock_table_id>.variation_text,\'\\n\') REGEXP measure.variation\n AND concat(<dtml-var stock_table_id>.variation_text,\'\\n\') REGEXP measure.variation\n
</dtml-if>\n </dtml-if>\n
\n \n
......
1435 1436
\ No newline at end of file \ No newline at end of file
<catalog_method>
<item key="sql_clear_catalog" type="int">
<value>1</value>
</item>
</catalog_method>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<tuple>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
<tuple/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_arg</string> </key>
<value>
<object>
<klass>
<global name="Args" module="Shared.DC.ZRDB.Aqueduct"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_data</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>_keys</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z0_drop_quantity_unit_conversion</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string>DROP TABLE IF EXISTS quantity_unit_conversion</string> </value>
</item>
<item>
<key> <string>template</string> </key>
<value>
<object>
<klass>
<global name="__newobj__" module="copy_reg"/>
</klass>
<tuple>
<global name="SQL" module="Shared.DC.ZRDB.DA"/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string encoding="cdata"><![CDATA[
<string>
]]></string> </value>
</item>
<item>
<key> <string>_vars</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>globals</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>raw</string> </key>
<value> <string>DROP TABLE IF EXISTS quantity_unit_conversion</string> </value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<catalog_method>
<item key="sql_uncatalog_object" type="int">
<value>1</value>
</item>
</catalog_method>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<tuple>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
<tuple/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_arg</string> </key>
<value>
<object>
<klass>
<global name="Args" module="Shared.DC.ZRDB.Aqueduct"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_data</string> </key>
<value>
<dictionary>
<item>
<key> <string>uid</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>_keys</string> </key>
<value>
<list>
<string>uid</string>
</list>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string>uid</string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z0_uncatalog_quantity_unit_conversion</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
DELETE FROM quantity_unit_conversion\n
WHERE <dtml-sqltest uid op=eq type=int>
]]></string> </value>
</item>
<item>
<key> <string>template</string> </key>
<value>
<object>
<klass>
<global name="__newobj__" module="copy_reg"/>
</klass>
<tuple>
<global name="SQL" module="Shared.DC.ZRDB.DA"/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string encoding="cdata"><![CDATA[
<string>
]]></string> </value>
</item>
<item>
<key> <string>_vars</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>globals</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>raw</string> </key>
<value> <string encoding="cdata"><![CDATA[
DELETE FROM quantity_unit_conversion\n
WHERE <dtml-sqltest uid op=eq type=int>
]]></string> </value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<catalog_method>
<item key="sql_catalog_object_list" type="int">
<value>1</value>
</item>
</catalog_method>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<tuple>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
<tuple/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_arg</string> </key>
<value>
<object>
<klass>
<global name="Args" module="Shared.DC.ZRDB.Aqueduct"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_data</string> </key>
<value>
<dictionary>
<item>
<key> <string>getQuantityUnitConversionDefinitionRowList</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>uid</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>_keys</string> </key>
<value>
<list>
<string>uid</string>
<string>getQuantityUnitConversionDefinitionRowList</string>
</list>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string>uid\r\n
getQuantityUnitConversionDefinitionRowList\r\n
</string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_catalog_quantity_unit_conversion_list</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
<dtml-let quantity_unit_conversion_dict="{}" value_list="[]">\n
<dtml-in getQuantityUnitConversionDefinitionRowList\n
prefix="loop">\n
<dtml-if loop_item>\n
<dtml-comment>\n
Make sure that we get no duplicates, and also aggregate the uids of the modified resources for deletion\n
</dtml-comment>\n
<dtml-in loop_item prefix="inner">\n
<dtml-call expr="quantity_unit_conversion_dict.setdefault(inner_item[1], {}).setdefault(inner_item[2], inner_item)">\n
</dtml-in>\n
</dtml-if>\n
</dtml-in>\n
\n
<dtml-if quantity_unit_conversion_dict>\n
DELETE FROM `quantity_unit_conversion` WHERE\n
<dtml-sqltest "quantity_unit_conversion_dict.keys()" column="resource_uid" type="int" multiple>;\n
\n
\n
<dtml-var sql_delimiter>\n
\n
<dtml-in "quantity_unit_conversion_dict.values()" prefix="loop">\n
<dtml-call "value_list.extend(loop_item.values())">\n
</dtml-in>\n
\n
INSERT INTO `quantity_unit_conversion`\n
VALUES\n
<dtml-in "value_list" prefix="loop">\n
(\n
<dtml-sqlvar expr="loop_item[0]" type="int" optional>,\n
<dtml-sqlvar expr="loop_item[1]" type="int">,\n
<dtml-sqlvar expr="loop_item[2]" type="int">,\n
<dtml-sqlvar expr="loop_item[3]" type="float">\n
)\n
<dtml-unless sequence-end>,</dtml-unless>\n
</dtml-in>\n
</dtml-if>\n
\n
</dtml-let>
]]></string> </value>
</item>
<item>
<key> <string>template</string> </key>
<value>
<object>
<klass>
<global name="__newobj__" module="copy_reg"/>
</klass>
<tuple>
<global name="SQL" module="Shared.DC.ZRDB.DA"/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string encoding="cdata"><![CDATA[
<string>
]]></string> </value>
</item>
<item>
<key> <string>_vars</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>globals</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>raw</string> </key>
<value> <string encoding="cdata"><![CDATA[
<dtml-let quantity_unit_conversion_dict="{}" value_list="[]">\n
<dtml-in getQuantityUnitConversionDefinitionRowList\n
prefix="loop">\n
<dtml-if loop_item>\n
<dtml-comment>\n
Make sure that we get no duplicates, and also aggregate the uids of the modified resources for deletion\n
</dtml-comment>\n
<dtml-in loop_item prefix="inner">\n
<dtml-call expr="quantity_unit_conversion_dict.setdefault(inner_item[1], {}).setdefault(inner_item[2], inner_item)">\n
</dtml-in>\n
</dtml-if>\n
</dtml-in>\n
\n
<dtml-if quantity_unit_conversion_dict>\n
DELETE FROM `quantity_unit_conversion` WHERE\n
<dtml-sqltest "quantity_unit_conversion_dict.keys()" column="resource_uid" type="int" multiple>;\n
\n
\n
<dtml-var sql_delimiter>\n
\n
<dtml-in "quantity_unit_conversion_dict.values()" prefix="loop">\n
<dtml-call "value_list.extend(loop_item.values())">\n
</dtml-in>\n
\n
INSERT INTO `quantity_unit_conversion`\n
VALUES\n
<dtml-in "value_list" prefix="loop">\n
(\n
<dtml-sqlvar expr="loop_item[0]" type="int" optional>,\n
<dtml-sqlvar expr="loop_item[1]" type="int">,\n
<dtml-sqlvar expr="loop_item[2]" type="int">,\n
<dtml-sqlvar expr="loop_item[3]" type="float">\n
)\n
<dtml-unless sequence-end>,</dtml-unless>\n
</dtml-in>\n
</dtml-if>\n
\n
</dtml-let>
]]></string> </value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<catalog_method>
<item key="sql_clear_catalog" type="int">
<value>1</value>
</item>
</catalog_method>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<tuple>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
<tuple/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_arg</string> </key>
<value>
<object>
<klass>
<global name="Args" module="Shared.DC.ZRDB.Aqueduct"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_data</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>_keys</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_col</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_create_quantity_unit_conversion</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string>CREATE TABLE `quantity_unit_conversion` (\n
`uid` BIGINT UNSIGNED,\n
`resource_uid` BIGINT UNSIGNED NOT NULL,\n
`quantity_unit_uid` BIGINT UNSIGNED NOT NULL,\n
`quantity` REAL NOT NULL,\n
PRIMARY KEY (`resource_uid`, `quantity_unit_uid`),\n
KEY (`uid`)\n
) TYPE=InnoDB;\n
</string> </value>
</item>
<item>
<key> <string>template</string> </key>
<value>
<object>
<klass>
<global name="__newobj__" module="copy_reg"/>
</klass>
<tuple>
<global name="SQL" module="Shared.DC.ZRDB.DA"/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string encoding="cdata"><![CDATA[
<string>
]]></string> </value>
</item>
<item>
<key> <string>_vars</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>globals</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>raw</string> </key>
<value> <string>CREATE TABLE `quantity_unit_conversion` (\n
`uid` BIGINT UNSIGNED,\n
`resource_uid` BIGINT UNSIGNED NOT NULL,\n
`quantity_unit_uid` BIGINT UNSIGNED NOT NULL,\n
`quantity` REAL NOT NULL,\n
PRIMARY KEY (`resource_uid`, `quantity_unit_uid`),\n
KEY (`uid`)\n
) TYPE=InnoDB;\n
</string> </value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
167 168
\ No newline at end of file \ No newline at end of file
...@@ -12,6 +12,7 @@ erp5_mysql_innodb/z0_drop_movement ...@@ -12,6 +12,7 @@ erp5_mysql_innodb/z0_drop_movement
erp5_mysql_innodb/z0_drop_portal_ids erp5_mysql_innodb/z0_drop_portal_ids
erp5_mysql_innodb/z0_drop_predicate erp5_mysql_innodb/z0_drop_predicate
erp5_mysql_innodb/z0_drop_predicate_category erp5_mysql_innodb/z0_drop_predicate_category
erp5_mysql_innodb/z0_drop_quantity_unit_conversion
erp5_mysql_innodb/z0_drop_record erp5_mysql_innodb/z0_drop_record
erp5_mysql_innodb/z0_drop_roles_and_users erp5_mysql_innodb/z0_drop_roles_and_users
erp5_mysql_innodb/z0_drop_stock erp5_mysql_innodb/z0_drop_stock
...@@ -27,6 +28,7 @@ erp5_mysql_innodb/z0_uncatalog_measure ...@@ -27,6 +28,7 @@ erp5_mysql_innodb/z0_uncatalog_measure
erp5_mysql_innodb/z0_uncatalog_movement erp5_mysql_innodb/z0_uncatalog_movement
erp5_mysql_innodb/z0_uncatalog_predicate erp5_mysql_innodb/z0_uncatalog_predicate
erp5_mysql_innodb/z0_uncatalog_predicate_category erp5_mysql_innodb/z0_uncatalog_predicate_category
erp5_mysql_innodb/z0_uncatalog_quantity_unit_conversion
erp5_mysql_innodb/z0_uncatalog_stock erp5_mysql_innodb/z0_uncatalog_stock
erp5_mysql_innodb/z0_uncatalog_versioning erp5_mysql_innodb/z0_uncatalog_versioning
erp5_mysql_innodb/z_SubCatalogQuery erp5_mysql_innodb/z_SubCatalogQuery
...@@ -44,6 +46,7 @@ erp5_mysql_innodb/z_catalog_object_list ...@@ -44,6 +46,7 @@ erp5_mysql_innodb/z_catalog_object_list
erp5_mysql_innodb/z_catalog_paths erp5_mysql_innodb/z_catalog_paths
erp5_mysql_innodb/z_catalog_predicate_category_list erp5_mysql_innodb/z_catalog_predicate_category_list
erp5_mysql_innodb/z_catalog_predicate_list erp5_mysql_innodb/z_catalog_predicate_list
erp5_mysql_innodb/z_catalog_quantity_unit_conversion_list
erp5_mysql_innodb/z_catalog_roles_and_users_list erp5_mysql_innodb/z_catalog_roles_and_users_list
erp5_mysql_innodb/z_catalog_stock_list erp5_mysql_innodb/z_catalog_stock_list
erp5_mysql_innodb/z_catalog_translation_list erp5_mysql_innodb/z_catalog_translation_list
...@@ -63,6 +66,7 @@ erp5_mysql_innodb/z_create_movement ...@@ -63,6 +66,7 @@ erp5_mysql_innodb/z_create_movement
erp5_mysql_innodb/z_create_portal_ids erp5_mysql_innodb/z_create_portal_ids
erp5_mysql_innodb/z_create_predicate erp5_mysql_innodb/z_create_predicate
erp5_mysql_innodb/z_create_predicate_category erp5_mysql_innodb/z_create_predicate_category
erp5_mysql_innodb/z_create_quantity_unit_conversion
erp5_mysql_innodb/z_create_record erp5_mysql_innodb/z_create_record
erp5_mysql_innodb/z_create_roles_and_users erp5_mysql_innodb/z_create_roles_and_users
erp5_mysql_innodb/z_create_stock erp5_mysql_innodb/z_create_stock
......
...@@ -2377,24 +2377,81 @@ class TestInventoryDocument(InventoryAPITestCase): ...@@ -2377,24 +2377,81 @@ class TestInventoryDocument(InventoryAPITestCase):
[x.path for x in self.resource.getInventoryList( [x.path for x in self.resource.getInventoryList(
optimisation__=False, optimisation__=False,
mirror_uid=self.mirror_node.getUid())]) mirror_uid=self.mirror_node.getUid())])
class TestUnitConversion(InventoryAPITestCase): class BaseTestUnitConversion(InventoryAPITestCase):
QUANTITY_UNIT_CATEGORIES = { QUANTITY_UNIT_DICT = {}
'unit': {'unit': 1, 'a_few': None}, METRIC_TYPE_CATEGORY_LIST = ()
'length': {'m': 1, 'in': .0254},
'mass': {'kg': 1, 't': 1000, 'g': .001}, def setUpUnitDefinition(self):
unit_module = self.portal.quantity_unit_conversion_module
for base, t in self.QUANTITY_UNIT_DICT.iteritems():
standard, definition_dict = t
group = unit_module._getOb(base, None)
if group is None:
group = unit_module.newContent(
id=base,
portal_type='Quantity Unit Conversion Group',
quantity_unit="%s/%s" % (base, standard),
immediate_reindex=1 )
for unit, amount in definition_dict.iteritems():
if group._getOb(unit, None) is None:
group.newContent(
id=unit,
portal_type="Quantity Unit Conversion Definition",
quantity_unit="%s/%s" % (base, unit),
quantity=amount,
immediate_reindex=1)
def afterSetUp(self):
InventoryAPITestCase.afterSetUp(self)
self.setUpUnitDefinition()
def makeMovement(self, quantity, resource, *variation, **kw):
m = self._makeMovement(quantity=quantity, resource_value=resource,
source_value=self.node, destination_value=self.mirror_node, **kw)
if variation:
m.setVariationCategoryList(variation)
self._safeTic()
def convertedSimulation(self, metric_type, **kw):
return self.getSimulationTool().getInventory(
metric_type=metric_type, node_uid=self.node.getUid(),
**kw)
def getNeededCategoryList(self):
category_list = ['metric_type/' + c for c in self.METRIC_TYPE_CATEGORY_LIST]
for base, t in self.QUANTITY_UNIT_DICT.iteritems():
standard, definition_dict = t
quantity = 'quantity_unit/%s/' % base
category_list.append(quantity + standard)
category_list.extend(quantity + unit for unit in definition_dict)
category_list += InventoryAPITestCase.getNeededCategoryList(self)
return category_list
class TestUnitConversion(BaseTestUnitConversion):
QUANTITY_UNIT_DICT = {
# base: (reference, dict_of_others)
'unit': ('unit', dict(a_few=None)),
'length': ('m', {'in': .0254}),
'mass': ('kg', dict(t=1000, g=.001)),
} }
METRIC_TYPE_CATEGORIES = ( METRIC_TYPE_CATEGORY_LIST = (
'unit', 'unit',
'unit/0', 'unit/0',
'unit/1', 'unit/1',
'unit/2', 'unit/2',
'unit/lot',
'mass/net', 'mass/net',
'mass/nutr/lipid', 'mass/nutr/lipid',
) )
def afterSetUp(self): def afterSetUp(self):
InventoryAPITestCase.afterSetUp(self) BaseTestUnitConversion.afterSetUp(self)
self.resource.setQuantityUnitList(('unit/unit', 'length/in')) self.resource.setQuantityUnitList(('unit/unit', 'length/in'))
self.other_resource.setQuantityUnit('mass/g') self.other_resource.setQuantityUnit('mass/g')
...@@ -2433,54 +2490,176 @@ class TestUnitConversion(InventoryAPITestCase): ...@@ -2433,54 +2490,176 @@ class TestUnitConversion(InventoryAPITestCase):
self._safeTic() self._safeTic()
def getNeededCategoryList(self):
category_list = ['metric_type/' + c for c in self.METRIC_TYPE_CATEGORIES]
for level1, level2 in self.QUANTITY_UNIT_CATEGORIES.iteritems():
quantity = 'quantity_unit/%s/' % level1
category_list.extend(quantity + unit for unit in level2)
category_list += InventoryAPITestCase.getNeededCategoryList(self)
return category_list
def createCategories(self):
InventoryAPITestCase.createCategories(self)
quantity_unit = self.getCategoryTool().quantity_unit
for quantity_id, unit_dict in self.QUANTITY_UNIT_CATEGORIES.iteritems():
quantity_value = quantity_unit[quantity_id]
for unit_id, unit_scale in unit_dict.iteritems():
if unit_scale is not None:
quantity_value[unit_id].setProperty('quantity', unit_scale)
def testConvertedInventoryList(self): def testConvertedInventoryList(self):
def makeMovement(quantity, resource, *variation, **kw): self.makeMovement(2, self.resource, 'colour/green', 'size/big')
m = self._makeMovement(quantity=quantity, resource_value=resource, self.makeMovement(789, self.other_resource)
source_value=self.node, destination_value=self.mirror_node, **kw) self.makeMovement(-13, self.resource, 'colour/red', 'size/small',
if variation:
m.setVariationCategoryList(variation)
self._safeTic()
makeMovement(2, self.resource, 'colour/green', 'size/big')
makeMovement(789, self.other_resource)
makeMovement(-13, self.resource, 'colour/red', 'size/small',
'industrial_phase/phase1', 'industrial_phase/phase2') 'industrial_phase/phase1', 'industrial_phase/phase2')
def simulation(metric_type, **kw):
return self.getSimulationTool().getConvertedInventoryList(
metric_type=metric_type, node_uid=self.node.getUid(),
ignore_group_by=1, inventory_list=0, **kw)[0].converted_quantity
for i in range(3): for i in range(3):
self.assertEquals(None, simulation('unit/%i' % i)) self.assertEquals(None, self.convertedSimulation('unit/%i' % i))
self.assertEquals(None, simulation('unit', simulation_period='Current')) self.assertEquals(None,
self.assertEquals(11, simulation('unit')) self.convertedSimulation('unit',
simulation_period='Current'))
self.assertEquals(11, self.convertedSimulation('unit'))
self.assertEquals(11 * 123 - 789, self.assertEquals(11 * .123 - .789, self.convertedSimulation('mass/net'))
simulation('mass/net', quantity_unit=.001))
self.assertEquals((11 * 123 - 789) / 1e6, self.assertEquals((11 * 123 - 789) / 1e6,
simulation('mass/net', quantity_unit='mass/t')) self.convertedSimulation('mass/net',
quantity_unit='mass/t'))
self.assertEquals(13 * .056 - 2 * .043,
self.convertedSimulation('mass/nutr/lipid'))
self.assertEquals(13 * .056 - 2 * .043, simulation('mass/nutr/lipid')) class TestUnitConversionDefinition(BaseTestUnitConversion):
QUANTITY_UNIT_DICT = {
# base: (reference, dict_of_others)
'unit': ('unit', dict(lot=1000, pack=6)),
}
METRIC_TYPE_CATEGORY_LIST = (
'unit',
)
def afterSetUp(self):
BaseTestUnitConversion.afterSetUp(self)
# Aliases for readability
self.resource_bylot = self.resource
self.resource_bylot_overriding = self.other_resource
# And a third resource
self.resource_byunit = self.getProductModule().newContent(
title='Resource counted By Unit',
portal_type='Product')
self.resource_bypack = self.getProductModule().newContent(
title='Resource counted By Pack',
portal_type='Product')
self.resource_bylot.setQuantityUnit('unit/lot')
self.resource_bypack.setQuantityUnit('unit/pack')
self.resource_bylot_overriding.setQuantityUnit('unit/lot')
self.resource_byunit.setQuantityUnit('unit/unit')
self._safeTic()
base_unit = self.resource_bylot_overriding.newContent(
portal_type='Quantity Unit Conversion Group',
quantity_unit='unit/unit',
immediate_reindex=1)
unit = base_unit.newContent(
portal_type='Quantity Unit Conversion Definition',
quantity_unit='unit/lot',
quantity=50,
immediate_reindex=1)
self._safeTic()
def testAggregatedReports(self):
self.makeMovement(-10, self.resource_bylot)
self.makeMovement(-1, self.resource_bypack)
self.makeMovement(2, self.resource_bylot_overriding)
self.makeMovement(500, self.resource_byunit)
# Always displayed as quantity*unit_ratio
self.assertEquals(10*1000 + 1*6 - 2*50 - 500*1,
self.convertedSimulation('unit'))
self.assertEquals(10*1000 + 1*6 - 2*50 - 500*1,
self.convertedSimulation('unit',
quantity_unit='unit/unit'))
self.assertEquals(10*1 + 1*(6*0.001) - 2*1 - 500*(1./1000),
self.convertedSimulation('unit',
quantity_unit='unit/lot'))
# amounts are rounded on the 12th digit.
self.assertEquals(round(10*(1000./6) + 1*1 - 2*(50./6) - 500*(1./6), 12),
self.convertedSimulation('unit',
quantity_unit='unit/pack'))
def testResourceConvertQuantity(self):
# First, test without local Unit Definitions
for resource in (self.resource_bylot,
self.resource_bypack,
self.resource_byunit):
# not_reference -> reference quantity
self.assertEquals(1000,
resource.convertQuantity(1,
"unit/lot",
"unit/unit"))
# reference -> not_reference
self.assertEquals(1,
resource.convertQuantity(1000,
"unit/unit",
"unit/lot"))
# not_reference -> not_reference
self.assertEquals(1*1000./6,
resource.convertQuantity(1,
"unit/lot",
"unit/pack"))
self.assertEquals(1*6./1000,
resource.convertQuantity(1,
"unit/pack",
"unit/lot"))
# then with local Unit definition
self.assertEquals(1*50,
self.resource_bylot_overriding\
.convertQuantity(1, "unit/lot", "unit/unit"))
self.assertEquals(1./50,
self.resource_bylot_overriding\
.convertQuantity(1, "unit/unit", "unit/lot"))
self.assertEquals(1*50./6,
self.resource_bylot_overriding\
.convertQuantity(1, "unit/lot", "unit/pack"))
self.assertEquals(1*6./50,
self.resource_bylot_overriding\
.convertQuantity(1, "unit/pack", "unit/lot"))
def testResourceConvertQuantityAfterGlobalChange(self):
"""
after a change in a Global unit definition, definitions should get
reindexed.
"""
# Before the global change, global definition reads 1000
self.assertEquals(1000,
self.resource_bylot.convertQuantity(1,
"unit/lot",
"unit/unit"))
# which does not affect resources overriding the definition
self.assertEquals(1*50,
self.resource_bylot_overriding\
.convertQuantity(1, "unit/lot", "unit/unit"))
portal = self.getPortalObject()
lot_uid = portal.portal_categories.quantity_unit.unit.lot.getUid()
query = portal.portal_catalog(quantity_unit_uid=lot_uid,
grand_parent_portal_type= \
"Quantity Unit Conversion" \
" Module",
portal_type= \
"Quantity Unit Conversion" \
" Definition")
self.assertEquals(1, len(query))
query[0].getObject().setQuantity(500)
# this change triggers Resource reindexations. Wait for 'em!
transaction.commit()
self.tic()
# SQL tables should have been updated:
self.assertEquals(500,
self.resource_bylot.convertQuantity(1,
"unit/lot",
"unit/unit"))
# without affecting resources that override the definition
self.assertEquals(1*50,
self.resource_bylot_overriding\
.convertQuantity(1, "unit/lot", "unit/unit"))
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
...@@ -2492,6 +2671,7 @@ def test_suite(): ...@@ -2492,6 +2671,7 @@ def test_suite():
suite.addTest(unittest.makeSuite(TestTrackingList)) suite.addTest(unittest.makeSuite(TestTrackingList))
suite.addTest(unittest.makeSuite(TestInventoryDocument)) suite.addTest(unittest.makeSuite(TestInventoryDocument))
suite.addTest(unittest.makeSuite(TestUnitConversion)) suite.addTest(unittest.makeSuite(TestUnitConversion))
suite.addTest(unittest.makeSuite(TestUnitConversionDefinition))
return suite return suite
# vim: foldmethod=marker # vim: foldmethod=marker
...@@ -163,15 +163,27 @@ class TestResource(ERP5TypeTestCase): ...@@ -163,15 +163,27 @@ class TestResource(ERP5TypeTestCase):
if self.quantity_unit_gram is None: if self.quantity_unit_gram is None:
self.quantity_unit_gram = quantity_unit_weight.newContent( self.quantity_unit_gram = quantity_unit_weight.newContent(
portal_type='Category', portal_type='Category',
quantity=0.001,
id='gram') id='gram')
self.quantity_unit_kilo = quantity_unit_weight._getOb('kilo', None) self.quantity_unit_kilo = quantity_unit_weight._getOb('kilo', None)
if self.quantity_unit_kilo is None: if self.quantity_unit_kilo is None:
self.quantity_unit_kilo = quantity_unit_weight.newContent( self.quantity_unit_kilo = quantity_unit_weight.newContent(
portal_type='Category', portal_type='Category',
quantity=1,
id='kilo') id='kilo')
unit_conversion_module = self.portal.quantity_unit_conversion_module
weight_group = unit_conversion_module._getOb('weight', None)
if weight_group is None:
weight_group = unit_conversion_module.newContent(id='weight',
portal_type='Quantity Unit Conversion Group',
quantity_unit='weight/kilo')
gram_definition = weight_group._getOb('gram', None)
if gram_definition is None:
gram_definition = weight_group.newContent(id='gram',
portal_type='Quantity Unit Conversion Definition',
quantity_unit='weight/gram',
quantity=0.001)
def stepCreateResource(self, sequence=None, sequence_list=None, **kw): def stepCreateResource(self, sequence=None, sequence_list=None, **kw):
""" """
......
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