# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. # Jean-Paul Smets-Solanes <jp@nexedi.com> # # 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. # ############################################################################## import zope.interface from AccessControl import ClassSecurityInfo from Products.ERP5Type import Permissions, PropertySheet, interfaces from Products.ERP5.Document.Item import Item from Products.ERP5.mixin.movement_generator import MovementGeneratorMixin from Products.ERP5.mixin.periodicity import PeriodicityMixin class SubscriptionItem(Item, MovementGeneratorMixin, PeriodicityMixin): """ A SubscriptionItem is an Item which expands itself into simulation movements which represent the item future. Examples of subscription items (or subclasses) include: employee paysheet contracts, telecommunication subscriptions, banking service subscriptions, etc """ meta_type = 'ERP5 Subscription Item' portal_type = 'Subscription Item' # Declarative security security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) # Declarative properties property_sheets = ( PropertySheet.Base , PropertySheet.XMLObject , PropertySheet.CategoryCore , PropertySheet.DublinCore , PropertySheet.Price , PropertySheet.Item , PropertySheet.Amount , PropertySheet.Reference , PropertySheet.Periodicity ) # Declarative interfaces zope.interface.implements(interfaces.IExpandable, interfaces.IMovementGenerator, ) # IExpandable interface implementation def expand(self, applied_rule_id=None, force=0, activate_kw=None, **kw): """ Lookup start / stop properties in related Open Order or Path and expand. """ # only try to expand if we are not in draft state if self.getValidationState() == 'draft': # XXX-JPS harcoded return # use hint if provided (but what for ?) XXX-JPS if applied_rule_id is not None: portal_simulation = getToolByName(self, 'portal_simulation') my_applied_rule = portal_simulation[applied_rule_id] else: my_applied_rule = self._getRootAppliedRule(activate_kw=activate_kw) # Pass expand if my_applied_rule is not None: my_applied_rule.expand(activate_kw=activate_kw, **kw) # XXX-JPS why **kw ? def _getRootAppliedRule(self, tested_base_category_list=None, activate_kw=None): """ Returns existing root applied rule or, if none, create a new one a return it """ # Look up if existing applied rule my_applied_rule_list = self.getCausalityRelatedValueList( portal_type='Applied Rule') my_applied_rule = None if len(my_applied_rule_list) == 0: if self.isSimulated(): # No need to create a DeliveryRule # if we are already in the simulation process pass else: # Create a new applied order rule (portal_rules.order_rule) portal_rules = getToolByName(self, 'portal_rules') portal_simulation = getToolByName(self, 'portal_simulation') rule_value_list = portal_rules.searchRuleList(self, tested_base_category_list=tested_base_category_list) if len(rule_value_list) > 1: raise "SimulationError", 'Expandable Document %s has more than one matching'\ ' rule.' % self.getRelativeUrl() if len(rule_value_list): rule_value = rule_value_list[0] my_applied_rule = rule_value.constructNewAppliedRule(portal_simulation, activate_kw=activate_kw) # Set causality my_applied_rule.setCausalityValue(self) # We must make sure this rule is indexed # now in order not to create another one later my_applied_rule.reindexObject(activate_kw=activate_kw) # XXX-JPS removed **kw elif len(my_applied_rule_list) == 1: # Re expand the rule if possible my_applied_rule = my_applied_rule_list[0] else: raise "SimulationError", 'Expandable Document %s has more than one root applied'\ ' rule.' % self.getRelativeUrl() return my_applied_rule # IMovementGenerator interface implementation def getGeneratedMovementList(self, context, movement_list=None, rounding=False): """ Input movement list comes from Open Order XXX this code is duplicated. """ ret = [] rule = context.getSpecialiseValue() for input_movement, business_path in self \ ._getInputMovementAndPathTupleList(context): kw = self._getPropertyAndCategoryList(input_movement, business_path, rule) input_movement_url = input_movement.getRelativeUrl() kw.update({'delivery': input_movement_url}) simulation_movement = context.newContent( portal_type=RuleMixin.movement_type, temp_object=True, **kw) ret.append(simulation_movement) return ret def _getInputMovementList(self, context): """ Generate the list of input movements by looking at all open order lines relating to this subscription item. TODO: clever handling of quantity (based on the nature of resource, ie. float or unit) """ result = [] catalog_tool = getToolByName(self, 'portal_catalog') # Try to find the source open order open_order_movement_list = self.getAggregateRelatedValueList( portal_type="Open Order Line") # XXX-JPS Hard Coded if not open_order_movement_list: return result # Find out which parent open orders explanation_uid_list = map(lambda x:x.getParentUid(), open_order_movement_list) # Instead, should call getDeliveryValue or equivalent open_order_list = catalog_tool.searchResults(uid = explanation_uid_list, validation_state = 'validated') # XXX-JPS hard coding # Now generate movements for each valid open order for movement in open_order_movement_list: if movement.getParentValue().getValidationState() == 'open': # XXX-JPS hard coding resource = movement.getResource() start_date = movement.getStartDate() stop_date = movement.getStopDate() source = movement.getSource() source_section = movement.getSourceSection() destination = movement.getDestination() destination_section = movement.getDestinationSection() # XXX More arrows ? use context instead ? quantity = self.getQuantity() # Is it so ? XXX-JPS quantity_unit = movement.getQuantityUnit() price = movement.getPrice() current_date = start_date while current_date < stop_date: next_date = self.getNextPeriodicalDate(current_date) movement = self.newContent(temp_object=1, portal_type='Sale Order Line', # XXX-JPS Hard Coded resource=resource, quantity=quantity, quantity_unit=quantity_unit, price=price, start_date=current_date, stop_date=next_date, source=source, source_section=source_section, destination=destination, destination_section=destination_section, ) result.append(movement) current_date = next_date # And now return result return result