From cc05c028fd7509b555dd80a7710ee0a74430e81d Mon Sep 17 00:00:00 2001 From: Nicolas Dumazet <nicolas.dumazet@nexedi.com> Date: Thu, 21 Jan 2010 07:07:14 +0000 Subject: [PATCH] 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 --- product/ERP5/Document/Measure.py | 30 +- .../QuantityUnitConversionDefinition.py | 68 +++++ .../Document/QuantityUnitConversionGroup.py | 67 +++++ product/ERP5/Document/Resource.py | 119 +++++++- product/ERP5/Tool/SimulationTool.py | 82 ++++-- .../erp5_core/Resource_zGetInventoryList.xml | 64 +++-- product/ERP5/bootstrap/erp5_core/bt/revision | 2 +- ..._quantity_unit_conversion.catalog_keys.xml | 5 + .../z0_drop_quantity_unit_conversion.xml | 103 +++++++ ..._quantity_unit_conversion.catalog_keys.xml | 5 + .../z0_uncatalog_quantity_unit_conversion.xml | 122 ++++++++ ...tity_unit_conversion_list.catalog_keys.xml | 5 + ..._catalog_quantity_unit_conversion_list.xml | 203 +++++++++++++ ..._quantity_unit_conversion.catalog_keys.xml | 5 + .../z_create_quantity_unit_conversion.xml | 125 ++++++++ .../erp5_mysql_innodb_catalog/bt/revision | 2 +- .../bt/template_catalog_method_id_list | 4 + product/ERP5/tests/testInventoryAPI.py | 272 +++++++++++++++--- product/ERP5/tests/testResource.py | 16 +- 19 files changed, 1189 insertions(+), 110 deletions(-) create mode 100644 product/ERP5/Document/QuantityUnitConversionDefinition.py create mode 100644 product/ERP5/Document/QuantityUnitConversionGroup.py create mode 100644 product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_drop_quantity_unit_conversion.catalog_keys.xml create mode 100644 product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_drop_quantity_unit_conversion.xml create mode 100644 product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_uncatalog_quantity_unit_conversion.catalog_keys.xml create mode 100644 product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_uncatalog_quantity_unit_conversion.xml create mode 100644 product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_quantity_unit_conversion_list.catalog_keys.xml create mode 100644 product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_quantity_unit_conversion_list.xml create mode 100644 product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_create_quantity_unit_conversion.catalog_keys.xml create mode 100644 product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_create_quantity_unit_conversion.xml diff --git a/product/ERP5/Document/Measure.py b/product/ERP5/Document/Measure.py index e462070eee..393fa02e8f 100644 --- a/product/ERP5/Document/Measure.py +++ b/product/ERP5/Document/Measure.py @@ -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 () diff --git a/product/ERP5/Document/QuantityUnitConversionDefinition.py b/product/ERP5/Document/QuantityUnitConversionDefinition.py new file mode 100644 index 0000000000..20094440c9 --- /dev/null +++ b/product/ERP5/Document/QuantityUnitConversionDefinition.py @@ -0,0 +1,68 @@ +############################################################################## +# +# 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 + diff --git a/product/ERP5/Document/QuantityUnitConversionGroup.py b/product/ERP5/Document/QuantityUnitConversionGroup.py new file mode 100644 index 0000000000..99da221933 --- /dev/null +++ b/product/ERP5/Document/QuantityUnitConversionGroup.py @@ -0,0 +1,67 @@ +############################################################################## +# +# 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 + diff --git a/product/ERP5/Document/Resource.py b/product/ERP5/Document/Resource.py index 8f62398afe..4757fd0d0f 100644 --- a/product/ERP5/Document/Resource.py +++ b/product/ERP5/Document/Resource.py @@ -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 diff --git a/product/ERP5/Tool/SimulationTool.py b/product/ERP5/Tool/SimulationTool.py index 7ffac1ef8b..ab2ceb5a2c 100644 --- a/product/ERP5/Tool/SimulationTool.py +++ b/product/ERP5/Tool/SimulationTool.py @@ -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') diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Resource_zGetInventoryList.xml b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Resource_zGetInventoryList.xml index 1b02be2db8..5552b01972 100644 --- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Resource_zGetInventoryList.xml +++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Resource_zGetInventoryList.xml @@ -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 diff --git a/product/ERP5/bootstrap/erp5_core/bt/revision b/product/ERP5/bootstrap/erp5_core/bt/revision index 052219ca7d..63c889e3b9 100644 --- a/product/ERP5/bootstrap/erp5_core/bt/revision +++ b/product/ERP5/bootstrap/erp5_core/bt/revision @@ -1 +1 @@ -1435 \ No newline at end of file +1436 \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_drop_quantity_unit_conversion.catalog_keys.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_drop_quantity_unit_conversion.catalog_keys.xml new file mode 100644 index 0000000000..baf9e92bd9 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_drop_quantity_unit_conversion.catalog_keys.xml @@ -0,0 +1,5 @@ +<catalog_method> + <item key="sql_clear_catalog" type="int"> + <value>1</value> + </item> +</catalog_method> \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_drop_quantity_unit_conversion.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_drop_quantity_unit_conversion.xml new file mode 100644 index 0000000000..5b8ab3ac40 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_drop_quantity_unit_conversion.xml @@ -0,0 +1,103 @@ +<?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> diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_uncatalog_quantity_unit_conversion.catalog_keys.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_uncatalog_quantity_unit_conversion.catalog_keys.xml new file mode 100644 index 0000000000..d58ef064ea --- /dev/null +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_uncatalog_quantity_unit_conversion.catalog_keys.xml @@ -0,0 +1,5 @@ +<catalog_method> + <item key="sql_uncatalog_object" type="int"> + <value>1</value> + </item> +</catalog_method> \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_uncatalog_quantity_unit_conversion.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_uncatalog_quantity_unit_conversion.xml new file mode 100644 index 0000000000..847b2c201a --- /dev/null +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_uncatalog_quantity_unit_conversion.xml @@ -0,0 +1,122 @@ +<?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> diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_quantity_unit_conversion_list.catalog_keys.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_quantity_unit_conversion_list.catalog_keys.xml new file mode 100644 index 0000000000..93bd4a9bdf --- /dev/null +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_quantity_unit_conversion_list.catalog_keys.xml @@ -0,0 +1,5 @@ +<catalog_method> + <item key="sql_catalog_object_list" type="int"> + <value>1</value> + </item> +</catalog_method> \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_quantity_unit_conversion_list.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_quantity_unit_conversion_list.xml new file mode 100644 index 0000000000..40a9e5caa9 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_quantity_unit_conversion_list.xml @@ -0,0 +1,203 @@ +<?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> diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_create_quantity_unit_conversion.catalog_keys.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_create_quantity_unit_conversion.catalog_keys.xml new file mode 100644 index 0000000000..baf9e92bd9 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_create_quantity_unit_conversion.catalog_keys.xml @@ -0,0 +1,5 @@ +<catalog_method> + <item key="sql_clear_catalog" type="int"> + <value>1</value> + </item> +</catalog_method> \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_create_quantity_unit_conversion.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_create_quantity_unit_conversion.xml new file mode 100644 index 0000000000..06c72417f1 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_create_quantity_unit_conversion.xml @@ -0,0 +1,125 @@ +<?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> diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/bt/revision b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/bt/revision index 2efea5198b..a3090d211b 100644 --- a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/bt/revision +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/bt/revision @@ -1 +1 @@ -167 \ No newline at end of file +168 \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/bt/template_catalog_method_id_list b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/bt/template_catalog_method_id_list index 5aff40c19a..00418b5cb5 100644 --- a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/bt/template_catalog_method_id_list +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/bt/template_catalog_method_id_list @@ -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 diff --git a/product/ERP5/tests/testInventoryAPI.py b/product/ERP5/tests/testInventoryAPI.py index 91332371c5..4214ed4f70 100644 --- a/product/ERP5/tests/testInventoryAPI.py +++ b/product/ERP5/tests/testInventoryAPI.py @@ -2377,24 +2377,81 @@ class TestInventoryDocument(InventoryAPITestCase): [x.path for x in self.resource.getInventoryList( optimisation__=False, mirror_uid=self.mirror_node.getUid())]) - -class TestUnitConversion(InventoryAPITestCase): - QUANTITY_UNIT_CATEGORIES = { - 'unit': {'unit': 1, 'a_few': None}, - 'length': {'m': 1, 'in': .0254}, - 'mass': {'kg': 1, 't': 1000, 'g': .001}, + +class BaseTestUnitConversion(InventoryAPITestCase): + QUANTITY_UNIT_DICT = {} + METRIC_TYPE_CATEGORY_LIST = () + + 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/0', 'unit/1', 'unit/2', + 'unit/lot', 'mass/net', 'mass/nutr/lipid', ) def afterSetUp(self): - InventoryAPITestCase.afterSetUp(self) + BaseTestUnitConversion.afterSetUp(self) self.resource.setQuantityUnitList(('unit/unit', 'length/in')) self.other_resource.setQuantityUnit('mass/g') @@ -2433,54 +2490,176 @@ class TestUnitConversion(InventoryAPITestCase): 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 makeMovement(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() - - makeMovement(2, self.resource, 'colour/green', 'size/big') - makeMovement(789, self.other_resource) - makeMovement(-13, self.resource, 'colour/red', 'size/small', + self.makeMovement(2, self.resource, 'colour/green', 'size/big') + self.makeMovement(789, self.other_resource) + self.makeMovement(-13, self.resource, 'colour/red', 'size/small', '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): - 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(11, simulation('unit')) + self.assertEquals(None, + self.convertedSimulation('unit', + simulation_period='Current')) + self.assertEquals(11, self.convertedSimulation('unit')) - self.assertEquals(11 * 123 - 789, - simulation('mass/net', quantity_unit=.001)) + self.assertEquals(11 * .123 - .789, self.convertedSimulation('mass/net')) 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(): suite = unittest.TestSuite() @@ -2492,6 +2671,7 @@ def test_suite(): suite.addTest(unittest.makeSuite(TestTrackingList)) suite.addTest(unittest.makeSuite(TestInventoryDocument)) suite.addTest(unittest.makeSuite(TestUnitConversion)) + suite.addTest(unittest.makeSuite(TestUnitConversionDefinition)) return suite # vim: foldmethod=marker diff --git a/product/ERP5/tests/testResource.py b/product/ERP5/tests/testResource.py index a54343b6f8..07fc46a623 100644 --- a/product/ERP5/tests/testResource.py +++ b/product/ERP5/tests/testResource.py @@ -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): """ -- 2.30.9