Commit 662d8798 authored by Yusuke Muraoka's avatar Yusuke Muraoka

- changed the TransformationSourcingRule to refer a business process

  instead of a supply chain
- added a test for TransformationSourcingRule


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@27405 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 73ad6938
...@@ -38,17 +38,12 @@ from Products.ERP5.Document.Rule import Rule ...@@ -38,17 +38,12 @@ from Products.ERP5.Document.Rule import Rule
from Products.ERP5.Document.SimulationMovement import SimulationMovement from Products.ERP5.Document.SimulationMovement import SimulationMovement
from Products.ERP5Type.Errors import TransformationRuleError from Products.ERP5Type.Errors import TransformationRuleError
class TransformationMovementFactory: class MovementFactory:
def __init__(self): def getRequestList(self):
self.default = None # base information to use for making movements """
self.produced_list = list() return the list of a request which to be used to apply movements
self.consumed_list = list() """
raise NotImplementedError, 'Must be implemented'
def requestProduced(self, **produced):
self.produced_list.append(produced)
def requestConsumed(self, **consumed):
self.consumed_list.append(consumed)
def _getCausalityList(self, causality=None, causality_value=None, def _getCausalityList(self, causality=None, causality_value=None,
causality_list=None, causality_value_list=None, causality_list=None, causality_value_list=None,
...@@ -63,17 +58,6 @@ class TransformationMovementFactory: ...@@ -63,17 +58,6 @@ class TransformationMovementFactory:
return [causality_value.getRelativeUrl() return [causality_value.getRelativeUrl()
for causality_value in causality_value_list] for causality_value in causality_value_list]
def getRequestList(self):
_list = []
for (request_list, sign) in ((self.produced_list, -1),
(self.consumed_list, 1)):
for request in request_list:
d = self.default.copy()
d.update(request)
d['quantity'] *= sign
_list.append(d)
return _list
def makeMovements(self, applied_rule): def makeMovements(self, applied_rule):
""" """
make movements under the applied_rule by requests make movements under the applied_rule by requests
...@@ -84,10 +68,6 @@ class TransformationMovementFactory: ...@@ -84,10 +68,6 @@ class TransformationMovementFactory:
key = tuple(sorted(movement.getCausalityList())) key = tuple(sorted(movement.getCausalityList()))
movement_dict[key] = movement movement_dict[key] = movement
"""
produced quantity should be represented by minus quantity on movement.
because plus quantity is consumed.
"""
for request in self.getRequestList(): for request in self.getRequestList():
# get movement by causality # get movement by causality
key = tuple(sorted(self._getCausalityList(**request))) key = tuple(sorted(self._getCausalityList(**request)))
...@@ -121,6 +101,37 @@ class TransformationMovementFactory: ...@@ -121,6 +101,37 @@ class TransformationMovementFactory:
diff_movement.edit(**request) 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): class TransformationRuleMixin(Base):
security = ClassSecurityInfo() security = ClassSecurityInfo()
...@@ -266,7 +277,7 @@ class TransformationRule(TransformationRuleMixin, Rule): ...@@ -266,7 +277,7 @@ class TransformationRule(TransformationRuleMixin, Rule):
head_production_path_list = self.getHeadProductionPathList(transformation, head_production_path_list = self.getHeadProductionPathList(transformation,
business_process) business_process)
factory = self.getFactory() factory = self.getFactory()
factory.default = dict( factory.product = dict(
resource=transformation.getResource(), resource=transformation.getResource(),
quantity=parent_movement.getNetQuantity(), quantity=parent_movement.getNetQuantity(),
quantity_unit=parent_movement.getQuantityUnit(), quantity_unit=parent_movement.getQuantityUnit(),
...@@ -299,6 +310,21 @@ class TransformationRule(TransformationRuleMixin, Rule): ...@@ -299,6 +310,21 @@ class TransformationRule(TransformationRuleMixin, Rule):
% (phase, business_process) % (phase, business_process)
for path in phase_path_list: for path in phase_path_list:
# source, source_section
source_section = path.getSourceSection() # only support a static access
source_method_id = path.getSourceMethodId()
if source_method_id is None:
source = path.getSource()
else:
source = getattr(path, source_method_id)()
# destination, destination_section
destination_section = path.getDestinationSection() # only support a static access
destination_method_id = path.getDestinationMethodId()
if destination_method_id is None:
destination = path.getDestination()
else:
destination = getattr(path, destination_method_id)()
start_date = path.getExpectedStartDate(explanation) start_date = path.getExpectedStartDate(explanation)
stop_date = path.getExpectedStopDate(explanation) stop_date = path.getExpectedStopDate(explanation)
predecessor_remaining_phase_list = path.getPredecessorValue()\ predecessor_remaining_phase_list = path.getPredecessorValue()\
...@@ -307,7 +333,6 @@ class TransformationRule(TransformationRuleMixin, Rule): ...@@ -307,7 +333,6 @@ class TransformationRule(TransformationRuleMixin, Rule):
successor_remaining_phase_list = path.getSuccessorValue()\ successor_remaining_phase_list = path.getSuccessorValue()\
.getRemainingTradePhaseList(explanation, .getRemainingTradePhaseList(explanation,
trade_phase_list=trade_phase_list) trade_phase_list=trade_phase_list)
destination = path.getDestination()
# checking which is not last path of transformation # checking which is not last path of transformation
if len(successor_remaining_phase_list) != 0: if len(successor_remaining_phase_list) != 0:
...@@ -317,9 +342,10 @@ class TransformationRule(TransformationRuleMixin, Rule): ...@@ -317,9 +342,10 @@ class TransformationRule(TransformationRuleMixin, Rule):
start_date=start_date, start_date=start_date,
stop_date=stop_date, stop_date=stop_date,
# when last path of transformation, path.getQuantity() will be return 1. # when last path of transformation, path.getQuantity() will be return 1.
quantity=factory.default['quantity'] * path.getQuantity(), quantity=factory.product['quantity'] * path.getQuantity(),
source_section=source_section,
destination_section=destination_section,
destination=destination, destination=destination,
#destination_section=???,
trade_phase_value_list=successor_remaining_phase_list) trade_phase_value_list=successor_remaining_phase_list)
else: else:
# for making movement of last product of the transformation # for making movement of last product of the transformation
...@@ -333,12 +359,19 @@ class TransformationRule(TransformationRuleMixin, Rule): ...@@ -333,12 +359,19 @@ class TransformationRule(TransformationRuleMixin, Rule):
# trade phase of product is must be empty [] # trade phase of product is must be empty []
if last_prop_dict.get('trade_phase_value_list', None) is None: if last_prop_dict.get('trade_phase_value_list', None) is None:
last_prop_dict['trade_phase_value_list'] = successor_remaining_phase_list last_prop_dict['trade_phase_value_list'] = successor_remaining_phase_list
if last_prop_dict.get('source_section', None) is None:
last_prop_dict['source_section'] = source_section
# for the source, it is not need, because the produced.
if last_prop_dict.get('destination_section', None) is None:
last_prop_dict['destination_section'] = destination_section
if last_prop_dict.get('destination', None) is None: if last_prop_dict.get('destination', None) is None:
last_prop_dict['destination'] = destination last_prop_dict['destination'] = destination
if last_prop_dict['start_date'] != start_date or\ if last_prop_dict['start_date'] != start_date or\
last_prop_dict['stop_date'] != stop_date or\ last_prop_dict['stop_date'] != stop_date or\
last_prop_dict['trade_phase_value_list'] != successor_remaining_phase_list or\ last_prop_dict['trade_phase_value_list'] != successor_remaining_phase_list or\
last_prop_dict['source_section'] != source_section or\
last_prop_dict['destination_section'] != destination_section or\
last_prop_dict['destination'] != destination: last_prop_dict['destination'] != destination:
raise TransformationRuleError,\ raise TransformationRuleError,\
"""Returned property is different on Transformation %r and Business Process %r"""\ """Returned property is different on Transformation %r and Business Process %r"""\
...@@ -350,9 +383,10 @@ class TransformationRule(TransformationRuleMixin, Rule): ...@@ -350,9 +383,10 @@ class TransformationRule(TransformationRuleMixin, Rule):
causality_value=path, causality_value=path,
start_date=start_date, start_date=start_date,
stop_date=stop_date, stop_date=stop_date,
quantity=factory.default['quantity'] * path.getQuantity(), quantity=factory.product['quantity'] * path.getQuantity(),
source=path.getSource(), source_section=source_section,
#source_section=???, destination_section=destination_section,
source=source,
trade_phase_value_list=predecessor_remaining_phase_list) trade_phase_value_list=predecessor_remaining_phase_list)
# consumed movement # consumed movement
...@@ -362,11 +396,12 @@ class TransformationRule(TransformationRuleMixin, Rule): ...@@ -362,11 +396,12 @@ class TransformationRule(TransformationRuleMixin, Rule):
start_date=start_date, start_date=start_date,
stop_date=stop_date, stop_date=stop_date,
resource=amount.getResource(), resource=amount.getResource(),
quantity=factory.default['quantity'] * amount.getQuantity()\ quantity=factory.product['quantity'] * amount.getQuantity()\
/ amount.getEfficiency() * path.getQuantity(), / amount.getEfficiency() * path.getQuantity(),
quantity_unit=amount.getQuantityUnit(), quantity_unit=amount.getQuantityUnit(),
source=path.getSource(), source_section=source_section,
#source_section=???, destination_section=destination_section,
source=source,
trade_phase=path.getTradePhase()) trade_phase=path.getTradePhase())
""" """
...@@ -398,8 +433,7 @@ which last_phase_path_list is empty.""" % (transformation, business_process) ...@@ -398,8 +433,7 @@ which last_phase_path_list is empty.""" % (transformation, business_process)
factory.requestProduced( factory.requestProduced(
causality_value_list=last_phase_path_list, causality_value_list=last_phase_path_list,
# when last path of transformation, path.getQuantity() will be return 1. # when last path of transformation, path.getQuantity() will be return 1.
quantity=factory.default['quantity'] * path.getQuantity(), quantity=factory.product['quantity'] * path.getQuantity(),
#destination_section=???,
**last_prop_dict) **last_prop_dict)
factory.makeMovements(applied_rule) factory.makeMovements(applied_rule)
......
...@@ -27,108 +27,29 @@ ...@@ -27,108 +27,29 @@
# #
############################################################################## ##############################################################################
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 TransformationSourcingRuleMixin(ExtensionClass.Base): class SourcingMovementFactory(MovementFactory):
""" def __init__(self):
Mixin class used by TransformationSourcingRule and TransformationRule self.request_list = list()
"""
# Declarative security
security = ClassSecurityInfo()
security.declareProtected(Permissions.View,
'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, def requestSourcing(self, **sourcing):
'_buildMovementList') self.request_list.append(sourcing)
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 getRequestList(self):
def getTransformation(self, movement): return self.request_list
"""
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): class TransformationSourcingRule(TransformationRuleMixin, 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
...@@ -148,11 +69,11 @@ class TransformationSourcingRule(TransformationSourcingRuleMixin, Rule): ...@@ -148,11 +69,11 @@ class TransformationSourcingRule(TransformationSourcingRuleMixin, Rule):
, PropertySheet.DublinCore , PropertySheet.DublinCore
, PropertySheet.Task , PropertySheet.Task
) )
# Class variable def getFactory(self):
simulation_movement_portal_type = "Simulation Movement" return SourcingMovementFactory()
security.declareProtected(Permissions.ModifyPortalContent, 'expand') security.declareProtected(Permissions.ModifyPortalContent, 'expand')
def expand(self, applied_rule, activate_kw=None,**kw): def expand(self, applied_rule, **kw):
""" """
Expands the current movement downward. Expands the current movement downward.
-> new status -> expanded -> new status -> expanded
...@@ -160,107 +81,60 @@ class TransformationSourcingRule(TransformationSourcingRuleMixin, Rule): ...@@ -160,107 +81,60 @@ class TransformationSourcingRule(TransformationSourcingRuleMixin, Rule):
is expanded. is expanded.
""" """
parent_movement = applied_rule.getParentValue() parent_movement = applied_rule.getParentValue()
# Calculate the previous supply link explanation = self.getExplanation(movement=parent_movement)
supply_chain = self.getSupplyChain(parent_movement.getParentValue()) state = parent_movement.getCausalityValue().getPredecessorValue()
parent_supply_link = self.getCurrentSupplyLink(parent_movement) path_list = state.getSuccessorRelatedValueList()
previous_supply_link_list = supply_chain.\
getPreviousPackingListSupplyLinkList(
parent_supply_link,
movement=parent_movement)
if len(previous_supply_link_list) == 0:
raise TransformationSourcingRuleError,\
"Expand must not be called on %r" %\
applied_rule.getRelativeUrl()
else:
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)
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
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') if len(path_list) == 0:
def diverge(self, applied_rule): raise TransformationSourcingRuleError,\
""" "Not found deliverable business path"
-> new status -> diverged if len(path_list) > 1:
raise TransformationSourcingRuleError,\
"Found 2 or more deliverable business path"
This basically sets the rule to "diverged" path = path_list[0]
and blocks expansion process
"""
# # Solvers # source, source_section
# security.declareProtected(Permissions.View, 'isDivergent') source_section = path.getSourceSection() # only support a static access
# def isDivergent(self, applied_rule): source_method_id = path.getSourceMethodId()
# """ if source_method_id is None:
# Returns 1 if divergent rule source = path.getSource()
# """
#
# 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:
return 1 source = getattr(path, source_method_id)()
# destination, destination_section
destination_section = path.getDestinationSection() # only support a static access
destination_method_id = path.getDestinationMethodId()
if destination_method_id is None:
destination = path.getDestination()
else:
destination = getattr(path, destination_method_id)()
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,
)
def isOrderable(self, m): factory.makeMovements(applied_rule)
return 0 Rule.expand(self, applied_rule, **kw)
...@@ -37,14 +37,15 @@ from Products.ERP5Type.tests.Sequence import SequenceList ...@@ -37,14 +37,15 @@ from Products.ERP5Type.tests.Sequence import SequenceList
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Products.ERP5Type.tests.utils import reindex from Products.ERP5Type.tests.utils import reindex
from Products.ERP5.Document.TransformationRule import TransformationRule
from Products.ERP5.tests.testBPMCore import TestBPMMixin from Products.ERP5.tests.testBPMCore import TestBPMMixin
class TestMRPMixin(TestBPMMixin): class TestMRPMixin(TestBPMMixin):
transformation_portal_type = 'Transformation' transformation_portal_type = 'Transformation'
transformed_resource_portal_type = 'Transformation Transformed Resource' transformed_resource_portal_type = 'Transformation Transformed Resource'
product_portal_type = 'Product' product_portal_type = 'Product'
organisation_portal_type = 'Organisation'
order_portal_type = 'Production Order'
order_line_portal_type = 'Production Order Line'
def setUpOnce(self): def setUpOnce(self):
self.portal = self.getPortalObject() self.portal = self.getPortalObject()
...@@ -58,16 +59,37 @@ class TestMRPMixin(TestBPMMixin): ...@@ -58,16 +59,37 @@ class TestMRPMixin(TestBPMMixin):
portal_type=rule_tool.getPortalRuleTypeList()): portal_type=rule_tool.getPortalRuleTypeList()):
rule.invalidate() rule.invalidate()
def createTransformation(self): def _createDocument(self, portal_type, **kw):
module = self.portal.getDefaultModule( module = self.portal.getDefaultModule(
portal_type=self.transformation_portal_type) portal_type=portal_type)
return module.newContent(portal_type=self.transformation_portal_type) return self._createObject(module, portal_type, **kw)
def createTransformedResource(self, transformation=None): def _createObject(self, parent, portal_type, id=None, **kw):
if transformation is None: o = None
transformation = self.createTransformation() if id is not None:
return transformation.newContent( o = parent.get(str(id), None)
portal_type=self.transformed_resource_portal_type) if o is None:
o = parent.newContent(portal_type=portal_type)
o.edit(**kw)
return o
def createTransformation(self, **kw):
return self._createDocument(self.transformation_portal_type, **kw)
def createProduct(self, **kw):
return self._createDocument(self.product_portal_type, **kw)
def createOrganisation(self, **kw):
return self._createDocument(self.organisation_portal_type, **kw)
def createOrder(self, **kw):
return self._createDocument(self.order_portal_type, **kw)
def createOrderLine(self, order, **kw):
return self._createObject(order, self.order_line_portal_type, **kw)
def createTransformedResource(self, transformation, **kw):
return self._createObject(transformation, self.transformed_resource_portal_type, **kw)
@reindex @reindex
def createCategories(self): def createCategories(self):
...@@ -78,39 +100,56 @@ class TestMRPMixin(TestBPMMixin): ...@@ -78,39 +100,56 @@ class TestMRPMixin(TestBPMMixin):
self.createCategoriesInCategory(category_tool.trade_phase.mrp, self.createCategoriesInCategory(category_tool.trade_phase.mrp,
['p' + str(i) for i in range(5)]) # phase0 ~ 4 ['p' + str(i) for i in range(5)]) # phase0 ~ 4
def createProduct(self): @reindex
module = self.portal.getDefaultModule( def createDefaultOrder(self, transformation=None, business_process=None):
portal_type=self.product_portal_type) if transformation is None:
return module.newContent(portal_type=self.product_portal_type) transformation = self.createDefaultTransformation()
if business_process is None:
business_process = self.createSimpleBusinessProcess()
base_date = DateTime()
order = self.createOrder(specialise_value=business_process,
start_date=base_date,
stop_date=base_date+3)
order_line = self.createOrderLine(order,
quantity=10,
resource=transformation.getResource(),
specialise_value=transformation)
# XXX in some case, specialise_value is not related to order_line by edit,
# but by setSpecialise() is ok, Why?
order_line.setSpecialiseValue(transformation)
return order
@reindex @reindex
def createDefaultTransformation(self): def createDefaultTransformation(self):
resource1 = self.createProduct() resource1 = self.createProduct(id='1', quantity_unit_list=['weight/kg'])
resource2 = self.createProduct() resource2 = self.createProduct(id='2', quantity_unit_list=['weight/kg'])
resource3 = self.createProduct() resource3 = self.createProduct(id='3', quantity_unit_list=['weight/kg'])
resource4 = self.createProduct() resource4 = self.createProduct(id='4', quantity_unit_list=['weight/kg'])
resource5 = self.createProduct() resource5 = self.createProduct(id='5', quantity_unit_list=['weight/kg'])
transformation = self.createTransformation()
amount1 = self.createTransformedResource(transformation=transformation) transformation = self.createTransformation(resource_value=resource5)
amount2 = self.createTransformedResource(transformation=transformation) self.createTransformedResource(transformation=transformation,
amount3 = self.createTransformedResource(transformation=transformation) resource_value=resource1,
amount4 = self.createTransformedResource(transformation=transformation) quantity=3,
quantity_unit_list=['weight/kg'],
resource1.edit(title='product', quantity_unit_list=['weight/kg']) trade_phase='mrp/p2')
resource2.edit(title='triangle', quantity_unit_list=['weight/kg']) self.createTransformedResource(transformation=transformation,
resource3.edit(title='box', quantity_unit_list=['weight/kg']) resource_value=resource2,
resource4.edit(title='circle', quantity_unit_list=['weight/kg']) quantity=1,
resource5.edit(title='banana', quantity_unit_list=['weight/kg']) quantity_unit_list=['weight/kg'],
trade_phase='mrp/p2')
transformation.edit(resource_value=resource1) self.createTransformedResource(transformation=transformation,
amount1.edit(resource_value=resource2, quantity=3, resource_value=resource3,
quantity_unit_list=['weight/kg'], trade_phase='mrp/p2') quantity=4,
amount2.edit(resource_value=resource3, quantity=1, quantity_unit_list=['weight/kg'],
quantity_unit_list=['weight/kg'], trade_phase='mrp/p2') trade_phase='mrp/p3')
amount3.edit(resource_value=resource4, quantity=4, self.createTransformedResource(transformation=transformation,
quantity_unit_list=['weight/kg'], trade_phase='mrp/p3') resource_value=resource4,
amount4.edit(resource_value=resource5, quantity=1, quantity=1,
quantity_unit_list=['weight/kg'], trade_phase='mrp/p3') quantity_unit_list=['weight/kg'],
trade_phase='mrp/p3')
return transformation return transformation
@reindex @reindex
...@@ -125,18 +164,34 @@ class TestMRPMixin(TestBPMMixin): ...@@ -125,18 +164,34 @@ class TestMRPMixin(TestBPMMixin):
business_state_partial = self.createBusinessState(business_process) business_state_partial = self.createBusinessState(business_process)
business_state_done = self.createBusinessState(business_process) business_state_done = self.createBusinessState(business_process)
# organisations
source_section = self.createOrganisation(title='source_section')
source = self.createOrganisation(title='source')
destination_section = self.createOrganisation(title='destination_section')
destination = self.createOrganisation(title='destination')
business_process.edit(referential_date='stop_date') business_process.edit(referential_date='stop_date')
business_path_p2.edit(id='p2', business_path_p2.edit(id='p2',
predecessor_value=business_state_ready, predecessor_value=business_state_ready,
successor_value=business_state_partial, successor_value=business_state_partial,
quantity=1, quantity=1,
trade_phase=['mrp/p2']) trade_phase=['mrp/p2'],
source_section_value=source_section,
source_value=source,
destination_section_value=destination_section,
destination_value=destination,
)
business_path_p3.edit(id='p3', business_path_p3.edit(id='p3',
predecessor_value=business_state_partial, predecessor_value=business_state_partial,
successor_value=business_state_done, successor_value=business_state_done,
quantity=1, quantity=1,
deliverable=1, # root explanation deliverable=1, # root explanation
trade_phase=['mrp/p3']) trade_phase=['mrp/p3'],
source_section_value=source_section,
source_value=source,
destination_section_value=destination_section,
destination_value=destination,
)
return business_process return business_process
@reindex @reindex
...@@ -151,20 +206,51 @@ class TestMRPMixin(TestBPMMixin): ...@@ -151,20 +206,51 @@ class TestMRPMixin(TestBPMMixin):
business_state_ready = self.createBusinessState(business_process) business_state_ready = self.createBusinessState(business_process)
business_state_partial = self.createBusinessState(business_process) business_state_partial = self.createBusinessState(business_process)
# organisations
source_section = self.createOrganisation(title='source_section')
source = self.createOrganisation(title='source')
destination_section = self.createOrganisation(title='destination_section')
destination = self.createOrganisation(title='destination')
business_process.edit(referential_date='stop_date') business_process.edit(referential_date='stop_date')
business_path_p2.edit(id='p2', business_path_p2.edit(id='p2',
predecessor_value=business_state_ready, predecessor_value=business_state_ready,
successor_value=business_state_partial, successor_value=business_state_partial,
quantity=1, quantity=1,
trade_phase=['mrp/p2']) trade_phase=['mrp/p2'],
source_section_value=source_section,
source_value=source,
destination_section_value=destination_section,
destination_value=destination,
)
business_path_p3.edit(id='p3', business_path_p3.edit(id='p3',
predecessor_value=business_state_ready, predecessor_value=business_state_ready,
successor_value=business_state_partial, successor_value=business_state_partial,
quantity=1, quantity=1,
deliverable=1, # root explanation deliverable=1, # root explanation
trade_phase=['mrp/p3']) trade_phase=['mrp/p3'],
source_section_value=source_section,
source_value=source,
destination_section_value=destination_section,
destination_value=destination,
)
return business_process return business_process
@reindex
def beforeTearDown(self):
super(TestMRPMixin, self).beforeTearDown()
transaction.abort()
for module in (
self.portal.organisation_module,
self.portal.production_order_module,
self.portal.transformation_module,
self.portal.business_process_module,
# don't remove document because reuse it for testing of id
# self.portal.product_module,
self.portal.portal_simulation,):
module.manage_delObjects(list(module.objectIds()))
transaction.commit()
class TestMRPImplementation(TestMRPMixin, ERP5TypeTestCase): class TestMRPImplementation(TestMRPMixin, ERP5TypeTestCase):
"""the test for implementation""" """the test for implementation"""
def test_TransformationRule_getHeadProductionPathList(self): def test_TransformationRule_getHeadProductionPathList(self):
...@@ -181,25 +267,25 @@ class TestMRPImplementation(TestMRPMixin, ERP5TypeTestCase): ...@@ -181,25 +267,25 @@ class TestMRPImplementation(TestMRPMixin, ERP5TypeTestCase):
set(rule.getHeadProductionPathList(transformation, business_process))) set(rule.getHeadProductionPathList(transformation, business_process)))
def test_TransformationRule_expand(self): def test_TransformationRule_expand(self):
transformation = self.createDefaultTransformation() # mock order
order = self.createDefaultOrder()
order_line = order.objectValues()[0]
""" business_process = order.getSpecialiseValue()
Simple case
"""
business_process = self.createSimpleBusinessProcess()
# mock order # paths
order = self.portal.production_order_module.newContent(portal_type="Production Order") path_p2 = '%s/p2' % business_process.getRelativeUrl()
order_line = order.newContent(portal_type="Production Order Line") path_p3 = '%s/p3' % business_process.getRelativeUrl()
base_date = DateTime() # organisations
order.edit(specialise_value=business_process, path = business_process.objectValues(
start_date=base_date, portal_type=self.portal.getPortalBusinessPathTypeList())[0]
stop_date=base_date+3, source_section = path.getSourceSection()
source_section_value=order, source = path.getSource()
source_value=order) destination_section = path.getDestinationSection()
order_line.edit(quantity=10) destination = path.getDestination()
order_line.setSpecialiseValue(transformation) # XXX Why can not define by edit? consumed_organisations = (source_section, source, destination_section, None)
produced_organisations = (source_section, None, destination_section, destination)
# don't need another rules, just need TransformationRule for test # don't need another rules, just need TransformationRule for test
self.invalidateRules() self.invalidateRules()
...@@ -213,7 +299,7 @@ class TestMRPImplementation(TestMRPMixin, ERP5TypeTestCase): ...@@ -213,7 +299,7 @@ class TestMRPImplementation(TestMRPMixin, ERP5TypeTestCase):
applied_rule.edit(causality_value=order) applied_rule.edit(causality_value=order)
movement.edit(order_value=order_line, movement.edit(order_value=order_line,
quantity=order_line.getQuantity(), quantity=order_line.getQuantity(),
resource=transformation.getResource()) resource=order_line.getResource())
# test mock # test mock
applied_rule = movement.newContent(potal_type='Applied Rule') applied_rule = movement.newContent(potal_type='Applied Rule')
...@@ -222,28 +308,51 @@ class TestMRPImplementation(TestMRPMixin, ERP5TypeTestCase): ...@@ -222,28 +308,51 @@ class TestMRPImplementation(TestMRPMixin, ERP5TypeTestCase):
# assertion # assertion
expected_value_set = set([ expected_value_set = set([
(('business_process_module/1/p2',), 'product_module/1', 'mrp/p3', -10), ((path_p2,), 'product_module/5', produced_organisations, 'mrp/p3', -10),
(('business_process_module/1/p2',), 'product_module/2', 'mrp/p2', 30), ((path_p2,), 'product_module/1', consumed_organisations, 'mrp/p2', 30),
(('business_process_module/1/p2',), 'product_module/3', 'mrp/p2', 10), ((path_p2,), 'product_module/2', consumed_organisations, 'mrp/p2', 10),
(('business_process_module/1/p3',), 'product_module/1', 'mrp/p3', 10), ((path_p3,), 'product_module/5', consumed_organisations, 'mrp/p3', 10),
(('business_process_module/1/p3',), 'product_module/4', 'mrp/p3', 40), ((path_p3,), 'product_module/3', consumed_organisations, 'mrp/p3', 40),
(('business_process_module/1/p3',), 'product_module/5', 'mrp/p3', 10), ((path_p3,), 'product_module/4', consumed_organisations, 'mrp/p3', 10),
(('business_process_module/1/p3',), 'product_module/1', None, -10)]) ((path_p3,), 'product_module/5', produced_organisations, None, -10)])
movement_list = applied_rule.objectValues() movement_list = applied_rule.objectValues()
self.assertEquals(len(expected_value_set), len(movement_list)) self.assertEquals(len(expected_value_set), len(movement_list))
movement_value_set = set([]) movement_value_set = set([])
for movement in movement_list: for movement in movement_list:
movement_value_set |= set([(tuple(movement.getCausalityList()), movement_value_set |= set([(tuple(movement.getCausalityList()),
movement.getResource(), movement.getResource(),
(movement.getSourceSection(),
movement.getSource(),
movement.getDestinationSection(),
movement.getDestination(),), # organisations
movement.getTradePhase(), movement.getTradePhase(),
movement.getQuantity())]) movement.getQuantity())])
self.assertEquals(expected_value_set, movement_value_set) self.assertEquals(expected_value_set, movement_value_set)
""" def test_TransformationRule_expand_concurrent(self):
Concurrent case
"""
business_process = self.createConcurrentBusinessProcess() business_process = self.createConcurrentBusinessProcess()
order.edit(specialise_value=business_process)
# mock order
order = self.createDefaultOrder(business_process=business_process)
order_line = order.objectValues()[0]
# phases
phase_p2 = '%s/p2' % business_process.getRelativeUrl()
phase_p3 = '%s/p3' % business_process.getRelativeUrl()
# organisations
path = business_process.objectValues(
portal_type=self.portal.getPortalBusinessPathTypeList())[0]
source_section = path.getSourceSection()
source = path.getSource()
destination_section = path.getDestinationSection()
destination = path.getDestination()
organisations = (source_section, source, destination_section, destination)
consumed_organisations = (source_section, source, destination_section, None)
produced_organisations = (source_section, None, destination_section, destination)
# don't need another rules, just need TransformationRule for test
self.invalidateRules()
self.stepTic() self.stepTic()
...@@ -254,7 +363,7 @@ class TestMRPImplementation(TestMRPMixin, ERP5TypeTestCase): ...@@ -254,7 +363,7 @@ class TestMRPImplementation(TestMRPMixin, ERP5TypeTestCase):
applied_rule.edit(causality_value=order) applied_rule.edit(causality_value=order)
movement.edit(order_value=order_line, movement.edit(order_value=order_line,
quantity=order_line.getQuantity(), quantity=order_line.getQuantity(),
resource=transformation.getResource()) resource=order_line.getResource())
# test mock # test mock
applied_rule = movement.newContent(potal_type='Applied Rule') applied_rule = movement.newContent(potal_type='Applied Rule')
...@@ -263,59 +372,160 @@ class TestMRPImplementation(TestMRPMixin, ERP5TypeTestCase): ...@@ -263,59 +372,160 @@ class TestMRPImplementation(TestMRPMixin, ERP5TypeTestCase):
# assertion # assertion
expected_value_set = set([ expected_value_set = set([
(('business_process_module/2/p2',), 'product_module/2', 'mrp/p2', 30), ((phase_p2,), 'product_module/1', consumed_organisations, 'mrp/p2', 30),
(('business_process_module/2/p2',), 'product_module/3', 'mrp/p2', 10), ((phase_p2,), 'product_module/2', consumed_organisations, 'mrp/p2', 10),
(('business_process_module/2/p3',), 'product_module/4', 'mrp/p3', 40), ((phase_p3,), 'product_module/3', consumed_organisations, 'mrp/p3', 40),
(('business_process_module/2/p3',), 'product_module/5', 'mrp/p3', 10), ((phase_p3,), 'product_module/4', consumed_organisations, 'mrp/p3', 10),
(('business_process_module/2/p2', 'business_process_module/2/p3'), 'product_module/1', None, -10)]) ((phase_p2, phase_p3), 'product_module/5', produced_organisations, None, -10)])
movement_list = applied_rule.objectValues() movement_list = applied_rule.objectValues()
self.assertEquals(len(expected_value_set), len(movement_list)) self.assertEquals(len(expected_value_set), len(movement_list))
movement_value_set = set([]) movement_value_set = set([])
for movement in movement_list: for movement in movement_list:
movement_value_set |= set([(tuple(movement.getCausalityList()), movement_value_set |= set([(tuple(movement.getCausalityList()),
movement.getResource(), movement.getResource(),
(movement.getSourceSection(),
movement.getSource(),
movement.getDestinationSection(),
movement.getDestination(),), # organisations
movement.getTradePhase(), movement.getTradePhase(),
movement.getQuantity())]) movement.getQuantity())])
self.assertEquals(expected_value_set, movement_value_set) self.assertEquals(expected_value_set, movement_value_set)
def test_TransformationRule_expand_reexpand(self):
""" """
test case of difference when any movement are frozen test case of difference when any movement are frozen
by using above result by using above result
""" """
# update relation self.test_TransformationRule_expand_concurrent()
self.stepTic() self.stepTic()
for movement in movement_list: applied_rule = self.portal.portal_simulation.objectValues()[0]
business_process = applied_rule.getCausalityValue().getSpecialiseValue()
# phases
phase_p2 = '%s/p2' % business_process.getRelativeUrl()
phase_p3 = '%s/p3' % business_process.getRelativeUrl()
# organisations
path = business_process.objectValues(
portal_type=self.portal.getPortalBusinessPathTypeList())[0]
source_section = path.getSourceSection()
source = path.getSource()
destination_section = path.getDestinationSection()
destination = path.getDestination()
consumed_organisations = (source_section, source, destination_section, None)
produced_organisations = (source_section, None, destination_section, destination)
movement = applied_rule.objectValues()[0]
applied_rule = movement.objectValues()[0]
# these movements are made by transformation
for movement in applied_rule.objectValues():
movement.edit(quantity=1) movement.edit(quantity=1)
# XXX change state isFrozen of movement to 1, # set the state value of isFrozen to 1,
# but I think this way might be wrong.
movement._baseSetFrozen(1) movement._baseSetFrozen(1)
# re-expand # re-expand
rule = self.portal.portal_rules.default_transformation_rule
rule.expand(applied_rule) rule.expand(applied_rule)
# assertion # assertion
expected_value_set = set([ expected_value_set = set([
(('business_process_module/2/p2',), 'product_module/2', 'mrp/p2', 1), # Frozen ((phase_p2,), 'product_module/1', consumed_organisations, 'mrp/p2', 1), # Frozen
(('business_process_module/2/p2',), 'product_module/2', 'mrp/p2', 29), ((phase_p2,), 'product_module/1', consumed_organisations, 'mrp/p2', 29),
(('business_process_module/2/p2',), 'product_module/3', 'mrp/p2', 1), # Frozen ((phase_p2,), 'product_module/2', consumed_organisations, 'mrp/p2', 1), # Frozen
(('business_process_module/2/p2',), 'product_module/3', 'mrp/p2', 9), ((phase_p2,), 'product_module/2', consumed_organisations, 'mrp/p2', 9),
(('business_process_module/2/p3',), 'product_module/4', 'mrp/p3', 1), # Frozen ((phase_p3,), 'product_module/3', consumed_organisations, 'mrp/p3', 1), # Frozen
(('business_process_module/2/p3',), 'product_module/4', 'mrp/p3', 39), ((phase_p3,), 'product_module/3', consumed_organisations, 'mrp/p3', 39),
(('business_process_module/2/p3',), 'product_module/5', 'mrp/p3', 1), # Frozen ((phase_p3,), 'product_module/4', consumed_organisations, 'mrp/p3', 1), # Frozen
(('business_process_module/2/p3',), 'product_module/5', 'mrp/p3', 9), ((phase_p3,), 'product_module/4', consumed_organisations, 'mrp/p3', 9),
(('business_process_module/2/p2', 'business_process_module/2/p3'), 'product_module/1', None, 1), # Frozen ((phase_p2, phase_p3), 'product_module/5', produced_organisations, None, 1), # Frozen
(('business_process_module/2/p2', 'business_process_module/2/p3'), 'product_module/1', None, -11)]) ((phase_p2, phase_p3), 'product_module/5', produced_organisations, None, -11)])
movement_list = applied_rule.objectValues() movement_list = applied_rule.objectValues()
self.assertEquals(len(expected_value_set), len(movement_list)) self.assertEquals(len(expected_value_set), len(movement_list))
movement_value_set = set([]) movement_value_set = set([])
for movement in movement_list: for movement in movement_list:
movement_value_set |= set([(tuple(movement.getCausalityList()), movement_value_set |= set([(tuple(movement.getCausalityList()),
movement.getResource(), movement.getResource(),
(movement.getSourceSection(),
movement.getSource(),
movement.getDestinationSection(),
movement.getDestination(),), # organisations
movement.getTradePhase(), movement.getTradePhase(),
movement.getQuantity())]) movement.getQuantity())])
self.assertEquals(expected_value_set, movement_value_set) self.assertEquals(expected_value_set, movement_value_set)
def test_TransformationSourcingRule_expand(self):
# mock order
order = self.createDefaultOrder()
order_line = order.objectValues()[0]
# don't need another rules, just need TransformationSourcingRule for test
self.invalidateRules()
self.stepTic()
business_process = order.getSpecialiseValue()
# get last path of a business process
# in simple business path, the last is between "partial_produced" and "done"
causality_path = None
for state in business_process.objectValues(
portal_type=self.portal.getPortalBusinessStateTypeList()):
if len(state.getRemainingTradePhaseList(self.portal)) == 0:
causality_path = state.getSuccessorRelatedValue()
# phases
phase_p2 = '%s/p2' % business_process.getRelativeUrl()
# organisations
source_section = causality_path.getSourceSection()
source = causality_path.getSource()
destination_section = causality_path.getDestinationSection()
destination = causality_path.getDestination()
organisations = (source_section, source, destination_section, destination)
# sourcing resource
sourcing_resource = order_line.getResource()
# alter simulations of the order
# root
applied_rule = self.portal.portal_simulation.newContent(portal_type='Applied Rule')
movement = applied_rule.newContent(portal_type='Simulation Movement')
applied_rule.edit(causality_value=order)
movement.edit(order_value=order_line,
causality_value=causality_path,
quantity=order_line.getQuantity(),
resource=sourcing_resource,
)
self.stepTic()
# test mock
applied_rule = movement.newContent(potal_type='Applied Rule')
rule = self.portal.portal_rules.default_transformation_sourcing_rule
rule.expand(applied_rule)
# assertion
expected_value_set = set([
((phase_p2,), sourcing_resource, organisations, 10)])
movement_list = applied_rule.objectValues()
self.assertEquals(len(expected_value_set), len(movement_list))
movement_value_set = set([])
for movement in movement_list:
movement_value_set |= set([(tuple(movement.getCausalityList()),
movement.getResource(),
(movement.getSourceSection(),
movement.getSource(),
movement.getDestinationSection(),
movement.getDestination(),), # organisations
movement.getQuantity())])
self.assertEquals(expected_value_set, movement_value_set)
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestMRPImplementation)) suite.addTest(unittest.makeSuite(TestMRPImplementation))
......
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