diff --git a/product/ERP5/Tool/SimulationTool.py b/product/ERP5/Tool/SimulationTool.py index 2fe9549d324232382cca2e8cd3060c5da3e96085..b1bdb98c253cd103b38b22aa51a95866827e4b72 100644 --- a/product/ERP5/Tool/SimulationTool.py +++ b/product/ERP5/Tool/SimulationTool.py @@ -46,7 +46,7 @@ from Products.ERP5 import DeliverySolver from Products.ERP5 import TargetSolver from Products.PythonScripts.Utility import allow_class -from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery +from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery, SimpleQuery from Shared.DC.ZRDB.Results import Results from Products.ERP5Type.Utils import mergeZRDBResults @@ -237,54 +237,28 @@ class SimulationTool(BaseTool): return self._buildSimulationStateQuery(simulation_state_dict=simulation_state_dict) def _buildSimulationStateQuery(self, simulation_state_dict, table='stock'): - input_simulation_state = simulation_state_dict.get( - 'input_simulation_state') - output_simulation_state = simulation_state_dict.get( - 'output_simulation_state') simulation_state = simulation_state_dict.get('simulation_state') if simulation_state is not None: - simulation_query = Query(operator='IN', - **{'%s.simulation_state' % (table, ): - simulation_state}) - elif input_simulation_state is not None: - input_quantity_query = ComplexQuery( - ComplexQuery( - Query(**{'%s.quantity' % table: '>=0'}), - Query(**{'%s.is_cancellation' % table: 0}), - operator='AND'), - ComplexQuery( - Query(**{'%s.quantity' % table: '<0'}), - Query(**{'%s.is_cancellation' % table: 1}), - operator='AND'), - operator='OR') - input_simulation_query = Query(operator='IN', - **{'%s.simulation_state' % (table, ): - input_simulation_state}) - simulation_query = ComplexQuery(input_quantity_query, - input_simulation_query, - operator='AND') + return SimpleQuery(**{table + '.simulation_state': simulation_state}) + input_simulation_state = simulation_state_dict.get('input_simulation_state') + if input_simulation_state is not None: + simulation_query = ComplexQuery( + self._getIncreaseQuery(table, 'quantity', True), + SimpleQuery(**{table + '.simulation_state': input_simulation_state}), + operator='AND', + ) + output_simulation_state = simulation_state_dict.get('output_simulation_state') if output_simulation_state is not None: - output_quantity_query = ComplexQuery( - ComplexQuery( - Query(**{'%s.quantity' % table: '<0'}), - Query(**{'%s.is_cancellation' % table: 0}), - operator='AND'), - ComplexQuery( - Query(**{'%s.quantity' % table: '>=0'}), - Query(**{'%s.is_cancellation' % table: 1}), - operator='AND'), - operator='OR') - output_simulation_query = Query(operator='IN', - **{'%s.simulation_state' % (table, ): - output_simulation_state}) - output_query = ComplexQuery(output_quantity_query, - output_simulation_query, - operator='AND') - simulation_query = ComplexQuery(simulation_query, output_query, - operator='OR') - else: - simulation_query = None - return simulation_query + simulation_query = ComplexQuery( + simulation_query, + ComplexQuery( + self._getIncreaseQuery(table, 'quantity', False), + SimpleQuery(**{table + '.simulation_state': output_simulation_state}), + operator='AND', + ), + operator='OR' + ) + return simulation_query def _getSimulationStateDict(self, simulation_state=None, omit_transit=0, input_simulation_state=None, @@ -370,112 +344,48 @@ class SimulationTool(BaseTool): simulation_dict['simulation_state'] = output_simulation_state return simulation_dict - def _getOmitQuery(self, query_table=None, omit_input=0, omit_output=0, - omit_asset_increase=0, omit_asset_decrease=0, **kw): - omit_dict = self._getOmitDict(omit_input=omit_input, - omit_output=omit_output, - omit_asset_increase=omit_asset_increase, - omit_asset_decrease=omit_asset_decrease) - return self._buildOmitQuery(query_table=query_table, omit_dict=omit_dict) - - def _buildOmitQuery(self, query_table, omit_dict): - """ - Build a specific query in order to take: - - negatives quantity values if omit_input - - postives quantity values if omit_output - - negatives asset price values if omit_asset_increase - - positives asset price values if omit_asset_decrease - """ - omit_query = None - omit_input = omit_dict.get('input', False) - omit_output = omit_dict.get('output', False) - omit_asset_increase = omit_dict.get('asset_increase', False) - omit_asset_decrease = omit_dict.get('asset_decrease', False) - if omit_input or omit_output\ - or omit_asset_increase or omit_asset_decrease: - # Make sure to check some conditions - condition_expression = \ - "%(query_table)s.node_uid <> %(query_table)s.mirror_node_uid \ - OR %(query_table)s.section_uid <> %(query_table)s.mirror_section_uid \ - OR %(query_table)s.mirror_node_uid IS NULL \ - OR %(query_table)s.mirror_section_uid IS NULL \ - OR %(query_table)s.payment_uid IS NOT NULL \ - " % {'query_table': query_table} - if omit_input: - quantity_query = ComplexQuery( - ComplexQuery( - Query(**{'%s.quantity' % query_table: '<0'}), - Query(**{'%s.is_cancellation' % query_table: 0}), - operator='AND'), - ComplexQuery( - Query(**{'%s.quantity' % query_table: '>=0'}), - Query(**{'%s.is_cancellation' % query_table: 1}), - operator='AND'), - operator='OR') - omit_query = ComplexQuery(quantity_query, condition_expression, - operator='AND') - if omit_output: - quantity_query = ComplexQuery( - ComplexQuery( - Query(**{'%s.quantity' % query_table: '>=0'}), - Query(**{'%s.is_cancellation' % query_table: 0}), - operator='AND'), - ComplexQuery( - Query(**{'%s.quantity' % query_table: '<0'}), - Query(**{'%s.is_cancellation' % query_table: 1}), - operator='AND'), - operator='OR') - output_query = ComplexQuery(quantity_query, condition_expression, - operator='AND') - if omit_query is not None: - omit_query = ComplexQuery(omit_query, output_query, operator='AND') - else: - omit_query = output_query - - if omit_asset_increase: - asset_price_query = ComplexQuery( - ComplexQuery( - Query(**{'%s.total_price' % query_table: '<0'}), - Query(**{'%s.is_cancellation' % query_table: 0}), - operator='AND'), - ComplexQuery( - Query(**{'%s.total_price' % query_table: '>=0'}), - Query(**{'%s.is_cancellation' % query_table: 1}), - operator='AND'), - operator='OR') - asset_increase_query = ComplexQuery(asset_price_query, condition_expression, - operator='AND') - if omit_query is not None: - omit_query = ComplexQuery(omit_query, asset_increase_query, operator='AND') - else: - omit_query = asset_increase_query - - if omit_asset_decrease: - asset_price_query = ComplexQuery( - ComplexQuery( - Query(**{'%s.total_price' % query_table: '>=0'}), - Query(**{'%s.is_cancellation' % query_table: 0}), - operator='AND'), - ComplexQuery( - Query(**{'%s.total_price' % query_table: '<0'}), - Query(**{'%s.is_cancellation' % query_table: 1}), - operator='AND'), - operator='OR') - asset_decrease_query = ComplexQuery(asset_price_query, condition_expression, - operator='AND') - if omit_query is not None: - omit_query = ComplexQuery(omit_query, asset_decrease_query, operator='AND') - else: - omit_query = asset_decrease_query - - return omit_query - - def _getOmitDict(self, omit_input=False, omit_output=False, - omit_asset_increase=False, omit_asset_decrease=False): - return { 'input': omit_input, - 'output': omit_output, - 'asset_increase': omit_asset_increase, - 'asset_decrease': omit_asset_decrease, } + def _getIncreaseQuery(self, table, column, increase, sql_catalog_id=None): + """ + Returns a Query filtering rows depending on whether they represent an + increase or a decrease. + table (string) + Name of table to use as stock table. + column (string) + Name of interesting column. Supported values are: + - total_price for asset price increase/decrease + - quantity for quantity increase (aka input)/decrease (aka output) + increase (bool) + False: decreasing rows are kept + True: increasing rows are kept + sql_catalog_id (string or None) + Idenfitier of an SQLCatalog object relevant to table, or None for + default one. + """ + if column == 'total_price': + dedicated_column = 'is_asset_increase' + elif column == 'quantity': + dedicated_column = 'is_input' + else: + raise ValueError('Unknown column %r' % (column, )) + if self.getPortalObject().portal_catalog.hasColumn( + dedicated_column, + sql_catalog_id, + ): + return SimpleQuery(**{dedicated_column: increase}) + # Dedicated columns are not present, compute on the fly. + return ComplexQuery( + ComplexQuery( + SimpleQuery(comparison_operator='<', **{table + '.' + column: 0}), + SimpleQuery(**{table + '.is_cancellation': increase}), + operator='AND', + ), + ComplexQuery( + SimpleQuery(comparison_operator='>=', **{table + '.' + column: 0}), + SimpleQuery(**{table + '.is_cancellation': not increase}), + operator='AND', + ), + operator='OR', + ) def _generateSQLKeywordDict(self, table='stock', **kw): sql_kw, new_kw = self._generateKeywordDict(**kw) @@ -534,32 +444,54 @@ class SimulationTool(BaseTool): for key, value in new_kw.pop('related_key_dict', {}).iteritems(): new_kw['%s_%s' % (table, key)] = value # Simulation states matched with input and output omission - def joinQueriesIfNeeded(query_a, query_b, operator): - if None not in (query_a, query_b): - return ComplexQuery(query_a, query_b, operator=operator) - elif query_a is not None: - return query_a - return query_b def getSimulationQuery(simulation_dict, omit_dict): simulation_query = self._buildSimulationStateQuery( - simulation_state_dict=simulation_dict, - table=table) - omit_query = self._buildOmitQuery(query_table=table, - omit_dict=omit_dict) - return joinQueriesIfNeeded(query_a=simulation_query, - query_b=omit_query, operator='AND') - regular_query = getSimulationQuery(new_kw.pop('simulation_dict', {}), - new_kw.pop('omit_dict', {})) + simulation_state_dict=simulation_dict, + table=table, + ) + query_list = [ + self._getIncreaseQuery(table, column, value) + for key, column, value in ( + ('input', 'quantity', False), + ('output', 'quantity', True), + ('asset_increase', 'total_price', False), + ('asset_decrease', 'total_price', True), + ) + if omit_dict.get(key) + ] + if query_list: + if simulation_query is not None: + query_list.append(simulation_query) + return ComplexQuery( + "%(query_table)s.node_uid <> %(query_table)s.mirror_node_uid " + "OR %(query_table)s.section_uid <> %(query_table)s.mirror_section_uid " + "OR %(query_table)s.mirror_node_uid IS NULL " + "OR %(query_table)s.mirror_section_uid IS NULL " + "OR %(query_table)s.payment_uid IS NOT NULL" % { + 'query_table': table, + }, + operator='AND', + *query_list + ) + return simulation_query + simulation_query = getSimulationQuery( + new_kw.pop('simulation_dict', {}), + new_kw.pop('omit_dict', {}), + ) reserved_kw = new_kw.pop('reserved_kw', None) if reserved_kw is not None: reserved_query = getSimulationQuery( reserved_kw.pop('simulation_dict', {}), - reserved_kw.pop('omit_dict', {})) - simulation_query = joinQueriesIfNeeded(query_a=regular_query, - query_b=reserved_query, - operator='OR') - else: - simulation_query = regular_query + reserved_kw.pop('omit_dict', {}), + ) + if simulation_query is None: + simulation_query = reserved_query + elif reserved_query is not None: + simulation_query = ComplexQuery( + simulation_query, + reserved_query, + operator='OR', + ) if simulation_query is not None: new_kw['query'] = simulation_query @@ -824,32 +756,32 @@ class SimulationTool(BaseTool): #if len(variation_category_uid_list) : # new_kw['variationCategory'] = variation_category_uid_list - simulation_dict = self._getSimulationStateDict( - simulation_state=simulation_state, - omit_transit=omit_transit, - input_simulation_state=input_simulation_state, - output_simulation_state=output_simulation_state, - transit_simulation_state=transit_simulation_state, - strict_simulation_state=strict_simulation_state) - new_kw['simulation_dict'] = simulation_dict - omit_dict = self._getOmitDict(omit_input=omit_input, - omit_output=omit_output, - omit_asset_increase=omit_asset_increase, - omit_asset_decrease=omit_asset_decrease) - new_kw['omit_dict'] = omit_dict + new_kw['simulation_dict'] = self._getSimulationStateDict( + simulation_state=simulation_state, + omit_transit=omit_transit, + input_simulation_state=input_simulation_state, + output_simulation_state=output_simulation_state, + transit_simulation_state=transit_simulation_state, + strict_simulation_state=strict_simulation_state, + ) + new_kw['omit_dict'] = { + 'input': omit_input, + 'output': omit_output, + 'asset_increase': omit_asset_increase, + 'asset_decrease': omit_asset_decrease, + } if reserved_kw is not None: if not isinstance(reserved_kw, dict): # Not a dict when taken from URL, so, cast is needed # to make pop method available reserved_kw = dict(reserved_kw) - new_reserved_kw = {} - reserved_omit_input = reserved_kw.pop('omit_input',0) - reserved_omit_output = reserved_kw.pop('omit_output',0) - new_reserved_kw['omit_dict'] = self._getOmitDict( - omit_input=reserved_omit_input, - omit_output=reserved_omit_output) - new_reserved_kw['simulation_dict'] = self._getSimulationStateDict(**reserved_kw) - new_kw['reserved_kw'] = new_reserved_kw + new_kw['reserved_kw'] = { + 'omit_dict': { + 'input': reserved_kw.pop('omit_input', False), + 'ouput': reserved_kw.pop('omit_output', False), + }, + 'simulation_dict': self._getSimulationStateDict(**reserved_kw), + } # It is necessary to use here another SQL query (or at least a subquery) # to get _DISTINCT_ uid from predicate_category table. # Otherwise, by using a where_expression, cells which fit conditions