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
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):
"""
A Measure
......@@ -139,7 +133,7 @@ class Measure(XMLMatrix):
metric_type = self.getMetricType()
if quantity_unit is not None and metric_type and \
quantity_unit.getParentId() == metric_type.split('/', 1)[0]:
return getQuantity(quantity_unit)
return self.getQuantityUnitDefinitionRatio(quantity_unit)
security.declareProtected(AccessContentsInformation, 'getConvertedQuantity')
def getConvertedQuantity(self, variation_list=()):
......@@ -168,19 +162,27 @@ class Measure(XMLMatrix):
# Cataloging.
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.
Called by Resource.getMeasureRowList.
"""
quantity_unit = self.getConvertedQuantityUnit()
if not quantity_unit:
quantity_unit = self.getQuantityUnitValue()
metric_type = self.getMetricType()
if quantity_unit is None or not metric_type or \
quantity_unit.getParentId() != metric_type.split('/', 1)[0]:
return ()
quantity_unit_uid = quantity_unit.getUid()
uid = self.getUid()
resource = self.getResourceValue()
resource_uid = resource.getUid()
metric_type_uid = self.getMetricTypeUid()
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
# set a specific metric_type for the management unit.
......@@ -196,8 +198,12 @@ class Measure(XMLMatrix):
# so we simply return 1 row.
if quantity is not None:
quantity *= quantity_unit
if (not default or quantity ==
getQuantity(resource.getQuantityUnitValue())):
management_unit_uid = resource.getQuantityUnitUid()
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 ()
......
##############################################################################
#
# 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):
if management_unit == quantity_unit:
return 1.0
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]:
measure = self.getDefaultMeasure(quantity_unit)
quantity /= measure.getConvertedQuantity(variation_list)
else:
quantity /= float(traverse(management_unit).getProperty('quantity'))
quantity /= self.getQuantityUnitDefinitionRatio(traverse(management_unit))
return quantity
# Unit conversion
......@@ -852,6 +852,88 @@ class Resource(XMLMatrix, Variated):
if len(result) == 1:
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,
'getMeasureRowList')
def getMeasureRowList(self):
......@@ -863,6 +945,8 @@ class Resource(XMLMatrix, Variated):
if quantity_unit_value is None:
return ()
quantity_unit_definition_dict = self._getQuantityUnitDefinitionDict()
metric_type_map = {} # duplicate metric_type are not valid
for measure in self.getMeasureList():
......@@ -875,7 +959,7 @@ class Resource(XMLMatrix, Variated):
insert_list = []
for measure in metric_type_map.itervalues():
if measure is not None:
insert_list += measure.asCatalogRowList()
insert_list += measure.asCatalogRowList(quantity_unit_definition_dict)
quantity_unit = quantity_unit_value.getCategoryRelativeUrl()
if self.getDefaultMeasure(quantity_unit) is None:
......@@ -884,7 +968,9 @@ class Resource(XMLMatrix, Variated):
# 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 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 \
.getCategoryUid(metric_type, 'metric_type')
if quantity and metric_type_uid:
......@@ -892,3 +978,28 @@ class Resource(XMLMatrix, Variated):
insert_list += (uid, uid, '^', metric_type_uid, float(quantity)),
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
from Shared.DC.ZRDB.Results import Results
from Products.ERP5Type.Utils import mergeZRDBResults
from warnings import warn
class SimulationTool(BaseTool):
"""
The SimulationTool implements the ERP5
......@@ -868,6 +870,10 @@ class SimulationTool(BaseTool):
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
bad performance) check what we can do with
buildSQLQuery
......@@ -893,9 +899,14 @@ class SimulationTool(BaseTool):
if len(result) > 0:
if len(result) != 1:
raise ValueError, 'Sorry we must have only one'
inventory = result[0].total_quantity
if inventory is not None:
total_result = inventory
result = result[0]
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
......@@ -983,7 +994,7 @@ class SimulationTool(BaseTool):
selection_domain=None, selection_report=None,
statistic=0, inventory_list=1,
precision=None, connection_id=None,
quantity_unit=None, **kw):
**kw):
"""
Returns a list of inventories for a single or multiple
resources on a single or multiple nodes, grouped by resource,
......@@ -1018,6 +1029,34 @@ class SimulationTool(BaseTool):
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__:
sql_source_list = []
# If no group at all, give a default sort group by
......@@ -1181,7 +1220,9 @@ class SimulationTool(BaseTool):
selection_domain=selection_domain,
selection_report=selection_report, precision=precision,
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)
# Get delta inventory
delta_inventory_line_list = self.Resource_zGetInventoryList(
......@@ -1191,7 +1232,9 @@ class SimulationTool(BaseTool):
selection_domain=selection_domain,
selection_report=selection_report, precision=precision,
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)
# Match & add initial and delta inventories
if src__:
......@@ -1217,12 +1260,9 @@ class SimulationTool(BaseTool):
No group by criterion, regroup everything.
"""
return 'dummy_key'
result_column_id_dict = {}
result_column_id_dict['inventory'] = None
result_column_id_dict['total_quantity'] = 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):
"""
Addition columns of 2 lines and return a line with same
......@@ -1308,7 +1348,9 @@ class SimulationTool(BaseTool):
selection_domain=selection_domain,
selection_report=selection_report, precision=precision,
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)
if src__:
sql_source_list.append(result)
......@@ -1318,28 +1360,22 @@ class SimulationTool(BaseTool):
security.declareProtected(Permissions.AccessContentsInformation,
'getConvertedInventoryList')
def getConvertedInventoryList(self, metric_type, quantity_unit=1,
simulation_period='', **kw):
def getConvertedInventoryList(self, simulation_period='', **kw):
"""
Return list of inventory with a 'converted_quantity' additional column,
which contains the sum of measurements for the specified metric type,
expressed in the 'quantity_unit' unit.
metric_type - category relative url
quantity_unit - int, float or category relative url
metric_type - category
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):
quantity_unit = float(getCategory(quantity_unit, 'quantity_unit')
.getProperty('quantity'))
warn('getConvertedInventoryList is Deprecated, use ' \
'getInventory instead.', DeprecationWarning)
method = getattr(self,'get%sInventoryList' % simulation_period)
return method(quantity_unit=quantity_unit, **kw)
return method(**kw)
security.declareProtected(Permissions.AccessContentsInformation,
'getAllInventoryList')
......
......@@ -31,6 +31,12 @@
<key> <string>_data</string> </key>
<value>
<dictionary>
<item>
<key> <string>convert_quantity_result</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>from_table_list</string> </key>
<value>
......@@ -113,7 +119,7 @@
</value>
</item>
<item>
<key> <string>quantity_unit</string> </key>
<key> <string>quantity_unit_uid</string> </key>
<value>
<dictionary/>
</value>
......@@ -189,7 +195,8 @@
<string>precision</string>
<string>inventory_list</string>
<string>statistic</string>
<string>quantity_unit</string>
<string>convert_quantity_result</string>
<string>quantity_unit_uid</string>
<string>stock_table_id</string>
</list>
</value>
......@@ -224,7 +231,8 @@ output_simulation_state:list\r\n
precision\r\n
inventory_list\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>
</item>
<item>
......@@ -269,18 +277,20 @@ SELECT\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 total_quantity,\n
<dtml-if quantity_unit>\n
SUM(ROUND(<dtml-var stock_table_id>.quantity * measure.quantity, <dtml-var precision>))\n
/ <dtml-sqlvar quantity_unit type=float> AS converted_quantity,\n
</dtml-if>\n
<dtml-if convert_quantity_result>\n
SUM(ROUND(<dtml-var stock_table_id>.quantity * measure.quantity \n
<dtml-if quantity_unit_uid> / quantity_unit_conversion.quantity</dtml-if>, <dtml-var precision>))\n
AS converted_quantity,\n
</dtml-if>\n
SUM(ROUND(<dtml-var stock_table_id>.total_price, <dtml-var precision>)) AS total_price\n
<dtml-else>\n
SUM(<dtml-var stock_table_id>.quantity) AS inventory,\n
SUM(<dtml-var stock_table_id>.quantity) AS total_quantity,\n
<dtml-if quantity_unit>\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>\n
<dtml-if convert_quantity_result>\n
ROUND(SUM(<dtml-var stock_table_id>.quantity * measure.quantity\n
<dtml-if quantity_unit_uid> / quantity_unit_conversion.quantity</dtml-if>), 12)\n
AS converted_quantity,\n
</dtml-if>\n
SUM(<dtml-var stock_table_id>.total_price) AS total_price\n
</dtml-if>\n
<dtml-if inventory_list>\n
......@@ -324,6 +334,11 @@ FROM\n
catalog, <dtml-var stock_table_id>\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
<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-if expr="table_key not in (\'catalog\', stock_table_id)">\n
, <dtml-var table_item> AS <dtml-var table_key>\n
......@@ -353,7 +368,7 @@ WHERE\n
AND <dtml-var "portal_selections.buildSQLExpressionFromDomainSelection(selection_report, strict_membership=1)">\n
</dtml-if>\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
</dtml-if>\n
\n
......@@ -410,18 +425,20 @@ SELECT\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 total_quantity,\n
<dtml-if quantity_unit>\n
SUM(ROUND(<dtml-var stock_table_id>.quantity * measure.quantity, <dtml-var precision>))\n
/ <dtml-sqlvar quantity_unit type=float> AS converted_quantity,\n
</dtml-if>\n
<dtml-if convert_quantity_result>\n
SUM(ROUND(<dtml-var stock_table_id>.quantity * measure.quantity \n
<dtml-if quantity_unit_uid> / quantity_unit_conversion.quantity</dtml-if>, <dtml-var precision>))\n
AS converted_quantity,\n
</dtml-if>\n
SUM(ROUND(<dtml-var stock_table_id>.total_price, <dtml-var precision>)) AS total_price\n
<dtml-else>\n
SUM(<dtml-var stock_table_id>.quantity) AS inventory,\n
SUM(<dtml-var stock_table_id>.quantity) AS total_quantity,\n
<dtml-if quantity_unit>\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>\n
<dtml-if convert_quantity_result>\n
ROUND(SUM(<dtml-var stock_table_id>.quantity * measure.quantity\n
<dtml-if quantity_unit_uid> / quantity_unit_conversion.quantity</dtml-if>), 12)\n
AS converted_quantity,\n
</dtml-if>\n
SUM(<dtml-var stock_table_id>.total_price) AS total_price\n
</dtml-if>\n
<dtml-if inventory_list>\n
......@@ -465,6 +482,11 @@ FROM\n
catalog, <dtml-var stock_table_id>\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
<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-if expr="table_key not in (\'catalog\', stock_table_id)">\n
, <dtml-var table_item> AS <dtml-var table_key>\n
......@@ -494,7 +516,7 @@ WHERE\n
AND <dtml-var "portal_selections.buildSQLExpressionFromDomainSelection(selection_report, strict_membership=1)">\n
</dtml-if>\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
</dtml-if>\n
\n
......
1435
\ No newline at end of file
1436
\ 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
\ No newline at end of file
168
\ No newline at end of file
......@@ -12,6 +12,7 @@ erp5_mysql_innodb/z0_drop_movement
erp5_mysql_innodb/z0_drop_portal_ids
erp5_mysql_innodb/z0_drop_predicate
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_roles_and_users
erp5_mysql_innodb/z0_drop_stock
......@@ -27,6 +28,7 @@ erp5_mysql_innodb/z0_uncatalog_measure
erp5_mysql_innodb/z0_uncatalog_movement
erp5_mysql_innodb/z0_uncatalog_predicate
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_versioning
erp5_mysql_innodb/z_SubCatalogQuery
......@@ -44,6 +46,7 @@ erp5_mysql_innodb/z_catalog_object_list
erp5_mysql_innodb/z_catalog_paths
erp5_mysql_innodb/z_catalog_predicate_category_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_stock_list
erp5_mysql_innodb/z_catalog_translation_list
......@@ -63,6 +66,7 @@ erp5_mysql_innodb/z_create_movement
erp5_mysql_innodb/z_create_portal_ids
erp5_mysql_innodb/z_create_predicate
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_roles_and_users
erp5_mysql_innodb/z_create_stock
......
This diff is collapsed.
......@@ -163,15 +163,27 @@ class TestResource(ERP5TypeTestCase):
if self.quantity_unit_gram is None:
self.quantity_unit_gram = quantity_unit_weight.newContent(
portal_type='Category',
quantity=0.001,
id='gram')
self.quantity_unit_kilo = quantity_unit_weight._getOb('kilo', None)
if self.quantity_unit_kilo is None:
self.quantity_unit_kilo = quantity_unit_weight.newContent(
portal_type='Category',
quantity=1,
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):
"""
......
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