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,190 +27,31 @@ ...@@ -28,190 +27,31 @@
# #
############################################################################## ##############################################################################
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):
"""
make movements under the applied_rule by requests
"""
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.
"""
if movement is None and applied_rule is not None:
movement = applied_rule.getParentValue()
order_movement = movement.getRootSimulationMovement().getOrderValue()
explanation = self.getExplanation(movement=movement,
applied_rule=applied_rule)
# find line recursively
order_line = order_movement
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') class TransformationRule(TransformationSourcingRuleMixin, Rule):
def getRootExplanation(self, business_process):
""" """
the method of ProductionOrderRule returns most tail path of business process Order Rule object make sure an Order in the similation
is consistent with the real order
""" """
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 # CMF Type Definition
meta_type = 'ERP5 Transformation Rule' meta_type = 'ERP5 Transformation Rule'
portal_type = 'Transformation Rule' portal_type = 'Transformation Rule'
# Declarative security # Declarative security
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
...@@ -219,220 +59,266 @@ class TransformationRule(TransformationRuleMixin, Rule): ...@@ -219,220 +59,266 @@ class TransformationRule(TransformationRuleMixin, Rule):
, PropertySheet.DublinCore , PropertySheet.DublinCore
, PropertySheet.Task , PropertySheet.Task
) )
# Class variable
simulation_movement_portal_type = "Simulation Movement"
def getHeadProductionPathList(self, transformation, business_process): # Simulation workflow
"""
Return list of path which is head of transformation trade_phases
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') security.declareProtected(Permissions.ModifyPortalContent, 'expand')
def expand(self, applied_rule, **kw): def expand(self, applied_rule, **kw):
""" """
Expands the current movement downward.
-> new status -> expanded
An applied rule can be expanded only if its parent movement
is expanded.
""" """
parent_movement = applied_rule.getParentValue() parent_movement = applied_rule.getParentValue()
# Get production node and production section
transformation = self.getTransformation(movement=parent_movement) production = parent_movement.getSource()
business_process = self.getBusinessProcess(movement=parent_movement) production_section = parent_movement.getSourceSection()
explanation = self.getExplanation(movement=parent_movement) # Get the current supply link used to calculate consumed resource
# The current supply link is calculated from the parent AppliedRule.
# get all trade_phase of the Business Process supply_chain = self.getSupplyChain(parent_movement.getParentValue())
trade_phase_list = business_process.getTradePhaseList() parent_supply_link = self.getCurrentSupplyLink(parent_movement)
current_supply_link_list = supply_chain.\
# get head of production path from business process with trade_phase_list getPreviousProductionSupplyLinkList(parent_supply_link)
head_production_path_list = self.getHeadProductionPathList(transformation, if len(current_supply_link_list) != 1:
business_process) # We shall no pass here.
factory = self.getFactory() # The test method returned a wrong value !
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,\ raise TransformationRuleError,\
"Trade phase %r is not part of Business Process %r" % (phase, business_process) "Expand must not be called on %r" %\
applied_rule.getRelativeUrl()
amount_dict.setdefault(phase, []) else:
amount_dict[phase].append(amount) current_supply_link = current_supply_link_list[0]
# Generate produced movement
movement_dict = self._expandProducedResource(applied_rule,
production,
production_section,
current_supply_link)
# Generate consumed movement
consumed_mvt_dict = self._expandConsumedResource(applied_rule,
production,
production_section,
current_supply_link)
movement_dict.update(consumed_mvt_dict)
# Finally, build movement
self._buildMovementList(applied_rule, movement_dict, **kw)
# Expand each movement created
Rule.expand(self, applied_rule, **kw)
last_phase_path_list = list() # to keep phase_path_list def _expandProducedResource(self, applied_rule, production,
last_prop_dict = dict() production_section, current_supply_link):
for (phase, amount_list) in amount_dict.items():
phase_path_list = business_process.getPathValueList(phase)
""" """
XXX: In this context, we assume quantity as ratio, Produced resource.
but this "quantity as ratio" is consistent with transformation. Create a movement for the resource produced by the transformation.
Only one produced movement can be created.
""" """
if sum(map(lambda path: path.getQuantity(), phase_path_list)) != 1: parent_movement = applied_rule.getParentValue()
raise TransformationRuleError,\ stop_date = parent_movement.getStartDate()
"sum ratio of Trade Phase %r of Business Process %r is not one"\ produced_movement_dict = {
% (phase, business_process) 'pr': {
"resource": parent_movement.getResource(),
for path in phase_path_list: # XXX what is lost quantity ?
# source, source_section "quantity": parent_movement.getQuantity(),# + lost_quantity,
source_section = path.getSourceSection() # only support a static access "quantity_unit": parent_movement.getQuantityUnit(),
source_method_id = path.getSourceMethodId() "variation_category_list":\
if source_method_id is None: parent_movement.getVariationCategoryList(),
source = path.getSource() "variation_property_dict": \
else: parent_movement.getVariationPropertyDict(),
source = getattr(path, source_method_id)() "source_list": (),
# destination, destination_section "source_section_list": (),
destination_section = path.getDestinationSection() # only support a static access "destination": production,
destination_method_id = path.getDestinationMethodId() "destination_section": production_section,
if destination_method_id is None: "deliverable": 1,
destination = path.getDestination() 'start_date': current_supply_link.calculateStartDate(stop_date),
else: 'stop_date': stop_date,
destination = getattr(path, destination_method_id)() 'causality_value': current_supply_link,
}
start_date = path.getExpectedStartDate(explanation) }
stop_date = path.getExpectedStopDate(explanation) return produced_movement_dict
predecessor_remaining_phase_list = path.getPredecessorValue()\
.getRemainingTradePhaseList(explanation, def _expandConsumedResource(self, applied_rule, production,
trade_phase_list=trade_phase_list) production_section, current_supply_link):
successor_remaining_phase_list = path.getSuccessorValue()\ """
.getRemainingTradePhaseList(explanation, Consumed resource.
trade_phase_list=trade_phase_list) Create a movement for each resource consumed by the transformation,
and for the previous variation of the produced resource.
# checking which is not last path of transformation """
if len(successor_remaining_phase_list) != 0: # Calculate all consumed resource
# partial produced movement # Store each value in a dictionnary before created them.
factory.requestProduced( # { movement_id: {property_name: property_value,} ,}
causality_value=path, consumed_movement_dict = {}
start_date=start_date, parent_movement = applied_rule.getParentValue()
stop_date=stop_date, supply_chain = self.getSupplyChain(parent_movement.getParentValue())
# when last path of transformation, path.getQuantity() will be return 1. # Consumed previous variation
quantity=factory.product['quantity'] * path.getQuantity(), previous_variation_dict = self._expandConsumedPreviousVariation(
source_section=source_section, applied_rule,
destination_section=destination_section, production,
destination=destination, production_section,
trade_phase_value_list=successor_remaining_phase_list) supply_chain,
else: current_supply_link)
# for making movement of last product of the transformation consumed_movement_dict.update(previous_variation_dict)
last_phase_path_list.append(path) # Consumed raw materials
raw_material_dict = self._expandConsumedRawMaterials(
# path params must be same applied_rule,
if last_prop_dict.get('start_date', None) is None: production,
last_prop_dict['start_date'] = start_date production_section,
if last_prop_dict.get('stop_date', None) is None: supply_chain,
last_prop_dict['stop_date'] = stop_date current_supply_link)
# trade phase of product is must be empty [] consumed_movement_dict.update(raw_material_dict)
if last_prop_dict.get('trade_phase_value_list', None) is None: return consumed_movement_dict
last_prop_dict['trade_phase_value_list'] = successor_remaining_phase_list
if last_prop_dict.get('source_section', None) is None: def _expandConsumedPreviousVariation(self, applied_rule, production,
last_prop_dict['source_section'] = source_section production_section, supply_chain,
# for the source, it is not need, because the produced. current_supply_link):
if last_prop_dict.get('destination_section', None) is None: """
last_prop_dict['destination_section'] = destination_section Create a movement for the previous variation of the produced resource.
if last_prop_dict.get('destination', None) is None: """
last_prop_dict['destination'] = destination id_count = 1
consumed_movement_dict = {}
if last_prop_dict['start_date'] != start_date or\ parent_movement = applied_rule.getParentValue()
last_prop_dict['stop_date'] != stop_date or\ # Calculate the variation category list of parent movement
last_prop_dict['trade_phase_value_list'] != successor_remaining_phase_list or\ base_category_list = parent_movement.getVariationBaseCategoryList()
last_prop_dict['source_section'] != source_section or\ if "industrial_phase" in base_category_list:
last_prop_dict['destination_section'] != destination_section or\ # We do not want to get the industrial phase variation
last_prop_dict['destination'] != destination: base_category_list.remove("industrial_phase")
raise TransformationRuleError,\ category_list = parent_movement.getVariationCategoryList(
"""Returned property is different on Transformation %r and Business Process %r"""\ base_category_list=base_category_list)
% (transformation, business_process) # Calculate the previous variation
for previous_supply_link in supply_chain.\
# when the path is part of production but not first, consume previous partial product getPreviousSupplyLinkList(current_supply_link):
if path not in head_production_path_list: previous_ind_phase_list = supply_chain.\
factory.requestConsumed( getPreviousProductionIndustrialPhaseList(previous_supply_link,
causality_value=path, all=1)
start_date=start_date, if previous_ind_phase_list != []:
stop_date=stop_date, # Industrial phase is a category
quantity=factory.product['quantity'] * path.getQuantity(), ind_phase_list = [x.getRelativeUrl() for x in \
source_section=source_section, previous_ind_phase_list]
destination_section=destination_section, consumed_mvt_id = "%s_%s" % ("mr", id_count)
source=source, id_count += 1
trade_phase_value_list=predecessor_remaining_phase_list) stop_date = parent_movement.getStartDate()
consumed_movement_dict[consumed_mvt_id] = {
# consumed movement '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: for amount in amount_list:
factory.requestConsumed( consumed_mvt_id = "%s_%s" % ("cr", amount.getId())
causality_value=path, stop_date = parent_movement.getStartDate()
start_date=start_date, resource_price = amount.getResourcePrice()
stop_date=stop_date, price = None
resource=amount.getResource(), if resource_price is not None:
quantity=factory.product['quantity'] * amount.getQuantity()\ price = amount.getNetQuantity() * resource_price # getNetQuantity to support efficency from transformation
/ amount.getEfficiency() * path.getQuantity(), consumed_movement_dict[consumed_mvt_id] = {
quantity_unit=amount.getQuantityUnit(), 'start_date': current_supply_link.calculateStartDate(stop_date),
source_section=source_section, 'stop_date': stop_date,
destination_section=destination_section, "resource": amount.getResource(),
source=source, "variation_category_list":\
trade_phase=path.getTradePhase()) 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):
""" """
valid graph for transformation Solve inconsistency according to a certain number of solutions
a --- b --- c templates. This updates the
a -- -> new status -> solved
\
X b
/
c --
invalid graph This applies a solution to an applied rule. Once
a ------- b the solution is applied, the parent movement is checked.
c ------- d If it does not diverge, the rule is reexpanded. If not,
diverge is called on the parent movement.
"""
-- b security.declareProtected(Permissions.ModifyPortalContent, 'diverge')
/ def diverge(self, applied_rule):
a X
\
-- c
""" """
# when empty -> new status -> diverged
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( This basically sets the rule to "diverged"
causality_value_list=last_phase_path_list, and blocks expansion process
# when last path of transformation, path.getQuantity() will be return 1. """
quantity=factory.product['quantity'] * path.getQuantity(),
**last_prop_dict) # # 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
# """
# 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,29 +27,108 @@ ...@@ -27,29 +27,108 @@
# #
############################################################################## ##############################################################################
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() Mixin class used by TransformationSourcingRule and TransformationRule
"""
# Declarative security
security = ClassSecurityInfo()
def requestSourcing(self, **sourcing): security.declareProtected(Permissions.View,
self.request_list.append(sourcing) '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 getRequestList(self): def getCurrentSupplyLink(self, movement):
return self.request_list """
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
class TransformationSourcingRule(TransformationRuleMixin, Rule): 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 Transformation Sourcing Rule object make sure
items required in a Transformation are sourced items required in a Transformation are sourced
...@@ -60,7 +139,8 @@ class TransformationSourcingRule(TransformationRuleMixin, Rule): ...@@ -60,7 +139,8 @@ class TransformationSourcingRule(TransformationRuleMixin, Rule):
# Declarative security # Declarative security
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
...@@ -68,11 +148,11 @@ class TransformationSourcingRule(TransformationRuleMixin, Rule): ...@@ -68,11 +148,11 @@ class TransformationSourcingRule(TransformationRuleMixin, Rule):
, 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
...@@ -80,60 +160,107 @@ class TransformationSourcingRule(TransformationRuleMixin, Rule): ...@@ -80,60 +160,107 @@ class TransformationSourcingRule(TransformationRuleMixin, Rule):
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(
parent_supply_link,
movement=parent_movement)
if len(previous_supply_link_list) == 0:
raise TransformationSourcingRuleError,\ raise TransformationSourcingRuleError,\
"Not found deliverable business path" "Expand must not be called on %r" %\
if len(path_list) > 1: applied_rule.getRelativeUrl()
raise TransformationSourcingRuleError,\ else:
"Found 2 or more deliverable business path" movement_dict = {}
for previous_supply_link in previous_supply_link_list:
# Calculate the source
source_value = None
source_node = previous_supply_link.getSourceValue()
if source_node is not None:
source_value = source_node.getDestinationValue()
source_section_value = previous_supply_link.getSourceSectionValue()
# Generate the dict
stop_date = parent_movement.getStartDate()
movement_dict.update({
"ts": {
'source_value': source_value,
'source_section_value': source_section_value,
'destination_value': parent_movement.getSourceValue(),
'destination_section_value': \
parent_movement.getSourceSectionValue(),
'resource_value': parent_movement.getResourceValue(),
'variation_category_list': parent_movement.\
getVariationCategoryList(),
"variation_property_dict": \
parent_movement.getVariationPropertyDict(),
'quantity': parent_movement.getNetQuantity(), # getNetQuantity to support efficency from transformation
'price': parent_movement.getPrice(),
'quantity_unit': parent_movement.getQuantityUnit(),
'start_date': previous_supply_link.calculateStartDate(stop_date),
'stop_date': stop_date,
'deliverable': 1,
# Save the value of the current supply link
'causality_value': previous_supply_link,
}
})
# Build the movement
self._buildMovementList(applied_rule, movement_dict,
activate_kw=activate_kw)
# Create one submovement which sources the transformation
Rule.expand(self, applied_rule, activate_kw=activate_kw, **kw)
path = path_list[0] 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
# source, source_section -> new status -> solved
source_section = path.getSourceSection() # only support a static access
source_method_id = path.getSourceMethodId() This applies a solution to an applied rule. Once
if source_method_id is None: the solution is applied, the parent movement is checked.
source = path.getSource() If it does not diverge, the rule is reexpanded. If not,
else: diverge is called on the parent movement.
source = getattr(path, source_method_id)() """
# destination, destination_section
destination_section = path.getDestinationSection() # only support a static access security.declareProtected(Permissions.ModifyPortalContent, 'diverge')
destination_method_id = path.getDestinationMethodId() def diverge(self, applied_rule):
if destination_method_id is None: """
destination = path.getDestination() -> 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: else:
destination = getattr(path, destination_method_id)() return 1
start_date = path.getExpectedStartDate(explanation)
stop_date = path.getExpectedStopDate(explanation)
quantity = parent_movement.getNetQuantity() * path.getQuantity()
price = parent_movement.getPrice()
if price is not None:
price *= path.getQuantity()
factory = self.getFactory()
factory.requestSourcing(
causality_value=path,
source=source,
source_section=source_section,
destination=destination,
destination_section=destination_section,
resource=parent_movement.getResource(),
variation_category_list=parent_movement.getVariationCategoryList(),
variation_property_dict=parent_movement.getVariationPropertyDict(),
quantity=quantity,
price=price,
quantity_unit=parent_movement.getQuantityUnit(),
start_date=start_date,
stop_date=stop_date,
deliverable=1,
)
factory.makeMovements(applied_rule) def isOrderable(self, m):
Rule.expand(self, applied_rule, **kw) 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()
if hasattr(self,"getTradePhase"):
# After installing BPM, trade_phase category to be exists
trade_phase = self.getTradePhase() 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