Commit fd530345 authored by Yusuke Muraoka's avatar Yusuke Muraoka

- reverted wrong changes since after rev.26461(ProductionOrderRule) and...

- reverted wrong changes since after rev.26461(ProductionOrderRule) and rev.22242(Transformation*Rule) without the changed definition of interfaces
- added relation, which refer to trade_phase, to the TransformedResource


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@27562 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 493c9916
...@@ -30,11 +30,12 @@ from AccessControl import ClassSecurityInfo ...@@ -30,11 +30,12 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces
from Products.ERP5.Document.Rule import Rule from Products.ERP5.Document.Rule import Rule
from Products.ERP5.Document.OrderRule import OrderRule from Products.ERP5.Document.OrderRule import OrderRule
from Products.ERP5.Document.TransformationRule import TransformationRuleMixin from Products.ERP5.Document.TransformationSourcingRule import\
TransformationSourcingRuleMixin
from zLOG import LOG, WARNING from zLOG import LOG, WARNING
class ProductionOrderRule(TransformationRuleMixin, OrderRule): class ProductionOrderRule(OrderRule):
""" """
Prouction Order Rule object use a Supply Chain to expand a Prouction Order Rule object use a Supply Chain to expand a
Production Order. Production Order.
...@@ -48,6 +49,9 @@ class ProductionOrderRule(TransformationRuleMixin, OrderRule): ...@@ -48,6 +49,9 @@ class ProductionOrderRule(TransformationRuleMixin, OrderRule):
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation) security.declareObjectProtected(Permissions.AccessContentsInformation)
__implements = ( interfaces.IPredicate,
interfaces.IRule )
# Default Properties # Default Properties
property_sheets = ( PropertySheet.Base property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject , PropertySheet.XMLObject
...@@ -89,17 +93,25 @@ class ProductionOrderRule(TransformationRuleMixin, OrderRule): ...@@ -89,17 +93,25 @@ class ProductionOrderRule(TransformationRuleMixin, OrderRule):
'quantity_unit', 'quantity_unit',
) )
root_explanation = self.getRootExplanation( supply_chain = self.getSupplyChain(applied_rule)
self.getBusinessProcess(applied_rule=applied_rule)) # We got a supply chain
property_dict['source_section'] = root_explanation.getSourceSection() # Try to get the last SupplyLink
source_method_id = root_explanation.getSourceMethodId() last_link = supply_chain.getLastLink()
if source_method_id is None: # We got a valid industrial_phase
property_dict['source'] = root_explanation.getSource() # Now, we have to generate Simulation Movement, in order to
else: # create a ProductionPackingList.
property_dict['source'] = getattr(root_explanation, source_method_id)() destination_node = last_link.getDestinationValue()
property_dict['causality'] = root_explanation.getRelativeUrl() source_value = destination_node.getDestination()
source_section_value = last_link.getDestinationSection()
if source_value is not None:
property_dict["source"] = source_value
if source_section_value is not None:
property_dict["source_section"] = source_section_value
for prop in default_property_list: for prop in default_property_list:
property_dict[prop] = movement.getProperty(prop) property_dict[prop] = movement.getProperty(prop)
return property_dict return property_dict
from Products.ERP5Type.Utils import monkeyPatch
monkeyPatch(TransformationSourcingRuleMixin, ProductionOrderRule)
# -*- coding:utf-8 -*-
############################################################################## ##############################################################################
# #
# Copyright (c) 2002-2009 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com> # Jean-Paul Smets-Solanes <jp@nexedi.com>
# Romain Courteaud <romain@nexedi.com> # Romain Courteaud <romain@nexedi.com>
# #
...@@ -28,411 +27,298 @@ ...@@ -28,411 +27,298 @@
# #
############################################################################## ##############################################################################
from ExtensionClass import Base
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces
from Products.ERP5.Document.Rule import Rule from Products.ERP5.Document.Rule import Rule
from Products.ERP5.Document.SimulationMovement import SimulationMovement
from Products.ERP5Type.Errors import TransformationRuleError from Products.ERP5Type.Errors import TransformationRuleError
from Products.ERP5.Document.TransformationSourcingRule import\
TransformationSourcingRuleMixin
class MovementFactory: from zLOG import LOG
def getRequestList(self):
"""
return the list of a request which to be used to apply movements
"""
raise NotImplementedError, 'Must be implemented'
def _getCausalityList(self, causality=None, causality_value=None,
causality_list=None, causality_value_list=None,
**kw):
if causality is not None:
return [causality]
elif causality_value is not None:
return [causality_value.getRelativeUrl()]
elif causality_list is not None:
return causality_list
elif causality_value_list is not None:
return [causality_value.getRelativeUrl()
for causality_value in causality_value_list]
def makeMovements(self, applied_rule): class TransformationRule(TransformationSourcingRuleMixin, Rule):
""" """
make movements under the applied_rule by requests Order Rule object make sure an Order in the similation
""" is consistent with the real order
movement_dict = {}
for movement in applied_rule.objectValues(
portal_type="Simulation Movement"):
key = tuple(sorted(movement.getCausalityList()))
movement_dict[key] = movement
for request in self.getRequestList():
# get movement by causality
key = tuple(sorted(self._getCausalityList(**request)))
movement = movement_dict.get(key, None)
# when no exist
if movement is None:
movement = applied_rule.newContent(portal_type="Simulation Movement")
# update
if movement.isFrozen():
self.makeDifferentMovement(movement, **request)
else:
movement.edit(**request)
def _requestNetQuantity(self, request):
quantity = request.get('quantity', None)
efficiency = request.get('efficiency', None)
if efficiency in (0, 0.0, None, ''):
efficiency = 1.0
return float(quantity) / efficiency
def makeDifferentMovement(self, movement, **request):
"""
make different movement, which is based on original movement.
this implementation just focus about quantity.
"""
applied_rule = movement.getParentValue()
request['quantity'] = self._requestNetQuantity(request)\
- movement.getNetQuantity()
if request['quantity'] != 0:
diff_movement = applied_rule.newContent(portal_type="Simulation Movement")
diff_movement.edit(**request)
class TransformationMovementFactory(MovementFactory):
def __init__(self):
self.product = None # base information to use for making movements
self.produced_list = list()
self.consumed_list = list()
def requestProduced(self, **produced):
self.produced_list.append(produced)
def requestConsumed(self, **consumed):
self.consumed_list.append(consumed)
def getRequestList(self):
"""
return the list of a request which to be used to apply movements
"""
_list = []
"""
produced quantity should be represented by minus quantity on movement.
because plus quantity is consumed.
"""
for (request_list, sign) in ((self.produced_list, -1),
(self.consumed_list, 1)):
for request in request_list:
d = self.product.copy()
d.update(request)
d['quantity'] *= sign
_list.append(d)
return _list
class TransformationRuleMixin(Base):
security = ClassSecurityInfo()
security.declareProtected(Permissions.View, 'getTransformation')
def getTransformation(self, movement=None, applied_rule=None):
""" """
Return transformation related to used by the applied rule. # CMF Type Definition
""" meta_type = 'ERP5 Transformation Rule'
if movement is None and applied_rule is not None: portal_type = 'Transformation Rule'
movement = applied_rule.getParentValue() # Declarative security
security = ClassSecurityInfo()
order_movement = movement.getRootSimulationMovement().getOrderValue() security.declareObjectProtected(Permissions.AccessContentsInformation)
explanation = self.getExplanation(movement=movement, __implements__ = ( interfaces.IPredicate,
applied_rule=applied_rule) interfaces.IRule )
# find line recursively # Default Properties
order_line = order_movement property_sheets = ( PropertySheet.Base
while order_line.getParentValue() != explanation:
order_line = order_line.getParentValue()
script = order_line._getTypeBasedMethod('_getTransformation')
if script is not None:
transformation = script()
else:
line_transformation = order_line.objectValues(
portal_type=self.getPortalTransformationTypeList())
if len(line_transformation) == 1:
transformation = line_transformation[0]
else:
transformation = order_line.getSpecialiseValue(
portal_type=self.getPortalTransformationTypeList())
if transformation.getResource() == movement.getResource():
return transformation
security.declareProtected(Permissions.View, 'getBusinessProcess')
def getBusinessProcess(self, **kwargs):
"""
Return business process related to root causality.
"""
explanation = self.getExplanation(**kwargs)
if explanation is not None:
specialise = explanation.getSpecialiseValue()
business_process_type_list = self.getPortalBusinessProcessTypeList()
# because trade condition can be specialised
while specialise is not None and \
specialise.getPortalType() not in business_process_type_list:
specialise = specialise.getSpecialiseValue()
return specialise
security.declareProtected(Permissions.View, 'getRootExplanation')
def getRootExplanation(self, business_process):
"""
the method of ProductionOrderRule returns most tail path of business process
"""
if business_process is not None:
for business_path in business_process.contentValues(
portal_type=self.getPortalBusinessPathTypeList()):
if business_path.isDeliverable():
return business_path
security.declareProtected(Permissions.View, 'getExplanation')
def getExplanation(self, movement=None, applied_rule=None):
if applied_rule is not None:
return applied_rule.getRootAppliedRule().getCausalityValue()
else:
return movement.getRootSimulationMovement()\
.getOrderValue().getExplanationValue()
class TransformationRule(TransformationRuleMixin, Rule):
"""
"""
# CMF Type Definition
meta_type = 'ERP5 Transformation Rule'
portal_type = 'Transformation Rule'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Default Properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject , PropertySheet.XMLObject
, PropertySheet.CategoryCore , PropertySheet.CategoryCore
, PropertySheet.DublinCore , PropertySheet.DublinCore
, PropertySheet.Task , PropertySheet.Task
) )
# Class variable
simulation_movement_portal_type = "Simulation Movement"
def getHeadProductionPathList(self, transformation, business_process): # Simulation workflow
""" security.declareProtected(Permissions.ModifyPortalContent, 'expand')
Return list of path which is head of transformation trade_phases def expand(self, applied_rule, **kw):
this method assumes trade_phase of head paths is only one
"""
production_trade_phase_set = set([amount.getTradePhase()
for amount in transformation\
.getAggregatedAmountList()])
head_path_list = []
for state in business_process.objectValues(
portal_type=self.getPortalBusinessStateTypeList()):
if len(state.getSuccessorRelatedValueList()) == 0:
head_path_list.extend(state.getPredecessorRelatedValueList())
result_list = []
for path in head_path_list:
result_list += self._getHeadPathByTradePhaseList(path, production_trade_phase_set)
return map(lambda t: t[0], filter(lambda t: t != (None, None), result_list))
def _getHeadPathByTradePhaseList(self, path, trade_phase_set):
_set = set(path.getTradePhaseList())
if _set & trade_phase_set:
return [(path, _set & trade_phase_set)]
successor_node = path.getSuccessorValue()
if successor_node is None:
return [(None, None)]
_list = []
for next_path in successor_node.getPredecessorRelatedValueList():
_list += self._getHeadPathByTradePhaseList(next_path, trade_phase_set)
return _list
def getFactory(self):
return TransformationMovementFactory()
security.declareProtected(Permissions.ModifyPortalContent, 'expand')
def expand(self, applied_rule, **kw):
"""
"""
parent_movement = applied_rule.getParentValue()
transformation = self.getTransformation(movement=parent_movement)
business_process = self.getBusinessProcess(movement=parent_movement)
explanation = self.getExplanation(movement=parent_movement)
# get all trade_phase of the Business Process
trade_phase_list = business_process.getTradePhaseList()
# get head of production path from business process with trade_phase_list
head_production_path_list = self.getHeadProductionPathList(transformation,
business_process)
factory = self.getFactory()
factory.product = dict(
resource=transformation.getResource(),
quantity=parent_movement.getNetQuantity(),
quantity_unit=parent_movement.getQuantityUnit(),
variation_category_list=parent_movement.getVariationCategoryList(),
variation_property_dict=parent_movement.getVariationPropertyDict(),)
# consumed amounts are sorted by phase, but not ordered.
amount_dict = {}
for amount in transformation.getAggregatedAmountList():
phase = amount.getTradePhase()
if phase not in trade_phase_list:
raise TransformationRuleError,\
"Trade phase %r is not part of Business Process %r" % (phase, business_process)
amount_dict.setdefault(phase, [])
amount_dict[phase].append(amount)
last_phase_path_list = list() # to keep phase_path_list
last_prop_dict = dict()
for (phase, amount_list) in amount_dict.items():
phase_path_list = business_process.getPathValueList(phase)
""" """
XXX: In this context, we assume quantity as ratio, Expands the current movement downward.
but this "quantity as ratio" is consistent with transformation. -> new status -> expanded
An applied rule can be expanded only if its parent movement
is expanded.
""" """
if sum(map(lambda path: path.getQuantity(), phase_path_list)) != 1: parent_movement = applied_rule.getParentValue()
# Get production node and production section
production = parent_movement.getSource()
production_section = parent_movement.getSourceSection()
# Get the current supply link used to calculate consumed resource
# The current supply link is calculated from the parent AppliedRule.
supply_chain = self.getSupplyChain(parent_movement.getParentValue())
parent_supply_link = self.getCurrentSupplyLink(parent_movement)
current_supply_link_list = supply_chain.\
getPreviousProductionSupplyLinkList(parent_supply_link)
if len(current_supply_link_list) != 1:
# We shall no pass here.
# The test method returned a wrong value !
raise TransformationRuleError,\ raise TransformationRuleError,\
"sum ratio of Trade Phase %r of Business Process %r is not one"\ "Expand must not be called on %r" %\
% (phase, business_process) applied_rule.getRelativeUrl()
else:
for path in phase_path_list: current_supply_link = current_supply_link_list[0]
# source, source_section # Generate produced movement
source_section = path.getSourceSection() # only support a static access movement_dict = self._expandProducedResource(applied_rule,
source_method_id = path.getSourceMethodId() production,
if source_method_id is None: production_section,
source = path.getSource() current_supply_link)
else: # Generate consumed movement
source = getattr(path, source_method_id)() consumed_mvt_dict = self._expandConsumedResource(applied_rule,
# destination, destination_section production,
destination_section = path.getDestinationSection() # only support a static access production_section,
destination_method_id = path.getDestinationMethodId() current_supply_link)
if destination_method_id is None: movement_dict.update(consumed_mvt_dict)
destination = path.getDestination() # Finally, build movement
else: self._buildMovementList(applied_rule, movement_dict, **kw)
destination = getattr(path, destination_method_id)() # Expand each movement created
Rule.expand(self, applied_rule, **kw)
start_date = path.getExpectedStartDate(explanation)
stop_date = path.getExpectedStopDate(explanation) def _expandProducedResource(self, applied_rule, production,
predecessor_remaining_phase_list = path.getPredecessorValue()\ production_section, current_supply_link):
.getRemainingTradePhaseList(explanation, """
trade_phase_list=trade_phase_list) Produced resource.
successor_remaining_phase_list = path.getSuccessorValue()\ Create a movement for the resource produced by the transformation.
.getRemainingTradePhaseList(explanation, Only one produced movement can be created.
trade_phase_list=trade_phase_list) """
parent_movement = applied_rule.getParentValue()
# checking which is not last path of transformation stop_date = parent_movement.getStartDate()
if len(successor_remaining_phase_list) != 0: produced_movement_dict = {
# partial produced movement 'pr': {
factory.requestProduced( "resource": parent_movement.getResource(),
causality_value=path, # XXX what is lost quantity ?
start_date=start_date, "quantity": parent_movement.getQuantity(),# + lost_quantity,
stop_date=stop_date, "quantity_unit": parent_movement.getQuantityUnit(),
# when last path of transformation, path.getQuantity() will be return 1. "variation_category_list":\
quantity=factory.product['quantity'] * path.getQuantity(), parent_movement.getVariationCategoryList(),
source_section=source_section, "variation_property_dict": \
destination_section=destination_section, parent_movement.getVariationPropertyDict(),
destination=destination, "source_list": (),
trade_phase_value_list=successor_remaining_phase_list) "source_section_list": (),
else: "destination": production,
# for making movement of last product of the transformation "destination_section": production_section,
last_phase_path_list.append(path) "deliverable": 1,
'start_date': current_supply_link.calculateStartDate(stop_date),
# path params must be same 'stop_date': stop_date,
if last_prop_dict.get('start_date', None) is None: 'causality_value': current_supply_link,
last_prop_dict['start_date'] = start_date }
if last_prop_dict.get('stop_date', None) is None: }
last_prop_dict['stop_date'] = stop_date return produced_movement_dict
# trade phase of product is must be empty []
if last_prop_dict.get('trade_phase_value_list', None) is None: def _expandConsumedResource(self, applied_rule, production,
last_prop_dict['trade_phase_value_list'] = successor_remaining_phase_list production_section, current_supply_link):
if last_prop_dict.get('source_section', None) is None: """
last_prop_dict['source_section'] = source_section Consumed resource.
# for the source, it is not need, because the produced. Create a movement for each resource consumed by the transformation,
if last_prop_dict.get('destination_section', None) is None: and for the previous variation of the produced resource.
last_prop_dict['destination_section'] = destination_section """
if last_prop_dict.get('destination', None) is None: # Calculate all consumed resource
last_prop_dict['destination'] = destination # Store each value in a dictionnary before created them.
# { movement_id: {property_name: property_value,} ,}
if last_prop_dict['start_date'] != start_date or\ consumed_movement_dict = {}
last_prop_dict['stop_date'] != stop_date or\ parent_movement = applied_rule.getParentValue()
last_prop_dict['trade_phase_value_list'] != successor_remaining_phase_list or\ supply_chain = self.getSupplyChain(parent_movement.getParentValue())
last_prop_dict['source_section'] != source_section or\ # Consumed previous variation
last_prop_dict['destination_section'] != destination_section or\ previous_variation_dict = self._expandConsumedPreviousVariation(
last_prop_dict['destination'] != destination: applied_rule,
raise TransformationRuleError,\ production,
"""Returned property is different on Transformation %r and Business Process %r"""\ production_section,
% (transformation, business_process) supply_chain,
current_supply_link)
# when the path is part of production but not first, consume previous partial product consumed_movement_dict.update(previous_variation_dict)
if path not in head_production_path_list: # Consumed raw materials
factory.requestConsumed( raw_material_dict = self._expandConsumedRawMaterials(
causality_value=path, applied_rule,
start_date=start_date, production,
stop_date=stop_date, production_section,
quantity=factory.product['quantity'] * path.getQuantity(), supply_chain,
source_section=source_section, current_supply_link)
destination_section=destination_section, consumed_movement_dict.update(raw_material_dict)
source=source, return consumed_movement_dict
trade_phase_value_list=predecessor_remaining_phase_list)
def _expandConsumedPreviousVariation(self, applied_rule, production,
# consumed movement production_section, supply_chain,
for amount in amount_list: current_supply_link):
factory.requestConsumed( """
causality_value=path, Create a movement for the previous variation of the produced resource.
start_date=start_date, """
stop_date=stop_date, id_count = 1
resource=amount.getResource(), consumed_movement_dict = {}
quantity=factory.product['quantity'] * amount.getQuantity()\ parent_movement = applied_rule.getParentValue()
/ amount.getEfficiency() * path.getQuantity(), # Calculate the variation category list of parent movement
quantity_unit=amount.getQuantityUnit(), base_category_list = parent_movement.getVariationBaseCategoryList()
source_section=source_section, if "industrial_phase" in base_category_list:
destination_section=destination_section, # We do not want to get the industrial phase variation
source=source, base_category_list.remove("industrial_phase")
trade_phase=path.getTradePhase()) category_list = parent_movement.getVariationCategoryList(
base_category_list=base_category_list)
# Calculate the previous variation
for previous_supply_link in supply_chain.\
getPreviousSupplyLinkList(current_supply_link):
previous_ind_phase_list = supply_chain.\
getPreviousProductionIndustrialPhaseList(previous_supply_link,
all=1)
if previous_ind_phase_list != []:
# Industrial phase is a category
ind_phase_list = [x.getRelativeUrl() for x in \
previous_ind_phase_list]
consumed_mvt_id = "%s_%s" % ("mr", id_count)
id_count += 1
stop_date = parent_movement.getStartDate()
consumed_movement_dict[consumed_mvt_id] = {
'start_date': current_supply_link.calculateStartDate(stop_date),
'stop_date': stop_date,
"resource": parent_movement.getResource(),
# XXX Is the quantity value correct ?
"quantity": parent_movement.getNetQuantity(), # getNetQuantity to support efficency from transformation
"quantity_unit": parent_movement.getQuantityUnit(),
"destination_list": (),
"destination_section_list": (),
"source": production,
"source_section": production_section,
"deliverable": 1,
"variation_category_list": category_list+ind_phase_list,
"variation_property_dict": \
parent_movement.getVariationPropertyDict(),
'causality_value': current_supply_link,
}
return consumed_movement_dict
def _expandConsumedRawMaterials(self, applied_rule, production,
production_section, supply_chain,
current_supply_link):
"""
Create a movement for each resource consumed by the transformation,
"""
parent_movement = applied_rule.getParentValue()
# Calculate the context for getAggregatedAmountList
base_category_list = parent_movement.getVariationBaseCategoryList()
if "industrial_phase" in base_category_list:
# We do not want to get the industrial phase variation
base_category_list.remove("industrial_phase")
category_list = parent_movement.getVariationCategoryList(
base_category_list=base_category_list)
# Get the transformation to use
transformation = self.getTransformation(applied_rule)
# Generate the fake context
tmp_context = parent_movement.asContext(
context=parent_movement,
REQUEST={'categories':category_list})
# Calculate the industrial phase list
previous_ind_phase_list = supply_chain.\
getPreviousPackingListIndustrialPhaseList(current_supply_link)
ind_phase_id_list = [x.getRelativeUrl() for x in previous_ind_phase_list]
# Call getAggregatedAmountList
# XXX expand failed if transformation is not defined.
# Do we need to catch the exception ?
amount_list = transformation.getAggregatedAmountList(
tmp_context,
ind_phase_url_list=ind_phase_id_list)
# Add entries in the consumed_movement_dict
consumed_movement_dict = {}
for amount in amount_list:
consumed_mvt_id = "%s_%s" % ("cr", amount.getId())
stop_date = parent_movement.getStartDate()
resource_price = amount.getResourcePrice()
price = None
if resource_price is not None:
price = amount.getNetQuantity() * resource_price # getNetQuantity to support efficency from transformation
consumed_movement_dict[consumed_mvt_id] = {
'start_date': current_supply_link.calculateStartDate(stop_date),
'stop_date': stop_date,
"resource": amount.getResource(),
"variation_category_list":\
amount.getVariationCategoryList(),
"variation_property_dict": \
amount.getVariationPropertyDict(),
"quantity": amount.getNetQuantity() * parent_movement.getQuantity(), # getNetQuantity to support efficency from transformation
"price": price,
"quantity_unit": amount.getQuantityUnit(),
"destination_list": (),
"destination_section_list": (),
"source": production,
"source_section": production_section,
"deliverable": 1,
'causality_value': current_supply_link,
}
return consumed_movement_dict
security.declareProtected(Permissions.ModifyPortalContent, 'solve')
def solve(self, applied_rule, solution_list):
"""
Solve inconsistency according to a certain number of solutions
templates. This updates the
""" -> new status -> solved
valid graph for transformation
a --- b --- c
a -- This applies a solution to an applied rule. Once
\ the solution is applied, the parent movement is checked.
X b If it does not diverge, the rule is reexpanded. If not,
/ diverge is called on the parent movement.
c -- """
invalid graph security.declareProtected(Permissions.ModifyPortalContent, 'diverge')
a ------- b def diverge(self, applied_rule):
c ------- d """
-> new status -> diverged
-- b This basically sets the rule to "diverged"
/ and blocks expansion process
a X """
\
-- c
"""
# when empty
if last_phase_path_list is None or len(last_phase_path_list) == 0:
raise TransformationRuleError,\
"""Could not make the product by Transformation %r and Business Process %r,
which last_phase_path_list is empty.""" % (transformation, business_process)
factory.requestProduced( # # Solvers
causality_value_list=last_phase_path_list, # security.declareProtected(Permissions.View, 'isDivergent')
# when last path of transformation, path.getQuantity() will be return 1. # def isDivergent(self, applied_rule):
quantity=factory.product['quantity'] * path.getQuantity(), # """
**last_prop_dict) # Returns 1 if divergent rule
# """
#
# security.declareProtected(Permissions.View, 'getDivergenceList')
# def getDivergenceList(self, applied_rule):
# """
# Returns a list Divergence descriptors
# """
#
# security.declareProtected(Permissions.View, 'getSolverList')
# def getSolverList(self, applied_rule):
# """
# Returns a list Divergence solvers
# """
# Deliverability / orderability
def isDeliverable(self, m):
return 1
def isOrderable(self, m):
return 0
factory.makeMovements(applied_rule)
Rule.expand(self, applied_rule, **kw)
...@@ -27,113 +27,240 @@ ...@@ -27,113 +27,240 @@
# #
############################################################################## ##############################################################################
import ExtensionClass
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces
from Products.ERP5.Document.Rule import Rule from Products.ERP5.Document.Rule import Rule
from Products.ERP5.Document.TransformationRule import MovementFactory, TransformationRuleMixin
from zLOG import LOG from zLOG import LOG
class ProductionOrderError(Exception): pass
class TransformationSourcingRuleError(Exception): pass class TransformationSourcingRuleError(Exception): pass
class SourcingMovementFactory(MovementFactory): class TransformationSourcingRuleMixin(ExtensionClass.Base):
def __init__(self):
self.request_list = list()
def requestSourcing(self, **sourcing):
self.request_list.append(sourcing)
def getRequestList(self):
return self.request_list
class TransformationSourcingRule(TransformationRuleMixin, Rule):
""" """
Transformation Sourcing Rule object make sure Mixin class used by TransformationSourcingRule and TransformationRule
items required in a Transformation are sourced
""" """
# CMF Type Definition
meta_type = 'ERP5 Transformation Sourcing Rule'
portal_type = 'Transformation Sourcing Rule'
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Default Properties security.declareProtected(Permissions.View,
property_sheets = ( PropertySheet.Base 'getSupplyChain')
def getSupplyChain(self, applied_rule):
"""
Get the SupplyChain.
"""
# Get the SupplyChain to use
supply_chain_portal_type = "Supply Chain"
order = applied_rule.getRootAppliedRule().getCausalityValue()
supply_chain = order.getSpecialiseValue(
portal_type=supply_chain_portal_type)
if supply_chain is None:
raise ProductionOrderError,\
"No SupplyChain defined on %s" % str(order)
else:
return supply_chain
def getCurrentSupplyLink(self, movement):
"""
Get the current SupplyLink
"""
# Get the current supply link
supply_link_portal_type = "Supply Link"
current_supply_link = movement.getCausalityValue(
portal_type=supply_link_portal_type)
return current_supply_link
security.declareProtected(Permissions.ModifyPortalContent,
'_buildMovementList')
def _buildMovementList(self, applied_rule, movement_dict,activate_kw=None,**kw):
"""
For each movement in the dictionnary, test if the movement already
exists.
If not, create it.
Then, update the movement attributes.
"""
for movement_id in movement_dict.keys():
movement = applied_rule.get(movement_id)
# Create the movement if it does not exist
if movement is None:
movement = applied_rule.newContent(
portal_type=self.simulation_movement_portal_type,
id=movement_id,
activate_kw=activate_kw
)
# We shouldn't modify frozen movements
elif movement.isFrozen():
# FIXME: this is not perfect, instead of just skipping this one, we
# should generate a compensation movement
continue
# Update movement properties
movement.edit(activate_kw=activate_kw, **(movement_dict[movement_id]))
security.declareProtected(Permissions.View, 'getTransformation')
def getTransformation(self, movement):
"""
Get transformation related to used by the applied rule.
"""
production_order_movement = movement.getRootSimulationMovement().\
getOrderValue()
# XXX Acquisition can be use instead
parent_uid = production_order_movement.getParentUid()
explanation_uid = production_order_movement.getExplanationUid()
if parent_uid == explanation_uid:
production_order_line = production_order_movement
else:
production_order_line = production_order_movement.getParentValue()
script = production_order_line._getTypeBasedMethod('_getTransformation')
if script is not None:
transformation = script()
else:
line_transformation = production_order_line.objectValues(
portal_type=self.getPortalTransformationTypeList())
if len(line_transformation)==1:
transformation = line_transformation[0]
else:
transformation = production_order_line.getSpecialiseValue(
portal_type=self.getPortalTransformationTypeList())
return transformation
class TransformationSourcingRule(TransformationSourcingRuleMixin, Rule):
"""
Transformation Sourcing Rule object make sure
items required in a Transformation are sourced
"""
# CMF Type Definition
meta_type = 'ERP5 Transformation Sourcing Rule'
portal_type = 'Transformation Sourcing Rule'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
__implements__ = ( interfaces.IPredicate,
interfaces.IRule )
# Default Properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject , PropertySheet.XMLObject
, PropertySheet.CategoryCore , PropertySheet.CategoryCore
, PropertySheet.DublinCore , PropertySheet.DublinCore
, PropertySheet.Task , PropertySheet.Task
) )
def getFactory(self): # Class variable
return SourcingMovementFactory() simulation_movement_portal_type = "Simulation Movement"
security.declareProtected(Permissions.ModifyPortalContent, 'expand') security.declareProtected(Permissions.ModifyPortalContent, 'expand')
def expand(self, applied_rule, **kw): def expand(self, applied_rule, activate_kw=None,**kw):
""" """
Expands the current movement downward. Expands the current movement downward.
-> new status -> expanded -> new status -> expanded
An applied rule can be expanded only if its parent movement An applied rule can be expanded only if its parent movement
is expanded. is expanded.
""" """
parent_movement = applied_rule.getParentValue() parent_movement = applied_rule.getParentValue()
explanation = self.getExplanation(movement=parent_movement) # Calculate the previous supply link
state = parent_movement.getCausalityValue().getPredecessorValue() supply_chain = self.getSupplyChain(parent_movement.getParentValue())
path_list = state.getSuccessorRelatedValueList() parent_supply_link = self.getCurrentSupplyLink(parent_movement)
previous_supply_link_list = supply_chain.\
if len(path_list) == 0: getPreviousPackingListSupplyLinkList(
raise TransformationSourcingRuleError,\ parent_supply_link,
"Not found deliverable business path" movement=parent_movement)
if len(path_list) > 1: if len(previous_supply_link_list) == 0:
raise TransformationSourcingRuleError,\ raise TransformationSourcingRuleError,\
"Found 2 or more deliverable business path" "Expand must not be called on %r" %\
applied_rule.getRelativeUrl()
path = path_list[0] else:
movement_dict = {}
# source, source_section for previous_supply_link in previous_supply_link_list:
source_section = path.getSourceSection() # only support a static access # Calculate the source
source_method_id = path.getSourceMethodId() source_value = None
if source_method_id is None: source_node = previous_supply_link.getSourceValue()
source = path.getSource() if source_node is not None:
else: source_value = source_node.getDestinationValue()
source = getattr(path, source_method_id)() source_section_value = previous_supply_link.getSourceSectionValue()
# destination, destination_section # Generate the dict
destination_section = path.getDestinationSection() # only support a static access stop_date = parent_movement.getStartDate()
destination_method_id = path.getDestinationMethodId() movement_dict.update({
if destination_method_id is None: "ts": {
destination = path.getDestination() 'source_value': source_value,
else: 'source_section_value': source_section_value,
destination = getattr(path, destination_method_id)() 'destination_value': parent_movement.getSourceValue(),
'destination_section_value': \
start_date = path.getExpectedStartDate(explanation) parent_movement.getSourceSectionValue(),
stop_date = path.getExpectedStopDate(explanation) 'resource_value': parent_movement.getResourceValue(),
'variation_category_list': parent_movement.\
quantity = parent_movement.getNetQuantity() * path.getQuantity() getVariationCategoryList(),
price = parent_movement.getPrice() "variation_property_dict": \
if price is not None: parent_movement.getVariationPropertyDict(),
price *= path.getQuantity() 'quantity': parent_movement.getNetQuantity(), # getNetQuantity to support efficency from transformation
'price': parent_movement.getPrice(),
factory = self.getFactory() 'quantity_unit': parent_movement.getQuantityUnit(),
factory.requestSourcing( 'start_date': previous_supply_link.calculateStartDate(stop_date),
causality_value=path, 'stop_date': stop_date,
source=source, 'deliverable': 1,
source_section=source_section, # Save the value of the current supply link
destination=destination, 'causality_value': previous_supply_link,
destination_section=destination_section, }
resource=parent_movement.getResource(), })
variation_category_list=parent_movement.getVariationCategoryList(), # Build the movement
variation_property_dict=parent_movement.getVariationPropertyDict(), self._buildMovementList(applied_rule, movement_dict,
quantity=quantity, activate_kw=activate_kw)
price=price, # Create one submovement which sources the transformation
quantity_unit=parent_movement.getQuantityUnit(), Rule.expand(self, applied_rule, activate_kw=activate_kw, **kw)
start_date=start_date,
stop_date=stop_date, security.declareProtected(Permissions.ModifyPortalContent, 'solve')
deliverable=1, def solve(self, applied_rule, solution_list):
) """
Solve inconsistency according to a certain number of solutions
factory.makeMovements(applied_rule) templates. This updates the
Rule.expand(self, applied_rule, **kw)
-> new status -> solved
This applies a solution to an applied rule. Once
the solution is applied, the parent movement is checked.
If it does not diverge, the rule is reexpanded. If not,
diverge is called on the parent movement.
"""
security.declareProtected(Permissions.ModifyPortalContent, 'diverge')
def diverge(self, applied_rule):
"""
-> new status -> diverged
This basically sets the rule to "diverged"
and blocks expansion process
"""
# # Solvers
# security.declareProtected(Permissions.View, 'isDivergent')
# def isDivergent(self, applied_rule):
# """
# Returns 1 if divergent rule
# """
#
# security.declareProtected(Permissions.View, 'getDivergenceList')
# def getDivergenceList(self, applied_rule):
# """
# Returns a list Divergence descriptors
# """
#
# security.declareProtected(Permissions.View, 'getSolverList')
# def getSolverList(self, applied_rule):
# """
# Returns a list Divergence solvers
# """
def isDeliverable(self, m):
resource = m.getResource()
if m.getResource() is None:
return 0
if resource.find('operation/') >= 0:
return 0
else:
return 1
def isOrderable(self, m):
return 0
...@@ -322,7 +322,11 @@ class TransformedResource(Predicate, XMLObject, XMLMatrix, Amount): ...@@ -322,7 +322,11 @@ class TransformedResource(Predicate, XMLObject, XMLMatrix, Amount):
else: else:
variation_category_list = self._getVariationCategoryList() variation_category_list = self._getVariationCategoryList()
variation_category_list_defined_by = self.getRelativeUrl() variation_category_list_defined_by = self.getRelativeUrl()
trade_phase = self.getTradePhase() if hasattr(self,"getTradePhase"):
# After installing BPM, trade_phase category to be exists
trade_phase = self.getTradePhase()
else:
trade_phase = None
# Store values in Amount # Store values in Amount
tmp_amount._edit( tmp_amount._edit(
# Properties define on transformation line # Properties define on transformation line
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment