def assertPRoles(ob, permission, expect): """ Asserts that in the context of ob, the given permission maps to the given roles. """ pr = PermissionRole(permission) roles = pr.__of__(ob) roles2 = aq_base(pr).__of__(ob) assert roles == roles2 or tuple(roles) == tuple(roles2), ( 'Different methods of checking roles computed unequal results') same = 0 if roles: # When verbose security is enabled, permission names are # embedded in the computed roles. Remove the permission # names. roles = [r for r in roles if not r.endswith('_Permission')] if roles is None or expect is None: if (roles is None or tuple(roles) == ('Anonymous',)) and ( expect is None or tuple(expect) == ('Anonymous',)): same = 1 else: got = {} for r in roles: got[r] = 1 expected = {} for r in expect: expected[r] = 1 if got == expected: # Dict compare does the Right Thing. same = 1 assert same, 'Expected roles: %s, got: %s' % (`expect`, `roles`)
def assertPRoles(ob, permission, expect): """ Asserts that in the context of ob, the given permission maps to the given roles. """ pr = PermissionRole(permission) roles = pr.__of__(ob) roles2 = aq_base(pr).__of__(ob) assert roles == roles2 or tuple(roles) == tuple(roles2), ( 'Different methods of checking roles computed unequal results') same = 0 if roles: # When verbose security is enabled, permission names are # embedded in the computed roles. Remove the permission # names. roles = [r for r in roles if not r.endswith('_Permission')] if roles is None or expect is None: if (roles is None or tuple(roles) == ('Anonymous', )) and \ (expect is None or tuple(expect) == ('Anonymous', )): same = 1 else: got = {} for r in roles: got[r] = 1 expected = {} for r in expect: expected[r] = 1 if got == expected: # Dict compare does the Right Thing. same = 1 assert same, 'Expected roles: %r, got: %r' % (expect, roles)
def register_constructor_permission(self, permission, meta_type, instance_class): if permission is None: permission = "Add %ss" % (meta_type or instance_class.meta_type) if isinstance(permission, tuple): permission, default = permission else: default = ('Manager', ) pr = PermissionRole(permission, default) registerPermissions(((permission, (), default), )) return pr, permission
def assertPRoles(ob, permission, expect): """ Asserts that in the context of ob, the given permission maps to the given roles. """ pr = PermissionRole(permission) roles = pr.__of__(ob) roles2 = aq_base(pr).__of__(ob) assert roles == roles2 or tuple(roles) == tuple(roles2), ( 'Different methods of checking roles computed unequal results') same = 0 if roles is None or expect is None: if (roles is None or tuple(roles) == ('Anonymous',)) and ( expect is None or tuple(expect) == ('Anonymous',)): same = 1 else: got = {} for r in roles: got[r] = 1 expected = {} for r in expect: expected[r] = 1 if got == expected: # Dict compare does the Right Thing. same = 1 assert same, 'Expected roles: %s, got: %s' % (`expect`, `roles`)
def _update_MembershipTool_searchMembers_permission(): """ Repair Collector #189 by careful surgery on the class dictionary of MembershipTool. """ new_permissions = [] for k, v in MTool.__ac_permissions__: if k == View: new_v = [x for x in v if x != 'searchMembers'] v = tuple(new_v) new_permissions.append((k, v)) new_permissions.append((ListPortalMembers, ('searchMembers', ))) MTool.__ac_permissions__ = tuple(new_permissions) MTool.searchMembers__roles__ = PermissionRole(ListPortalMembers, ('Manager', )) LOG( "CMFHotfix_20031026", INFO, "Updated permission on " + "CMFCore.MembershipTool.MembershipTool.searchMembers" + " from 'View' to 'List portal members'")
def registerClass(self, instance_class=None, meta_type='', permission=None, constructors=(), icon=None, permissions=None, legacy=(), visibility="Global", interfaces=_marker, container_filter=None): """Register a constructor Keyword arguments are used to provide meta data: instance_class -- The class of the object that will be created. This is not currently used, but may be used in the future to increase object mobility. meta_type -- The kind of object being created This appears in add lists. If not specified, then the class meta_type will be used. permission -- The permission name for the constructors. If not specified, then a permission name based on the meta type will be used. constructors -- A list of constructor methods A method can be a callable object with a __name__ attribute giving the name the method should have in the product, or the method may be a tuple consisting of a name and a callable object. The method must be picklable. The first method will be used as the initial method called when creating an object. icon -- No longer used. permissions -- Additional permissions to be registered If not provided, then permissions defined in the class will be registered. legacy -- A list of legacy methods to be added to ObjectManager for backward compatibility visibility -- "Global" if the object is globally visible, None else interfaces -- a list of the interfaces the object supports container_filter -- function that is called with an ObjectManager object as the only parameter, which should return a true object if the object is happy to be created in that container. The filter is called before showing ObjectManager's Add list, and before pasting (after object copy or cut), but not before calling an object's constructor. """ pack = self.__pack initial = constructors[0] productObject = self.__prod pid = productObject.id if permissions: if isinstance(permissions, str): # You goofed it! raise TypeError( 'Product context permissions should be a ' 'list of permissions not a string', permissions) for p in permissions: if isinstance(p, tuple): p, default = p registerPermissions(((p, (), default), )) else: registerPermissions(((p, ()), )) ############################################################ # Constructor permission setup if permission is None: permission = "Add %ss" % (meta_type or instance_class.meta_type) if isinstance(permission, tuple): permission, default = permission else: default = ('Manager', ) pr = PermissionRole(permission, default) registerPermissions(((permission, (), default), )) ############################################################ OM = ObjectManager for method in legacy: if isinstance(method, tuple): name, method = method aliased = 1 else: name = method.__name__ aliased = 0 if name not in OM.__dict__: setattr(OM, name, method) setattr(OM, name + '__roles__', pr) if aliased: # Set the unaliased method name and its roles # to avoid security holes. XXX: All "legacy" # methods need to be eliminated. setattr(OM, method.__name__, method) setattr(OM, method.__name__ + '__roles__', pr) if isinstance(initial, tuple): name, initial = initial else: name = initial.__name__ fd = getattr(pack, '__FactoryDispatcher__', None) if fd is None: class __FactoryDispatcher__(FactoryDispatcher): "Factory Dispatcher for a Specific Product" fd = pack.__FactoryDispatcher__ = __FactoryDispatcher__ if not hasattr(pack, '_m'): pack._m = AttrDict(fd) m = pack._m if interfaces is _marker: if instance_class is None: interfaces = () else: interfaces = tuple(implementedBy(instance_class)) Products.meta_types = Products.meta_types + ( { 'name': meta_type or instance_class.meta_type, # 'action': The action in the add drop down in the ZMI. This is # currently also required by the _verifyObjectPaste # method of CopyContainers like Folders. 'action': ('manage_addProduct/%s/%s' % (pid, name)), # 'product': product id 'product': pid, # 'permission': Guards the add action. 'permission': permission, # 'visibility': A silly name. Doesn't have much to do with # visibility. Allowed values: 'Global', None 'visibility': visibility, # 'interfaces': A tuple of oldstyle and/or newstyle interfaces. 'interfaces': interfaces, 'instance': instance_class, 'container_filter': container_filter }, ) m[name] = initial m[name + '__roles__'] = pr for method in constructors[1:]: if isinstance(method, tuple): name, method = method else: name = os.path.split(method.__name__)[-1] if name not in productObject.__dict__: m[name] = method m[name + '__roles__'] = pr
def testPermissionRoleSupportsGetattr(self): a = PermissionRole('a') self.assertTrue(getattr(a, '__roles__') == ('Manager', )) self.assertTrue(getattr(a, '_d') == ('Manager', )) self.assertTrue(getattr(a, '__name__') == 'a') self.assertTrue(getattr(a, '_p') == '_a_Permission')
def patch_pas(): # sort alphabetically by patched/added method name wrap_method(PluggableAuthService, '_delOb', _delOb) wrap_method( PluggableAuthService, '_getAllLocalRoles', _getAllLocalRoles, add=True, ) wrap_method(PluggableAuthService, '_doAddGroup', _doAddGroup, add=True) wrap_method(PluggableAuthService, '_doAddUser', _doAddUser) wrap_method(PluggableAuthService, '_doChangeGroup', _doChangeGroup, add=True) wrap_method(PluggableAuthService, '_doChangeUser', _doChangeUser, add=True) wrap_method(PluggableAuthService, '_doDelGroups', _doDelGroups, add=True) wrap_method(PluggableAuthService, '_doDelUser', _doDelUser, add=True) wrap_method(PluggableAuthService, '_doDelUsers', _doDelUsers, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, '_getLocalRolesForDisplay', _getLocalRolesForDisplay, add=True) wrap_method(PluggableAuthService, '_updateGroup', _updateGroup, add=True) wrap_method(PluggableAuthService, 'addRole', addRole, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method( PluggableAuthService, 'authenticate', authenticate, add=True, roles=(), ) wrap_method(PluggableAuthService, 'canListAllGroups', canListAllGroups, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'canListAllUsers', canListAllUsers, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'credentialsChanged', credentialsChanged, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method( PluggableAuthService, 'getAllLocalRoles', getAllLocalRoles, add=True, ) wrap_method(PluggableAuthService, 'getGroup', getGroup, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'getGroupById', getGroupById, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'getGroupByName', getGroupByName, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'getGroupIds', getGroupIds, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'getGroupNames', getGroupNames, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'getGroups', getGroups, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method( PluggableAuthService, 'getLocalRolesForDisplay', getLocalRolesForDisplay, add=True, ) wrap_method( PluggableAuthService, 'getUserIds', getUserIds, add=True, deprecated="Inefficient GRUF wrapper, use IUserIntrospection instead.") wrap_method( PluggableAuthService, 'getUserNames', getUserNames, add=True, deprecated="Inefficient GRUF wrapper, use IUserIntrospection instead.") wrap_method(PluggableAuthService, 'getUsers', getUsers, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'getPureUsers', getUsers, add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'userFolderAddUser', postonly(userFolderAddUser), add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'userFolderDelUsers', postonly(_doDelUsers), add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'userFolderEditGroup', postonly(_doChangeGroup), add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'userFolderEditUser', postonly(_doChangeUser), add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'userFolderDelGroups', postonly(_doDelGroups), add=True, roles=PermissionRole(ManageUsers, ('Manager', ))) wrap_method(PluggableAuthService, 'userSetGroups', _userSetGroups, add=True, deprecated="Method from GRUF was removed.") wrap_method(PluggableAuthService, 'userSetPassword', userSetPassword, add=True, roles=PermissionRole(ManageUsers, ('Manager', )))
def InitializeClass(self): from AccessControl.Permission import registerPermissions from AccessControl.PermissionRole import PermissionRole dict=self.__dict__ have=dict.has_key ft=type(InitializeClass) dict_items=dict.items() for name, v in dict_items: if getattr(v, '_need__name__', 0): d = v.__dict__ oldname = d.get('__name__', '') if d.get('_implicit__name__', 0): # Already supplied a name. if name != oldname: # Tried to implicitly assign a different name! try: classname = '%s.%s' % ( self.__module__, self.__name__) except AttributeError: classname = `self` logging.getLogger("Init").warning( 'Ambiguous name for method of %s: %r != %r', classname, d['__name__'], name) else: # Supply a name implicitly so that the method can # find the security assertions on its container. v._implicit__name__ = 1 v.__name__ = name if name=='manage' or name[:7]=='manage_': name=name+'__roles__' if not have(name): setattr(self, name, ('Manager',)) elif name=='manage' or name[:7]=='manage_' and type(v) is ft: name=name+'__roles__' if not have(name): setattr(self, name, ('Manager',)) # Look for a SecurityInfo object on the class. If found, call its # apply() method to generate __ac_permissions__ for the class. We # delete the SecurityInfo from the class dict after it has been # applied out of paranoia. for key, value in dict_items: if hasattr(value, '__security_info__'): security_info=value security_info.apply(self) delattr(self, key) break if self.__dict__.has_key('__ac_permissions__'): registerPermissions(self.__ac_permissions__) for acp in self.__ac_permissions__: pname, mnames = acp[:2] if len(acp) > 2: roles = acp[2] pr = PermissionRole(pname, roles) else: pr = PermissionRole(pname) for mname in mnames: setattr(self, mname+'__roles__', pr) if (mname and mname not in ('context', 'request') and not hasattr(self, mname)): # don't complain about context or request, as they are # frequently not available as class attributes logging.getLogger("Init").warning( "Class %s.%s has a security declaration for " "nonexistent method %r", self.__module__, self.__name__, mname)
class Movement(XMLObject, Amount, CompositionMixin, AmountGeneratorMixin): """ The Movement class allows to implement ERP5 universal accounting model. Movement instances are used in different situations: - Orders: Movement instances are use as a documentary object to define quantities in orders - Deliveries: movements track the actual transfer of resources in the past (accounting) or in the future (planning / budgetting) For example, the following objects are Orders: - a purchase order (the document we send to a supplier when we need some goods) - a sales order (the document we ask our customer to sign to confirm a sale) - a production order (the document we send to the workshop to confirm we need some operation / service to be achieved) Orders allow to describe a target, but can not be used to account the reality of actual transfered quantities. This is not the case for Deliveries: - an invoice (a delivery of money between abstract accounts) - a packing list (ie. a delivery of goods shipped) - a delivery report (ie. a delivery of goods received) - a production report (ie. a delivery of service) - a T/T report (a delivery of money between reals accounts) For planning, the following approach is used: 1- Movements from an order are never modified once the order is confirmed. This is a rule. An Order is like a contract. It can only be amended, if all parties agree. 2- Movements in a delivery may exist on their own (ex. an accounting transaction). Past movements can not be modified. Future movements may or may not be modified When an order is confirmed, the list of "order" movements it contains is copied into "delivery" movements. Each delivery movement contains a "causality" reference to the order it. This allows delivery to be completely different from order (ex. different resource, different date, different quantity) and allows to keep track of the causal relation between a delivery and an order. A delivery document (actually a delivery line) then points to one or more of the "delivery" movements in the simulation. It is possible to know which items have been delivered by making sure each movement in the simulation is associated to a "delivery document". By looking at all "simulation" Delivery movements can be applied the following transformations: - split : one movement is cut into 2 or more movements - submovements : one movement "generates" many other movements. For example, a delivery of goods from the workshop to the stock, will result in a "pull" calculation which generates operations and sourcing based on a BOM document. The causality of each delivery is the "applied rule" which was used to generate submovements One should note that - movements are never joined (because it would break causality and tracability) - movements acquire some context information from their causality Some submovements need no order to be "confirmed". Some submovements need an order to be "confirmed". For example - a submovement which allows to compute the CO2 emissions of a production facility needs no order confirmation (this kind of movement is mostly ised for reporting) - a submovement which takes some goods in a stock and brings them to a workshop needs some "stock movement" order to be "confirmed" - a submovement which requires someone to take his time for some operation nees a "service order" to be confirmed This means that the simulation process must make a distinction between different workflows applicable to a movement. For movements which require an order to be confirmed, the workflow involves the following steps: - an order is automaticaly generated, with "order movements" which become "causalities" for delivery movements (XXX this sound strange...) - each order movement is associated to one of the delivery As a result, a delivery movement which requires an order may have 2 causalities - one causality (ie. application of a rule) - another causality (ie. confirmation in an order) Each causality may define its own context. One context may be related for example to a customer request, another context may be related to an "internal customer" request (eg. the production manager). Context may eventually conflict each other. In a customer oriented company, movements should probably always be stored within the customer order and acquire from the order all context information. In a mass production facility, context may be defined in more than one location. This is an incentive for putting all movements in a simulation "box". The second approach is chosen for documentary consistency approach : in ERP5, documents rules, can be synchronized. Simulation can not be synchronized TODO: - consider creating a class GeneratedMovement and move some superfluous code to it """ meta_type = 'ERP5 Movement' portal_type = 'Movement' add_permission = Permissions.AddPortalContent # Declarative security security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) # Declarative interfaces zope.interface.implements(interfaces.IAmountGenerator, interfaces.IVariated, interfaces.IMovement) # Declarative properties property_sheets = ( PropertySheet.Base, PropertySheet.SimpleItem, PropertySheet.CategoryCore, PropertySheet.Amount, PropertySheet.Reference, PropertySheet.Task, PropertySheet.Arrow, PropertySheet.Movement, PropertySheet.Price, PropertySheet. Simulation # XXX-JPS property should be moved to GeneratedMovement class ) def isPropertyRecorded( self, k): # XXX-JPS method should be moved to GeneratedMovement class return False security.declareProtected(Permissions.AccessContentsInformation, 'isMovement') def isMovement(self): return 1 security.declareProtected(Permissions.AccessContentsInformation, 'isAccountable') def isAccountable(self): return True security.declareProtected(Permissions.AccessContentsInformation, 'isMovingItem') def isMovingItem(self, item): type_based_script = self._getTypeBasedMethod('isMovingItem') if type_based_script: return type_based_script(item) return False security.declareProtected(Permissions.AccessContentsInformation, 'getMovedItemUidList') def getMovedItemUidList(self): """This method returns an uid list of items """ return [item.getUid() for item in self.getAggregateValueList() \ if self.isMovingItem(item)] # Pricing methods def _getPrice(self, context): context = self.asContext(context=context, quantity=self.getConvertedQuantity()) operand_dict = self.getPriceCalculationOperandDict(context=context) if operand_dict is not None: price = operand_dict['price'] resource = self.getResourceValue() quantity_unit = self.getQuantityUnit() if price is not None and quantity_unit and resource is not None: return resource.convertQuantity( price, quantity_unit, resource.getDefaultQuantityUnit(), self.getVariationCategoryList()) return price def _getTotalPrice(self, default=None, context=None, fast=0, **kw): price = self.getPrice(context=context) quantity = self.getQuantity() if isinstance(price, (int, float)) and \ isinstance(quantity, (int, float)): return quantity * price else: return default def _getBaseUnitPrice(self, context): # Override Amount._getBaseUnitPrice to use Movement's # getPriceCalculationOperandDict instead of Resource's. operand_dict = context.getPriceCalculationOperandDict(context=context) if operand_dict is not None: base_unit_price = operand_dict.get('base_unit_price', None) return base_unit_price security.declareProtected(Permissions.AccessContentsInformation, 'getPriceCalculationOperandDict') def getPriceCalculationOperandDict(self, default=None, context=None, **kw): """Return a dict object which contains operands used for price calculation. The returned items depend on a site configuration, because this will invoke a custom script at the end. The only assumption is that the dict must contain a key 'price' which represents the final result of the price calculation. The purpose is to obtain descriptive information to notify the user of how a price is calculated in details, in particular, for invoices and quotations. So a script which is eventually called should provide all values required for generating such reports (e.g. a price, a price without a discount, and a discount). """ # First, try a type-based method, and if not present, use # the good-old-days way (which only returns a final result). if context is None: context = self method = context._getTypeBasedMethod('getPriceCalculationOperandDict') if method is None: # Try this, because when the context is an instance of a derived # class of Movement, Movement_getPriceCalculationOperandDict is # not searched. method = getattr(context, 'Movement_getPriceCalculationOperandDict', None) if method is not None: operand_dict = unrestricted_apply(method, kw=kw) if operand_dict is None: return default assert 'price' in operand_dict return operand_dict return {'price': context.Movement_lookupPrice()} security.declareProtected(Permissions.AccessContentsInformation, 'getPrice') def getPrice(self, default=None, context=None, evaluate=1, **kw): """ Get the Price in the context. If price is not stored locally, lookup a price and store it. FIXME: Don't trust this docstring, this method is not at all using the passed context, but uses this movement as context. """ # XXX As all accessors can recieve the default value as first positional # argument, so we changed the first positional argument from context to # default. Here we try to provide backward compatibility for scripts # passing the context as first positional argument, and advice them to use: # context.getPrice(context=context) # instead of: # context.getPrice(context) if isinstance(default, Base): msg = 'getPrice first argument is supposed to be the default value'\ ' accessor, the context should be passed as with the context='\ ' keyword argument' warn(msg, DeprecationWarning) LOG('ERP5', WARNING, msg) context = default default = None if len(kw): warn( 'Passing keyword arguments to Movement.getPrice has no effect', DeprecationWarning) local_price = self._baseGetPrice() if local_price is None and evaluate: # We must find a price for this movement local_price = self._getPrice(context=self) return local_price security.declareProtected(Permissions.AccessContentsInformation, 'getTotalPrice') def getTotalPrice(self, default=0.0, context=None, REQUEST=None, fast=None, **kw): """Return the total price in the context. The optional parameter "fast" is for compatibility, and will be ignored. """ # see getPrice if isinstance(default, Base) and context is None: msg = 'getTotalPrice first argument is supposed to be the default value'\ ' accessor, the context should be passed as with the context='\ ' keyword argument' warn(msg, DeprecationWarning) LOG('ERP5', WARNING, msg) context = default default = None tmp_context = self.asContext(context=context, REQUEST=REQUEST, **kw) result = self._getTotalPrice(default=default, context=tmp_context, fast=fast, **kw) method = self._getTypeBasedMethod('convertTotalPrice') if method is None: return result return method(result) security.declareProtected(Permissions.AccessContentsInformation, 'getTotalQuantity') def getTotalQuantity(self, default=0.0): """ Returns the quantity if no cell or the total quantity if cells """ return self.getQuantity(default=default) # Industrial price API security.declareProtected(Permissions.AccessContentsInformation, 'getIndustrialPrice') def getIndustrialPrice(self): """ Calculates industrial price in context of this movement """ resource = self.getResourceValue() if resource is not None: return resource.getIndustrialPrice(context=self) return None # Asset price calculation security.declareProtected(Permissions.AccessContentsInformation, 'getSourceInventoriatedTotalAssetPrice') def getSourceInventoriatedTotalAssetPrice(self): """ Returns a price which can be used to calculate stock value (asset) Asset price is used for calculation of inventory asset value and for accounting If the asset price is specified (as in accounting for multi-currency), then it is returned. If no asset price is specified, then we use the price as defined on the line, but only for incoming quantities (purchase price, industrial price, etc.). For outgoing quantities, it is the responsability of database to calculate asset prices based on calculation rules (FIFO, LIFO, AVERAGE, etc.). """ # This is what we use for accounting result = self.getSourceTotalAssetPrice() if result is not None: return result quantity = self.getQuantity() if quantity: source_asset_price = self.getSourceAssetPrice() if source_asset_price: return source_asset_price * -quantity return None security.declareProtected(Permissions.AccessContentsInformation, 'getSourceInventoriatedTotalAssetDebit') def getSourceInventoriatedTotalAssetDebit(self): """ Returns the debit part of inventoriated source total asset price. """ result = self.getSourceInventoriatedTotalAssetPrice() if result is not None: if (result > 0) ^ bool(self.isCancellationAmount()): return result return 0.0 security.declareProtected(Permissions.AccessContentsInformation, 'getSourceInventoriatedTotalAssetCredit') def getSourceInventoriatedTotalAssetCredit(self): """ Returns the credit part of inventoriated source total asset price. """ result = self.getSourceInventoriatedTotalAssetPrice() if result is not None: if (result < 0) ^ bool(self.isCancellationAmount()): return -result return 0.0 security.declareProtected(Permissions.AccessContentsInformation, 'getDestinationInventoriatedTotalAssetPrice') def getDestinationInventoriatedTotalAssetPrice(self): """ Returns a price which can be used to calculate stock value (asset) Asset price is used for calculation of inventory asset value and for accounting """ # This is what we use for accounting result = self.getDestinationTotalAssetPrice() if result is not None: return result quantity = self.getQuantity() if quantity: destination_asset_price = self.getDestinationAssetPrice() if destination_asset_price: return destination_asset_price * quantity return None security.declareProtected(Permissions.AccessContentsInformation, 'getDestinationInventoriatedTotalAssetDebit') def getDestinationInventoriatedTotalAssetDebit(self): """ Returns the debit part of inventoriated destination total asset price. """ result = self.getDestinationInventoriatedTotalAssetPrice() if result is not None: if (result > 0) ^ bool(self.isCancellationAmount()): return result return 0.0 security.declareProtected(Permissions.AccessContentsInformation, 'getDestinationInventoriatedTotalAssetCredit') def getDestinationInventoriatedTotalAssetCredit(self): """ Returns the credit part of inventoriated destination total asset price. """ result = self.getDestinationInventoriatedTotalAssetPrice() if result is not None: if (result < 0) ^ bool(self.isCancellationAmount()): return -result return 0.0 security.declareProtected(Permissions.AccessContentsInformation, 'getSourceAssetPrice') def getSourceAssetPrice(self): """ Returns the price converted to the currency of the source section This will be implemeted by calling currency conversion on currency resources """ type_based_script = self._getTypeBasedMethod('getSourceAssetPrice') if type_based_script: return type_based_script() return self._getAssetPrice(section=self.getSourceSectionValue(), date=self.getStartDate()) security.declareProtected(Permissions.AccessContentsInformation, 'getDestinationAssetPrice') def getDestinationAssetPrice(self): """ Returns the price converted to the currency of the destination section """ type_based_script = self._getTypeBasedMethod( 'getDestinationAssetPrice') if type_based_script: return type_based_script() return self._getAssetPrice(section=self.getDestinationSectionValue(), date=self.getStopDate()) def _getAssetPrice(self, section, date): price = self.getPrice() if section is None or not price or getattr( section.aq_base, 'getPriceCurrencyValue', None) is None: return price currency_value = self.getPriceCurrencyValue() if currency_value: section_currency = section.getPriceCurrency() if section_currency: exchange_rate = getExchangeRate(currency_value, section_currency, date) if exchange_rate: return exchange_rate * price return price # Causality computation security.declareProtected(Permissions.AccessContentsInformation, 'isConvergent') def isConvergent(self): """ Returns true if movement is not divergent """ return bool(not self.isDivergent()) security.declareProtected(Permissions.AccessContentsInformation, 'isDivergent') def isDivergent(self): """Return True if this movement diverges from the its simulation. """ for simulation_movement in self.getDeliveryRelatedValueList(): if simulation_movement.isDivergent(): return True return False security.declareProtected(Permissions.AccessContentsInformation, 'getDivergenceList') def getDivergenceList(self): """ Return a list of messages that contains the divergences """ divergence_list = [] for simulation_movement in self.getDeliveryRelatedValueList(): divergence_list.extend(simulation_movement.getDivergenceList()) return divergence_list security.declareProtected(Permissions.AccessContentsInformation, 'getExplanation') def getExplanation(self): """ Returns the relative_url of the explanation of this movement. """ explanation = self.getExplanationValue() if explanation is not None: return explanation.getRelativeUrl() security.declareProtected(Permissions.AccessContentsInformation, 'getExplanationUid') def getExplanationUid(self): """ Returns the uid of the explanation of this movement. """ explanation = self.getExplanationValue() if explanation is not None: return explanation.getUid() security.declareProtected(Permissions.AccessContentsInformation, 'getExplanationValue') def getExplanationValue(self): """ Returns the object explanation of this movement. """ try: return self.getRootDeliveryValue() except AttributeError: return None security.declareProtected(Permissions.AccessContentsInformation, 'getExplanationTitle') def getExplanationTitle(self, default=''): """ Returns the title of the explanation of this movement. """ explanation_value = self.getExplanationValue() if explanation_value is not None: return explanation_value.getTitle() return default security.declareProtected(Permissions.AccessContentsInformation, 'getExplanationReference') def getExplanationReference(self, default=''): """ Returns the reference of the explanation of this movement. """ explanation_value = self.getExplanationValue() if explanation_value is not None: return explanation_value.getReference() return default security.declareProtected(Permissions.AccessContentsInformation, 'getRootCausalityValueList') def getRootCausalityValueList(self): """ Returns the initial causality value for this movement. This method will look at the causality and check if the causality has already a causality """ return self.getExplanationValue().getRootCausalityValueList() # Simulation security.declareProtected(Permissions.AccessContentsInformation, 'isSimulated') def isSimulated(self): # 'order' category is deprecated. it is kept for compatibility. return (len(self.getDeliveryRelatedValueList()) > 0) or\ (len(self.getOrderRelatedValueList()) > 0) security.declareProtected(Permissions.AccessContentsInformation, 'isGeneratedBySimulation') def isGeneratedBySimulation(self): """ Returns true if the movement is linked to a simulation movement whose parent is not a root applied rule, even if the movement is being built. Otherwise, this means the movement is or should be linked to a root simulation movement. """ simulation_movement = self.getDeliveryRelatedValue() return simulation_movement is not None and \ not simulation_movement.getParentValue().isRootAppliedRule() security.declareProtected(Permissions.AccessContentsInformation, 'getSimulationQuantity') def getSimulationQuantity(self): """Computes the quantities in the simulation. """ return sum(m.getQuantity() for m in self.getDeliveryRelatedValueList()) # Debit and credit methods security.declareProtected(Permissions.AccessContentsInformation, 'getSourceDebit') def getSourceDebit(self): """ Return the quantity """ quantity = self.getQuantity() try: quantity = float(quantity) except TypeError: quantity = 0.0 if (quantity < 0) ^ bool(self.isCancellationAmount()): return -quantity return 0.0 security.declareProtected(Permissions.AccessContentsInformation, 'getSourceCredit') def getSourceCredit(self): """ Return the quantity """ quantity = self.getQuantity() try: quantity = float(quantity) except TypeError: quantity = 0.0 if (quantity < 0) ^ bool(self.isCancellationAmount()): return 0.0 return quantity security.declareProtected(Permissions.AccessContentsInformation, 'getDestinationDebit', 'getDestinationCredit') getDestinationDebit = getSourceCredit getDestinationCredit = getSourceDebit security.declareProtected(Permissions.ModifyPortalContent, 'setSourceDebit') def setSourceDebit(self, source_debit): """ Set the quantity """ if source_debit in (None, ''): return try: source_debit = float(source_debit) except TypeError: source_debit = 0.0 self.setCancellationAmount(source_debit < 0) self.setQuantity(-source_debit) security.declareProtected(Permissions.ModifyPortalContent, 'setSourceCredit') def setSourceCredit(self, source_credit): """ Set the quantity """ if source_credit in (None, ''): return try: source_credit = float(source_credit) except TypeError: source_credit = 0.0 self.setCancellationAmount(source_credit < 0) self.setQuantity(source_credit) security.declareProtected(Permissions.ModifyPortalContent, 'setDestinationDebit', 'setDestinationCredit') setDestinationDebit = setSourceCredit setDestinationCredit = setSourceDebit security.declarePrivate('_edit') def _edit(self, edit_order=(), **kw): """Overloaded _edit to support setting debit and credit at the same time, which is required for the GUI. Also sets the variation category list and property dict at the end, because _setVariationCategoryList and _setVariationPropertyDict needs the resource to be set. """ quantity = 0 if 'source_debit' in kw and 'source_credit' in kw: source_credit = kw.pop('source_credit') or 0 source_debit = kw.pop('source_debit') or 0 quantity += (source_credit - source_debit) kw['quantity'] = quantity kw['cancellation_amount'] = (source_credit < 0 or source_debit < 0) if 'destination_debit' in kw and 'destination_credit' in kw: destination_credit = kw.pop('destination_credit') or 0 destination_debit = kw.pop('destination_debit') or 0 quantity += (destination_debit - destination_credit) kw['quantity'] = quantity kw['cancellation_amount'] = (destination_credit < 0 or destination_debit < 0) # If both asset debit and asset credit are passed, we have to take care not # to erase the asset price if one of them is unset. if kw.get('source_asset_debit') or kw.get('source_asset_credit'): if kw.get('source_asset_debit') in (None, ''): kw.pop('source_asset_debit', None) if kw.get('source_asset_credit') in (None, ''): kw.pop('source_asset_credit', None) if kw.get('destination_asset_debit') or kw.get( 'destination_asset_credit'): if kw.get('destination_asset_debit') in (None, ''): kw.pop('destination_asset_debit', None) if kw.get('destination_asset_credit') in (None, ''): kw.pop('destination_asset_credit', None) if not edit_order: edit_order = ( 'variation_category_list', 'variation_property_dict', ) return XMLObject._edit(self, edit_order=edit_order, **kw) # Debit and credit methods for asset security.declareProtected(Permissions.AccessContentsInformation, 'getSourceAssetDebit') def getSourceAssetDebit(self): """ Return the debit part of the source total asset price. This is the same as getSourceDebit where quantity is replaced by source_total_asset_price. This method returns 0 if the total asset price is not set. """ quantity = self.getSourceTotalAssetPrice() try: quantity = float(quantity) except TypeError: quantity = 0.0 if (quantity < 0) ^ bool(self.isCancellationAmount()): return 0.0 return quantity security.declareProtected(Permissions.AccessContentsInformation, 'getSourceAssetCredit') def getSourceAssetCredit(self): """ Return the credit part of the source total asset price. This is the same as getSourceCredit where quantity is replaced by source_total_asset_price. This method returns 0 if the total asset price is not set. """ quantity = self.getSourceTotalAssetPrice() try: quantity = float(quantity) except TypeError: quantity = 0.0 if (quantity < 0) ^ bool(self.isCancellationAmount()): return -quantity return 0.0 security.declareProtected(Permissions.AccessContentsInformation, 'getDestinationAssetDebit') def getDestinationAssetDebit(self): """ Return the debit part of the destination total asset price. This is the same as getDestinationDebit where quantity is replaced by destination_total_asset_price. This method returns 0 if the total asset price is not set. """ quantity = self.getDestinationTotalAssetPrice() try: quantity = float(quantity) except TypeError: quantity = 0.0 if (quantity < 0) ^ bool(self.isCancellationAmount()): return 0.0 return quantity security.declareProtected(Permissions.AccessContentsInformation, 'getDestinationAssetCredit') def getDestinationAssetCredit(self): """ Return the credit part of the destination total asset price. This is the same as getDestinationCredit where quantity is replaced by destination_total_asset_price. This method returns 0 if the total asset price is not set. """ quantity = self.getDestinationTotalAssetPrice() try: quantity = float(quantity) except TypeError: quantity = 0.0 if (quantity < 0) ^ bool(self.isCancellationAmount()): return -quantity return 0.0 security.declareProtected(Permissions.ModifyPortalContent, 'setSourceAssetDebit') def setSourceAssetDebit(self, source_debit): """ Set the source total asset price """ if source_debit in (None, ''): self.setSourceTotalAssetPrice(None) return try: source_debit = float(source_debit) except TypeError: source_debit = 0.0 self.setCancellationAmount(source_debit < 0) self.setSourceTotalAssetPrice(source_debit) security.declareProtected(Permissions.ModifyPortalContent, 'setSourceAssetCredit') def setSourceAssetCredit(self, source_credit): """ Set the source total asset price """ if source_credit in (None, ''): self.setSourceTotalAssetPrice(None) return try: source_credit = float(source_credit) except TypeError: source_credit = 0.0 self.setCancellationAmount(source_credit < 0) self.setSourceTotalAssetPrice(-source_credit) security.declareProtected(Permissions.ModifyPortalContent, 'setDestinationAssetDebit') def setDestinationAssetDebit(self, destination_debit): """ Set the destination total asset price """ if destination_debit in (None, ''): self.setDestinationTotalAssetPrice(None) return try: destination_debit = float(destination_debit) except TypeError: destination_debit = 0.0 self.setCancellationAmount(destination_debit < 0) self.setDestinationTotalAssetPrice(destination_debit) security.declareProtected(Permissions.ModifyPortalContent, 'setDestinationAssetCredit') def setDestinationAssetCredit(self, destination_credit): """ Set the destination total asset price """ if destination_credit in (None, ''): self.setDestinationTotalAssetPrice(None) return try: destination_credit = float(destination_credit) except TypeError: destination_credit = 0.0 self.setCancellationAmount(destination_credit < 0) self.setDestinationTotalAssetPrice(-destination_credit) # Item Access (tracking) security.declareProtected(Permissions.AccessContentsInformation, 'getTrackedItemUidList') def getTrackedItemUidList(self): """ Return a list of uid for related items """ ### XXX We should filter by portal type here return self.getAggregateUidList() # Helper methods to display total quantities as produced / consumed security.declareProtected(Permissions.AccessContentsInformation, 'getProductionTotalQuantity') def getProductionTotalQuantity(self): """ Return the produced quantity """ quantity = self.getTotalQuantity() return self.getProductionQuantity(quantity=quantity) security.declareProtected(Permissions.AccessContentsInformation, 'getConsumptionTotalQuantity') def getConsumptionTotalQuantity(self): """ Return the produced quantity """ quantity = self.getTotalQuantity() return self.getConsumptionQuantity(quantity=quantity) security.declareProtected(Permissions.AccessContentsInformation, 'getSubVariationText') def getSubVariationText(self, **kw): """ Provide a string representation of XXX """ base_category_list = self.getPortalSubVariationBaseCategoryList() portal_type_list = self.getPortalSubVariationTypeList() return_list = [] for base_category in base_category_list: variation_list = self.getAcquiredCategoryMembershipList( base_category, portal_type=portal_type_list, base=1) return_list.extend(variation_list) return "\n".join(return_list) security.declareProtected(Permissions.AccessContentsInformation, 'getParentExplanationValue') def getParentExplanationValue(self): """ This method should be removed as soon as movement groups will be rewritten. It is a temp hack """ return self.getParentValue().getExplanationValue() # SKU vs. CU # security.declareProtected(Permissions.AccessContentsInformation, 'getSourceStandardInventoriatedQuantity') # def getSourceStandardInventoriatedQuantity(self): # """ # The inventoriated quantity converted in a default unit # # For assortments, returns the inventoriated quantity in terms of number of items # in the assortemnt. # # For accounting, returns the quantity converted in a default unit # """ # return self.getStandardInventoriatedQuantity() # security.declareProtected(Permissions.AccessContentsInformation, 'getDestinationStandardInventoriatedQuantity') # def getDestinationStandardInventoriatedQuantity(self): # """ # The inventoriated quantity converted in a default unit # # For assortments, returns the inventoriated quantity in terms of number of items # in the assortemnt. # # For accounting, returns the quantity converted in a default unit # """ # return self.getStandardInventoriatedQuantity() security.declareProtected(Permissions.AccessContentsInformation, 'asMovementList') def asMovementList(self): """ Placeholder method called when indexing a movement. It can be overloaded to generate multiple movements from a single one. It is used for cataloging a movement multiple time in the movement/stock tables. Ex: a movement have multiple destinations. asMovementList returns a list a movement context with different single destination. """ return (self, ) # XXX: Dirty but required for erp5_banking_core getBaobabSourceUid = lambda x: x.getSourceUid() getBaobabSourceUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationUid = lambda x: x.getDestinationUid() getBaobabDestinationUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceSectionUid = lambda x: x.getSourceSectionUid() getBaobabSourceSectionUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationSectionUid = lambda x: x.getDestinationSectionUid() getBaobabDestinationSectionUid__roles__ = PermissionRole(Permissions.View) getBaobabSourcePaymentUid = lambda x: x.getSourcePaymentUid() getBaobabSourcePaymentUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationPaymentUid = lambda x: x.getDestinationPaymentUid() getBaobabDestinationPaymentUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceFunctionUid = lambda x: x.getSourceFunctionUid() getBaobabSourceFunctionUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationFunctionUid = lambda x: x.getDestinationFunctionUid() getBaobabDestinationFunctionUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceProjectUid = lambda x: x.getSourceProjectUid() getBaobabSourceProjectUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationProjectUid = lambda x: x.getDestinationProjectUid() getBaobabDestinationProjectUid__roles__ = PermissionRole(Permissions.View)
class InventoryLine(DeliveryLine): """ An Inventory Line describe the inventory of a resource, by variations. """ meta_type = 'ERP5 Inventory Line' portal_type = 'Inventory Line' add_permission = Permissions.AddPortalContent isInventoryMovement = ConstantGetter('isInventoryMovement', value=True) # Declarative security security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) # Declarative properties property_sheets = (PropertySheet.Base, PropertySheet.XMLObject, PropertySheet.CategoryCore, PropertySheet.Amount, PropertySheet.InventoryMovement, PropertySheet.Task, PropertySheet.Arrow, PropertySheet.Movement, PropertySheet.VariationRange, PropertySheet.ItemAggregation) security.declareProtected(Permissions.AccessContentsInformation, 'getTotalInventory') def getTotalInventory(self): """ Returns the inventory if no cell or the total inventory if cells """ if not self.hasCellContent(): return self.getInventory() else: total_quantity = 0.0 for cell in self.getCellValueList(base_id='movement'): if cell.getInventory() is not None: total_quantity += cell.getInventory() return total_quantity security.declareProtected(Permissions.AccessContentsInformation, 'getQuantity') def getQuantity(self): """ Computes a quantity which allows to reach inventory """ if not self.hasCellContent(): # First check if quantity already exists quantity = self._baseGetQuantity() if quantity not in (0.0, 0, None): return quantity # Make sure inventory is defined somewhere (here or parent) inventory = getattr(aq_base(self), 'inventory', None) if inventory is not None: return inventory return quantity else: return None # Inventory cataloging security.declareProtected(Permissions.AccessContentsInformation, 'getConvertedInventory') def getConvertedInventory(self): """ provides a default inventory value - None since no inventory was defined. """ return self.getInventory() # XXX quantity unit is missing # Required for indexing security.declareProtected(Permissions.AccessContentsInformation, 'getInventoriatedQuantity') def getInventoriatedQuantity(self): """ Take into account efficiency in converted target quantity """ return Movement.getInventoriatedQuantity(self) # XXX: Dirty but required for erp5_banking_core getBaobabSourceUid = lambda x: x.getSourceUid() getBaobabSourceUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationUid = lambda x: x.getDestinationUid() getBaobabDestinationUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceSectionUid = lambda x: x.getSourceSectionUid() getBaobabSourceSectionUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationSectionUid = lambda x: x.getDestinationSectionUid() getBaobabDestinationSectionUid__roles__ = PermissionRole(Permissions.View) getBaobabSourcePaymentUid = lambda x: x.getSourcePaymentUid() getBaobabSourcePaymentUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationPaymentUid = lambda x: x.getDestinationPaymentUid() getBaobabDestinationPaymentUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceFunctionUid = lambda x: x.getSourceFunctionUid() getBaobabSourceFunctionUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationFunctionUid = lambda x: x.getDestinationFunctionUid() getBaobabDestinationFunctionUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceProjectUid = lambda x: x.getSourceProjectUid() getBaobabSourceProjectUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationProjectUid = lambda x: x.getDestinationProjectUid() getBaobabDestinationProjectUid__roles__ = PermissionRole(Permissions.View)
tabs.append({ 'label': 'Doc', 'action': 'showDocumentation', 'help': ('DocFinderTab', 'README.stx') }) return tabs if not hasattr(Tabs, '_old_filtered_manage_options'): Tabs._old_filtered_manage_options = Tabs.filtered_manage_options Tabs.filtered_manage_options = filtered_manage_options showDocumentation = HTMLFile('dtml/showDocumentation', globals()) def analyseDocumentation(self, object, type='scripter', filter=''): return DocFinder(object, type, filter) ViewDocRoles = PermissionRole(ViewDocPermission, ViewDocDefaultRoles) Item.showDocumentation = showDocumentation Item.showDocumentation__roles__ = ViewDocRoles Item.analyseDocumentation = analyseDocumentation Item.analyseDocumentation__roles__ = ViewDocRoles logger = getLogger('DocFinderTab') logger.info('Applied patch version %s.', __version__) except: import traceback traceback.print_exc()
class DeliveryCell(MappedValue, Movement, ImmobilisationMovement): """ A DeliveryCell allows to define specific quantities for each variation of a resource in a delivery line. """ meta_type = 'ERP5 Delivery Cell' portal_type = 'Delivery Cell' isCell = 1 # Declarative security security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) # Declarative properties property_sheets = (PropertySheet.Base, PropertySheet.CategoryCore, PropertySheet.Arrow, PropertySheet.Amount, PropertySheet.Task, PropertySheet.Movement, PropertySheet.Price, PropertySheet.Predicate, PropertySheet.MappedValue, PropertySheet.ItemAggregation) # Declarative interfaces zope.interface.implements(interfaces.IDivergenceController, ) security.declareProtected(Permissions.AccessContentsInformation, 'isPredicate') def isPredicate(self): """Movements are not predicates. """ return False # MatrixBox methods security.declareProtected(Permissions.AccessContentsInformation, 'hasCellContent') def hasCellContent(self, base_id='movement'): """A cell cannot have cell content itself. """ return 0 security.declareProtected(Permissions.AccessContentsInformation, 'isAccountable') def isAccountable(self): """ Returns 1 if this needs to be accounted Only account movements which are not associated to a delivery Whenever delivery is there, delivery has priority """ return self.getParentValue().getParentValue().isAccountable() security.declareProtected(Permissions.AccessContentsInformation, 'getPrice') def getPrice(self, *args, **kw): """ call Movement.getPrice """ return Movement.getPrice(self, *args, **kw) security.declareProtected(Permissions.AccessContentsInformation, 'getTotalPrice') def getTotalPrice(self, default=0.0, *args, **kw): """ call Movement.getTotalPrice """ return Movement.getTotalPrice(self, default=default, *args, **kw) security.declareProtected(Permissions.AccessContentsInformation, 'getRootDeliveryValue') def getRootDeliveryValue(self): """ Returns the root delivery responsible of this cell """ return self.getParentValue().getRootDeliveryValue() security.declareProtected(Permissions.ModifyPortalContent, 'notifyAfterUpdateRelatedContent') def notifyAfterUpdateRelatedContent(self, previous_category_url, new_category_url): """ Membership Crirerions and Category List are same in DeliveryCell Must update it (or change implementation to remove data duplication) """ update_method = self.portal_categories.updateRelatedCategory predicate_value = self.getPredicateValueList() new_predicate_value = map( lambda c: update_method(c, previous_category_url, new_category_url ), predicate_value) self._setPredicateValueList(new_predicate_value) # No reindex needed since uid stable # XXX FIXME: option variation are today not well implemented # This little hack is needed to make the matrixbox working # in DeliveryLine_viewIndustrialPhase # Generic form (DeliveryLine_viewOption) is required def _edit(self, **kw): """ Store variation_category_list, in order to store new value of industrial_phase after. """ edit_order = [ 'variation_category_list', # edit this one first 'item_id_list' ] # this one must be the last edit_order[1:1] = [ x for x in kw.pop('edit_order', ()) if x not in edit_order ] # Base._edit updates unordered properties first edit_order[1:1] = [x for x in kw if x not in edit_order] MappedValue._edit(self, edit_order=edit_order, **kw) # if self.isSimulated(): # self.getRootDeliveryValue().activate().propagateResourceToSimulation() security.declareProtected(Permissions.ModifyPortalContent, 'updateSimulationDeliveryProperties') def updateSimulationDeliveryProperties(self, movement_list=None): """ Set properties delivery_ratio and delivery_error for each simulation movement in movement_list (all movements by default), according to this delivery calculated quantity """ parent = self.getParentValue() if parent is not None: parent = parent.getParentValue() if parent is not None: parent.updateSimulationDeliveryProperties(movement_list, self) security.declareProtected(Permissions.AccessContentsInformation, 'isMovement') def isMovement(self): return 1 security.declareProtected(Permissions.AccessContentsInformation, 'isMovingItem') def isMovingItem(self, item): type_based_script = self._getTypeBasedMethod('isMovingItem') if type_based_script: return type_based_script(item) return self.isAccountable() # Override getQuantityUnitXXX to negate same methods defined in # Amount class. Because cell must acquire quantity unit from line # not from resource. security.declareProtected(Permissions.AccessContentsInformation, 'getQuantityUnitValue') def getQuantityUnitValue(self): return self.getParentValue().getQuantityUnitValue() security.declareProtected(Permissions.AccessContentsInformation, 'getQuantityUnit') def getQuantityUnit(self, checked_permission=None): return self.getParentValue().getQuantityUnit( checked_permission=checked_permission) # XXX: Dirty but required for erp5_banking_core ### Acquire Baobab source / destination uids from parent line getBaobabSourceUid = lambda x: x.getSourceUid() getBaobabSourceUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationUid = lambda x: x.getDestinationUid() getBaobabDestinationUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceSectionUid = lambda x: x.getSourceSectionUid() getBaobabSourceSectionUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationSectionUid = lambda x: x.getDestinationSectionUid() getBaobabDestinationSectionUid__roles__ = PermissionRole(Permissions.View) getBaobabSourcePaymentUid = lambda x: x.getSourcePaymentUid() getBaobabSourcePaymentUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationPaymentUid = lambda x: x.getDestinationPaymentUid() getBaobabDestinationPaymentUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceFunctionUid = lambda x: x.getSourceFunctionUid() getBaobabSourceFunctionUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationFunctionUid = lambda x: x.getDestinationFunctionUid() getBaobabDestinationFunctionUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceProjectUid = lambda x: x.getSourceProjectUid() getBaobabSourceProjectUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationProjectUid = lambda x: x.getDestinationProjectUid() getBaobabDestinationProjectUid__roles__ = PermissionRole(Permissions.View)
def apply_delete_objects_permission_role(klass, name, replacement): setattr(klass, name, PermissionRole('Delete objects', None))
class Delivery(XMLObject, ImmobilisationDelivery, SimulableMixin, CompositionMixin, AmountGeneratorMixin): """ Each time delivery is modified, it MUST launch a reindexing of inventories which are related to the resources contained in the Delivery """ # CMF Type Definition meta_type = 'ERP5 Delivery' portal_type = 'Delivery' isDelivery = ConstantGetter('isDelivery', value=True) # Declarative security security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) # Default Properties property_sheets = ( PropertySheet.Base , PropertySheet.XMLObject , PropertySheet.CategoryCore , PropertySheet.DublinCore , PropertySheet.Task , PropertySheet.Arrow , PropertySheet.Movement , PropertySheet.Delivery , PropertySheet.Reference , PropertySheet.Price ) # Declarative interfaces zope.interface.implements(interfaces.IAmountGenerator, interfaces.IDivergenceController, interfaces.IMovementCollection) security.declareProtected(Permissions.AccessContentsInformation, 'isAccountable') def isAccountable(self): """ Returns 1 if this needs to be accounted Only account movements which are not associated to a delivery Whenever delivery is there, delivery has priority """ return 1 security.declareProtected( Permissions.AccessContentsInformation, 'getTotalPrice') def getTotalPrice(self, fast=0, src__=0, base_contribution=None, rounding=False, **kw): """ Returns the total price for this order if the `fast` argument is set to a true value, then it use SQLCatalog to compute the price, otherwise it sums the total price of objects one by one. So if the order is not in the catalog, getTotalPrice(fast=1) will return 0, this is not a bug. base_contribution must be a relative url of a category. If passed, then fast parameter is ignored. """ if 'portal_type' not in kw: kw['portal_type'] = self.getPortalObject() \ .getPortalDeliveryMovementTypeList() if base_contribution is None: if fast: # XXX fast ignores base_contribution for now, but it should be possible # to use a related key kw['section_uid'] = self.getDestinationSectionUid() kw['stock.explanation_uid'] = self.getUid() return self.getPortalObject()\ .portal_simulation.getInventoryAssetPrice(**kw) result = sum([ line.getTotalPrice(fast=0) for line in self.objectValues(**kw) ]) else: # Find amounts from movements in the delivery. if isinstance(base_contribution, (tuple, list)): base_contribution_list = base_contribution else: base_contribution_list = (base_contribution,) base_contribution_value_list = [] portal_categories = self.portal_categories for relative_url in base_contribution_list: base_contribution_value = portal_categories.getCategoryValue(relative_url) if base_contribution_value is not None: base_contribution_value_list.append(base_contribution_value) if not base_contribution_value_list: # We cannot find any amount so that the result is 0. result = 0 else: matched_movement_list = [ movement for movement in self.getMovementList() if set(movement.getBaseContributionValueList()).intersection(base_contribution_value_list)] if rounding: portal_roundings = self.portal_roundings matched_movement_list = [ portal_roundings.getRoundingProxy(movement) for movement in matched_movement_list] result = sum([movement.getTotalPrice() for movement in matched_movement_list]) method = self._getTypeBasedMethod('convertTotalPrice') if method is not None: return method(result) return result security.declareProtected(Permissions.AccessContentsInformation, 'getTotalNetPrice') def getTotalNetPrice(self, fast=0, src__=0, **kw): """ Same as getTotalPrice, but including Tax and Discount (from legacy simulation). This method is deprecated because it uses deprecated Tax & Discount portal types. You should use getTotalPrice(base_contribution=) instead. """ total_price = self.getTotalPrice(fast=fast, src__=src__, **kw) kw['portal_type'] = self.getPortalObject().getPortalTaxMovementTypeList() return total_price + self.getTotalPrice(fast=fast, src__=src__, **kw) security.declareProtected(Permissions.AccessContentsInformation, 'getTotalQuantity') def getTotalQuantity(self, fast=0, src__=0, **kw): """ Returns the total quantity of this order. if the `fast` argument is set to a true value, then it use SQLCatalog to compute the quantity, otherwise it sums the total quantity of objects one by one. So if the order is not in the catalog, getTotalQuantity(fast=1) will return 0, this is not a bug. """ if 'portal_type' not in kw: kw['portal_type'] = self.getPortalObject() \ .getPortalDeliveryMovementTypeList() if fast: kw['section_uid'] = self.getDestinationSectionUid() kw['stock.explanation_uid'] = self.getUid() return self.getPortalObject().portal_simulation.getInventory(**kw) return sum([ line.getTotalQuantity(fast=0) for line in self.objectValues(**kw) ]) security.declareProtected(Permissions.AccessContentsInformation, 'getDeliveryUid') def getDeliveryUid(self): return self.getUid() security.declareProtected(Permissions.AccessContentsInformation, 'getDeliveryValue') def getDeliveryValue(self): """ Deprecated, we should use getRootDeliveryValue instead """ return self security.declareProtected(Permissions.AccessContentsInformation, 'getRootDeliveryValue') def getRootDeliveryValue(self): """ This method returns the delivery, it is usefull to retrieve the delivery from a line or a cell """ return self security.declareProtected(Permissions.AccessContentsInformation, 'getDelivery') def getDelivery(self): return self.getRelativeUrl() security.declareProtected(Permissions.AccessContentsInformation, '_getMovementList') def _getMovementList(self, portal_type=None, **kw): """ Return a list of movements """ movement_portal_type_set = set( self.getPortalObject().getPortalMovementTypeList()) movement_list = self.objectValues( portal_type=movement_portal_type_set, **kw) if movement_list: if isinstance(portal_type, str): portal_type = portal_type, elif isinstance(portal_type, (list, tuple)): portal_type = set(portal_type) # Browse lines recursively and collect leafs. stack = [iter(movement_list)] movement_list = [] while stack: for sub_object in stack[-1]: content_list = sub_object.objectValues( portal_type=movement_portal_type_set, **kw) if sub_object.hasCellContent(): cell_list = sub_object.getCellValueList() if len(cell_list) != len(content_list): content_list = set(content_list).difference(cell_list) if content_list: stack.append(iter(content_list)) break else: movement_list.extend(x for x in content_list if portal_type is None or x.getPortalType() in portal_type) elif content_list: stack.append(iter(content_list)) break elif portal_type is None or \ sub_object.getPortalType() in portal_type: movement_list.append(sub_object) else: del stack[-1] return movement_list security.declareProtected(Permissions.AccessContentsInformation, 'getMovementList') def getMovementList(self, portal_type=None, **kw): """ Return a list of movements. """ return self._getMovementList(portal_type=portal_type, **kw) security.declareProtected(Permissions.AccessContentsInformation, 'getSimulatedMovementList') def getSimulatedMovementList(self): """ Return a list of simulated movements. This does not contain Container Line or Container Cell. """ return self.getMovementList(portal_type= self.getPortalObject().getPortalSimulatedMovementTypeList()) security.declareProtected(Permissions.AccessContentsInformation, 'getInvoiceMovementList') def getInvoiceMovementList(self): """ Return a list of simulated movements. This does not contain Container Line or Container Cell. """ return self.getMovementList(portal_type= self.getPortalObject().getPortalInvoiceMovementTypeList()) security.declareProtected(Permissions.AccessContentsInformation, 'getContainerList') def getContainerList(self): """ Return a list of root containers. This does not contain sub-containers. """ return self.objectValues(portal_type= self.getPortalObject().getPortalContainerTypeList()) ####################################################### # Causality computation security.declareProtected(Permissions.AccessContentsInformation, 'isConvergent') def isConvergent(self,**kw): """ Returns 0 if the target is not met """ return bool(not self.isDivergent(**kw)) security.declareProtected(Permissions.AccessContentsInformation, 'isSimulated') def isSimulated(self): """ Returns 1 if all non-null movements have a delivery counterpart in the simulation """ for m in self.getMovementList(): if m.getQuantity() and not m.isSimulated(): return 0 return 1 security.declareProtected(Permissions.AccessContentsInformation, 'isDivergent') def isDivergent(self, fast=0, **kw): """Return True if this movement diverges from the its simulation. """ ## Note that fast option was removed. Now, fast=1 is ignored. # Check if the total quantity equals the total of each simulation movement quantity for simulation_movement in self._getAllRelatedSimulationMovementList(): if simulation_movement.isDivergent(): return True return False security.declareProtected(Permissions.AccessContentsInformation, 'getDivergenceList') def getDivergenceList(self, **kw): """ Return a list of messages that contains the divergences """ divergence_list = [] for simulation_movement in self._getAllRelatedSimulationMovementList(): divergence_list.extend(simulation_movement.getDivergenceList()) return divergence_list security.declareProtected(Permissions.AccessContentsInformation, 'updateCausalityState') @UnrestrictedMethod def updateCausalityState(self, solve_automatically=True, **kw): """ This is often called as an activity, it will check if the deliver is convergent, and if so it will put the delivery in a solved state, if not convergent in a diverged state """ isTransitionPossible = \ self.getPortalObject().portal_workflow.isTransitionPossible if isTransitionPossible(self, 'diverge') and \ isTransitionPossible(self, 'converge'): if self.isDivergent(**kw): if solve_automatically and \ isTransitionPossible(self, 'solve_automatically'): self.solveAutomatically() else: self.diverge() else: self.converge() def updateSimulation(self, calculate=False, **kw): if calculate: path = self.getPath() self.activate( after_tag='build:'+path, after_path_and_method_id=(path, '_localBuild'), ).updateCausalityState() if kw: super(Delivery, self).updateSimulation(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'splitAndDeferMovementList') def splitAndDeferMovementList(self, start_date=None, stop_date=None, movement_uid_list=[], delivery_solver=None, target_solver='CopyToTarget', delivery_builder=None): """ this method will unlink and delete movements in movement_uid_list and rebuild a new Packing List with them. 1/ change date in simulation, call TargetSolver and expand 2/ detach simulation movements from to-be-deleted movements 3/ delete movements XXX make sure that all detached movements are deleted at the same time, else the interaction workflow would reattach them to a delivery rule. 4/ call builder """ tag_list = [] movement_list = [x for x in self.getMovementList() if x.getUid() in movement_uid_list] if not movement_list: return deferred_simulation_movement_list = [] # defer simulation movements if start_date is not None or stop_date is not None: for movement in movement_list: start_date = start_date or movement.getStartDate() stop_date = stop_date or movement.getStopDate() for s_m in movement.getDeliveryRelatedValueList(): if s_m.getStartDate() != start_date or \ s_m.getStopDate() != stop_date: s_m.edit(start_date=start_date, stop_date=stop_date) deferred_simulation_movement_list.append(s_m) solver_tag = '%s_splitAndDefer_solver' % self.getRelativeUrl() expand_tag = '%s_splitAndDefer_expand' % self.getRelativeUrl() detach_tag = '%s_splitAndDefer_detach' % self.getRelativeUrl() build_tag = '%s_splitAndDefer_build' % self.getRelativeUrl() # call solver and expand on deferrd movements for movement in movement_list: movement.activate(tag=solver_tag).Movement_solveMovement( delivery_solver, target_solver) tag_list.append(solver_tag) kw = {'after_tag': tag_list[:], 'tag': expand_tag} for s_m in deferred_simulation_movement_list: s_m.expand('deferred', activate_kw=kw) tag_list.append(expand_tag) detached_movement_url_list = [] deleted_movement_uid_list = [] #detach simulation movements for movement in movement_list: movement_url = movement.getRelativeUrl() movement_uid = getattr(movement,'uid',None) if movement_uid: deleted_movement_uid_list.append(movement_uid) for s_m in movement.getDeliveryRelatedValueList(): delivery_list = \ [x for x in s_m.getDeliveryList() if x != movement_url] s_m.activate(after_tag=tag_list[:], tag=detach_tag).setDeliveryList( delivery_list) detached_movement_url_list.append(s_m.getRelativeUrl()) tag_list.append(detach_tag) #delete delivery movements # deleteContent uses the uid as a activity tag self.activate(after_tag=tag_list[:]).deleteContent([movement.getId() for movement in movement_list]) tag_list.extend(deleted_movement_uid_list) # update causality state on self, after deletion self.activate(after_tag=tag_list[:], activity='SQLQueue').updateCausalityState() # call builder on detached movements builder = getattr(self.portal_deliveries, delivery_builder) builder.activate(after_tag=tag_list[:], tag=build_tag).build( movement_relative_url_list=detached_movement_url_list) ####################################################### # Defer indexing process def reindexObject(self, *k, **kw): """ Reindex children and simulation """ self.recursiveReindexObject(*k, **kw) # do not reexpand simulation: this is a task for DSolver / TSolver ####################################################### # Stock Management def _getMovementResourceList(self): resource_set = {m.getResource() for m in self.objectValues(portal_type= self.getPortalObject().getPortalMovementTypeList())} resource_set.discard(None) return list(resource_set) security.declareProtected(Permissions.AccessContentsInformation, 'getInventory') def getInventory(self, **kw): """ Returns inventory """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getInventory(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getCurrentInventory') def getCurrentInventory(self, **kw): """ Returns current inventory """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getCurrentInventory(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getAvailableInventory') def getAvailableInventory(self, **kw): """ Returns available inventory (current inventory - deliverable) """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getAvailableInventory(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getFutureInventory') def getFutureInventory(self, **kw): """ Returns inventory at infinite """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getFutureInventory(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getInventoryList') def getInventoryList(self, **kw): """ Returns list of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getInventoryList(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getCurrentInventoryList') def getCurrentInventoryList(self, **kw): """ Returns list of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getCurrentInventoryList(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getFutureInventoryList') def getFutureInventoryList(self, **kw): """ Returns list of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getFutureInventoryList(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getInventoryStat') def getInventoryStat(self, **kw): """ Returns statistics of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getInventoryStat(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getCurrentInventoryStat') def getCurrentInventoryStat(self, **kw): """ Returns statistics of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getCurrentInventoryStat(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getFutureInventoryStat') def getFutureInventoryStat(self, **kw): """ Returns statistics of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getFutureInventoryStat(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getInventoryChart') def getInventoryChart(self, **kw): """ Returns list of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getInventoryChart(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getCurrentInventoryChart') def getCurrentInventoryChart(self, **kw): """ Returns list of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getCurrentInventoryChart(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getFutureInventoryChart') def getFutureInventoryChart(self, **kw): """ Returns list of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getFutureInventoryChart(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getInventoryHistoryList') def getInventoryHistoryList(self, **kw): """ Returns list of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getInventoryHistoryList(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getInventoryHistoryChart') def getInventoryHistoryChart(self, **kw): """ Returns list of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getInventoryHistoryChart(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getMovementHistoryList') def getMovementHistoryList(self, **kw): """ Returns list of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getMovementHistoryList(**kw) security.declareProtected(Permissions.AccessContentsInformation, 'getMovementHistoryStat') def getMovementHistoryStat(self, **kw): """ Returns list of inventory grouped by section or site """ kw['resource'] = self._getMovementResourceList() return self.portal_simulation.getMovementHistoryStat(**kw) # JPS: We must still decide if getInventoryAssetPrice is part of the Delivery API # security.declareProtected(Permissions.AccessContentsInformation, 'getInventoryAssetPrice') # def getInventoryAssetPrice(self, **kw): # """ # Returns asset at infinite # """ # kw['category'] = self._getMovementResourceList() # return self.portal_simulation.getInventoryAssetPrice(**kw) # # security.declareProtected(Permissions.AccessContentsInformation, 'getFutureInventoryAssetPrice') # def getFutureInventoryAssetPrice(self, **kw): # """ # Returns asset at infinite # """ # kw['category'] = self._getMovementResourceList() # return self.portal_simulation.getFutureInventoryAssetPrice(**kw) # # security.declareProtected(Permissions.AccessContentsInformation, 'getCurrentInventoryAssetPrice') # def getCurrentInventoryAssetPrice(self, **kw): # """ # Returns asset at infinite # """ # kw['category'] = self._getMovementResourceList() # return self.portal_simulation.getCurrentInventoryAssetPrice(**kw) # # security.declareProtected(Permissions.AccessContentsInformation, 'getAvailableInventoryAssetPrice') # def getAvailableInventoryAssetPrice(self, **kw): # """ # Returns asset at infinite # """ # kw['category'] = self._getMovementResourceList() # return self.portal_simulation.getAvailableInventoryAssetPrice(**kw) ########################################################################## # Applied Rule stuff security.declareProtected(Permissions.AccessContentsInformation, 'localBuild') def localBuild(self, activity_kw=()): """Activate builders for this delivery The generated activity will find all buildable business links for this delivery, and call related builders, which will select all simulation movements part of the same explanation(s) as the delivery. XXX: Consider moving it to SimulableMixin if it's useful for Subscription Items. """ # XXX: Previous implementation waited for expand activities of related # documents and even suggested to look at explanation tree, # instead of causalities. Is it required ? kw = {'priority': 3} kw.update(activity_kw) after_tag = kw.pop('after_tag', None) if isinstance(after_tag, basestring): after_tag = [after_tag] else: after_tag = list(after_tag) if after_tag else [] after_tag.append('build:' + self.getPath()) sm = getSecurityManager() newSecurityManager(None, nobody) try: unrestricted_apply(self.activate(after_tag=after_tag, **kw)._localBuild) finally: setSecurityManager(sm) def _localBuild(self): """Do an immediate local build for this delivery""" return self.asComposedDocument().build(explanation=self) def _createRootAppliedRule(self): # Only create RAR if we are not in a "too early" or "too late" state. state = self.getSimulationState() if (state != 'deleted' and state not in self.getPortalObject().getPortalDraftOrderStateList()): return super(Delivery, self)._createRootAppliedRule() security.declareProtected( Permissions.AccessContentsInformation, 'getRootCausalityValueList') def getRootCausalityValueList(self): """ Returns the initial causality value for this movement. This method will look at the causality and check if the causality has already a causality """ seen_set = set() def recursive(self): if self in seen_set: return [] seen_set.add(self) causality_value_list = self.getCausalityValueList() if causality_value_list: initial_list = [] for causality in causality_value_list: # The causality may be something which has not this method # (e.g. item) if getattr(causality, 'getRootCausalityValueList', None) is None: continue assert causality != self initial_list += [x for x in recursive(causality) if x not in initial_list] return initial_list return [self] return recursive(self) # XXX Temp hack, should be removed has soon as the structure of # the order/delivery builder will be reviewed. It might # be reviewed if we plan to configure movement groups in the zmi security.declareProtected( Permissions.ModifyPortalContent, 'setRootCausalityValueList') def setRootCausalityValueList(self,value): """ This is a hack """ pass security.declareProtected( Permissions.AccessContentsInformation, 'getParentExplanationValue') def getParentExplanationValue(self): """ This method should be removed as soon as movement groups will be rewritten. It is a temp hack """ return self # XXX Temp hack, should be removed has soon as the structure of # the order/delivery builder will be reviewed. It might # be reviewed if we plan to configure movement groups in the zmi security.declareProtected( Permissions.ModifyPortalContent, 'setParentExplanationValue') def setParentExplanationValue(self,value): """ This is a hack """ pass security.declareProtected(Permissions.AccessContentsInformation, 'getBuilderList') def getBuilderList(self): """Returns appropriate builder list.""" return self._getTypeBasedMethod('getBuilderList')() # XXX - quite a hack, since no way to know... # propper implementation should use business path definition # however, the real question is "is this really necessary" # since the main purpose of this method is superceded # by IDivergenceController security.declareProtected( Permissions.AccessContentsInformation, 'getRootSpecialiseValue') def getRootSpecialiseValue(self, portal_type_list): """Returns first specialise value matching portal type""" def findSpecialiseValue(context): if context.getPortalType() in portal_type_list: return context if getattr(context, 'getSpecialiseValueList', None) is not None: for specialise in context.getSpecialiseValueList(): specialise_value = findSpecialiseValue(specialise) if specialise_value is not None: return specialise_value return None return findSpecialiseValue(self) security.declareProtected( Permissions.ModifyPortalContent, 'disconnectSimulationMovementList') def disconnectSimulationMovementList(self, movement_list=None): """Disconnects simulation movements from delivery's lines If movement_list is passed only those movements will be disconnected from simulation. If movements in movement_list do not belong to current delivery they are silently ignored. Returns list of disconnected Simulation Movements. Known issues and open questions: * how to protect disconnection from completed delivery? * what to do if movements from movement_list do not belong to delivery? * it is required to remove causality relation from delivery or delivery lines?? """ delivery_movement_value_list = self.getMovementList() if movement_list is not None: movement_value_list = [self.restrictedTraverse(movement) for movement in movement_list] # only those how are in this delivery movement_value_list = [movement_value for movement_value in movement_value_list if movement_value in delivery_movement_value_list] else: movement_value_list = delivery_movement_value_list disconnected_simulation_movement_list = [] for movement_value in movement_value_list: # Note: Relies on fact that is invoked, when simulation movements are # indexed properly for simulation_movement in movement_value \ .getDeliveryRelatedValueList(portal_type='Simulation Movement'): simulation_movement.edit( delivery = None, delivery_ratio = None ) disconnected_simulation_movement_list.append( simulation_movement.getRelativeUrl()) return disconnected_simulation_movement_list def _getAllRelatedSimulationMovementList(self): result = [] for movement in self.getMovementList(): result += movement.getDeliveryRelatedValueList() return result security.declareProtected(Permissions.AccessContentsInformation, 'getDivergentTesterAndSimulationMovementList') def getDivergentTesterAndSimulationMovementList(self): """ This method returns a list of (tester, simulation_movement) for each divergence. """ divergent_tester_list = [] for simulation_movement in self._getAllRelatedSimulationMovementList(): simulation_movement = simulation_movement.getObject() rule = simulation_movement.getParentValue().getSpecialiseValue() for tester in rule._getDivergenceTesterList(exclude_quantity=False): if tester.explain(simulation_movement) not in (None, []): divergent_tester_list.append((tester, simulation_movement)) return divergent_tester_list # XXX: Dirty but required for erp5_banking_core getBaobabSourceUid = lambda x: x.getSourceUid() getBaobabSourceUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationUid = lambda x: x.getDestinationUid() getBaobabDestinationUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceSectionUid = lambda x: x.getSourceSectionUid() getBaobabSourceSectionUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationSectionUid = lambda x: x.getDestinationSectionUid() getBaobabDestinationSectionUid__roles__ = PermissionRole(Permissions.View) getBaobabSourcePaymentUid = lambda x: x.getSourcePaymentUid() getBaobabSourcePaymentUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationPaymentUid = lambda x: x.getDestinationPaymentUid() getBaobabDestinationPaymentUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceFunctionUid = lambda x: x.getSourceFunctionUid() getBaobabSourceFunctionUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationFunctionUid = lambda x: x.getDestinationFunctionUid() getBaobabDestinationFunctionUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceProjectUid = lambda x: x.getSourceProjectUid() getBaobabSourceProjectUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationProjectUid = lambda x: x.getDestinationProjectUid() getBaobabDestinationProjectUid__roles__ = PermissionRole(Permissions.View)
security.declareProtected(Permissions.View, 'setPosted') def setPosted(self, value): """ Custom method that's automatically sets the reference of the account transfer """ if self.getPortalType() == "Account Transfer": self.setReference("posted") return self._setPosted(value) ### Dynamic patch Delivery.getBaobabSourceUid = lambda x: x.getSourceUid() Delivery.getBaobabSourceUid__roles__ = PermissionRole(Permissions.View) Delivery.getBaobabDestinationUid = lambda x: x.getDestinationUid() Delivery.getBaobabDestinationUid__roles__ = PermissionRole(Permissions.View) Delivery.getBaobabSourceSectionUid = lambda x: x.getSourceSectionUid() Delivery.getBaobabSourceSectionUid__roles__ = PermissionRole(Permissions.View) Delivery.getBaobabDestinationSectionUid = lambda x: x.getDestinationSectionUid( ) Delivery.getBaobabDestinationSectionUid__roles__ = PermissionRole( Permissions.View) Delivery.getBaobabSourcePaymentUid = lambda x: x.getSourcePaymentUid() Delivery.getBaobabSourcePaymentUid__roles__ = PermissionRole(Permissions.View)
action is the relative URL of the add form or wizard permission is the name of the permission required to instantiate this class icon is the name of an image file to use as the document's icon """ return if 0: meta_type = meta_type or getattr(instance_class, 'meta_type', '') if constructors: pr = PermissionRole(permission) for c in constructors: name = c.__name__ setattr(PortalFolder, name, c) setattr(PortalFolder, '%s__roles__' % name, pr) PortalFolder.content_meta_types = PortalFolder.content_meta_types + ( { 'name': meta_type, 'action': action, 'permission': permission }, ) if icon: path = 'misc_/CMF/%s' % urllib.quote(meta_type) instance_class.icon = path
class Container(Movement, XMLObject): """ Container is equivalent to a movement with qty 1.0 and resource = to the kind of packaging Container may point to item (ex. Container serial No or Parcel Serial No if tracing required) Container may eventually usa optional property sheet to store parcel No information (we use Item property sheet for that). Some acquisition may be required... A Container which does not point to an Item can act itself as an Item for traceability. Container Line / Container Cell is used to store quantities (never accounted) Container Line / Countainer Cell may point to Item """ meta_type = 'ERP5 Container' portal_type = 'Container' # Declarative security security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) # Declarative properties property_sheets = (PropertySheet.Base, PropertySheet.XMLObject, PropertySheet.CategoryCore, PropertySheet.Amount, PropertySheet.Task, PropertySheet.Arrow, PropertySheet.Movement, PropertySheet.Price, PropertySheet.VariationRange, PropertySheet.ItemAggregation, PropertySheet.Item, PropertySheet.Container, PropertySheet.SortIndex) security.declareProtected(Permissions.AccessContentsInformation, 'getQuantity') def getQuantity(self, default=1.0): """ Returns 1 because only one container is shipped """ return 1.0 security.declareProtected(Permissions.AccessContentsInformation, 'isAccountable') def isAccountable(self): """ Returns 1 if this needs to be accounted Only account movements which are not associated to a delivery Whenever delivery is there, delivery has priority """ # Always accountable - to account the containers which we use return 1 security.declareProtected(Permissions.ModifyPortalContent, 'hasCellContent') def hasCellContent(self, base_id='movement'): """ This method can be overriden """ return 0 security.declareProtected(Permissions.AccessContentsInformation, 'isDivergent') def isDivergent(self): """Return True if this movement diverges from the its simulation. Containers are never divergent. """ return False security.declareProtected(Permissions.AccessContentsInformation, 'getContainerText') def getContainerText(self): """ Creates a unique string which allows to compare/hash two containers """ result = "" container_line_list = list(self.objectValues()) container_line_list.sort(key=lambda x: x.getResource()) for container_line in container_line_list: if container_line.hasCellContent(): container_cell_list = list(container_line.objectValues()) container_cell_list.sort(key=lambda x: x.getVariationText()) for container_cell in container_cell_list: result += "%s %s %s\n" % ( container_cell.getResource(), container_cell.getQuantity(), '|'.join( container_cell.getVariationText().split('\n'))) else: result += "%s %s\n" % (container_line.getResource(), container_line.getQuantity()) container_list = list(self.objectValues(spec=self.meta_type)) container_list.sort(key=lambda x: x.getContainerText()) more_result = "" for container in container_list: more_result += container.getContainerText() result = result + '\n'.join( map(lambda x: " %s" % x, more_result.split('\n'))) return result # Used for optimization - requires reindexing using container_uid security.declareProtected(Permissions.AccessContentsInformation, 'getContainerUid') def getContainerUid(self): return self.getUid() security.declareProtected(Permissions.AccessContentsInformation, 'getContainerValue') def getContainerValue(self): return self security.declareProtected(Permissions.AccessContentsInformation, 'getContainer') def getContainer(self): return self.getRelativeUrl() # Quantity methods security.declareProtected(Permissions.AccessContentsInformation, 'getContainedTotalQuantity') def getContainedTotalQuantity(self, recursive=0): """ The sum of quantities of contained lines """ result = 0.0 for o in self.contentValues( filter={'portal_type': self.getPortalContainerLineTypeList()}): result += o.getTotalQuantity() if recursive: for o in self.contentValues( filter={'portal_type': self.getPortalContainerTypeList()}): result += o.getContainedTotalQuantity() return result security.declareProtected(Permissions.AccessContentsInformation, 'getContainedTotalPrice') def getContainedTotalPrice(self, recursive=0): """ The sum of price of contained lines """ result = 0.0 for o in self.contentValues( filter={'portal_type': self.getPortalContainerLineTypeList()}): result += o.getTotalPrice() if recursive: for o in self.contentValues( filter={'portal_type': self.getPortalContainerTypeList()}): result += o.getContainedTotalPrice() return result # Item Access security.declareProtected(Permissions.AccessContentsInformation, 'getTrackedItemUidList') def getTrackedItemUidList(self): """ Return a list of uid for related items. If this container is related to no item, it is treated as an Item """ ### XXX We should filter by portal type here item_uid_list = self.getAggregateUidList() if len(item_uid_list): return item_uid_list return (self.getUid(), ) # XXX: Dirty but required for erp5_banking_core getBaobabSourceUid = lambda x: x.getSourceUid() getBaobabSourceUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationUid = lambda x: x.getDestinationUid() getBaobabDestinationUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceSectionUid = lambda x: x.getSourceSectionUid() getBaobabSourceSectionUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationSectionUid = lambda x: x.getDestinationSectionUid() getBaobabDestinationSectionUid__roles__ = PermissionRole(Permissions.View) getBaobabSourcePaymentUid = lambda x: x.getSourcePaymentUid() getBaobabSourcePaymentUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationPaymentUid = lambda x: x.getDestinationPaymentUid() getBaobabDestinationPaymentUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceFunctionUid = lambda x: x.getSourceFunctionUid() getBaobabSourceFunctionUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationFunctionUid = lambda x: x.getDestinationFunctionUid() getBaobabDestinationFunctionUid__roles__ = PermissionRole(Permissions.View) getBaobabSourceProjectUid = lambda x: x.getSourceProjectUid() getBaobabSourceProjectUid__roles__ = PermissionRole(Permissions.View) getBaobabDestinationProjectUid = lambda x: x.getDestinationProjectUid() getBaobabDestinationProjectUid__roles__ = PermissionRole(Permissions.View)