Commit 87f66783 authored by Romain Courteaud's avatar Romain Courteaud

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX EXPAND

slapos_accounting:

* generate one open order per instance tree
* create empty contraint property sheet for open order
* only one line per open order
* workaround wrong select returning a result multiple times
* XXX break instance tree periodicity. Must move to hosting subscription
* rename script for hosting subscription
* revert failure error
* force open order line to have an Instance tree and a hosting subscription
* hosting subscript will host the date info
* set open order periodicity on the hosting subscription
* typo
* hosting subscription will contain the periodicity
* create hosting subscription
* fixup start/stop date confusion
* add hosting subscription workflow
* stop using Instance Tree as Subscription Item
* move periodicity view on hosting subscription
* simulation is expanded from Hosting Subscription
* validate hosting subscription
* check all aggregate value
* revert aggregate tester
* simulate from hosting subscription
* constraint is on hosting subscription
* interaction are on hosting subscription
* open order line has 2 aggregate
* do not create open order if it was not allocated
* script renamed

slapos_erp5:

* open order line have 2 items now

slapos_cloud:

* open order line has 2 items now
* hosting subscription have a workflow again
* add hosting subscription template
* update HS_view

slapos_subscription_request:

* periodicity is on hosting subscription
parent 9c5a6155
...@@ -8,8 +8,14 @@ ...@@ -8,8 +8,14 @@
<portal_type id="Computer Consumption TioXML File"> <portal_type id="Computer Consumption TioXML File">
<item>SortIndex</item> <item>SortIndex</item>
</portal_type> </portal_type>
<portal_type id="Instance Tree"> <portal_type id="Hosting Subscription">
<item>SlapOSAccountingInstanceTreeConstraint</item> <item>SlapOSAccountingHostingSubscriptionConstraint</item>
</portal_type>
<portal_type id="Open Sale Order">
<item>SlapOSAccountingOpenSaleOrderConstraint</item>
</portal_type>
<portal_type id="Open Sale Order Line">
<item>SlapOSAccountingOpenSaleOrderLineConstraint</item>
</portal_type> </portal_type>
<portal_type id="Sale Invoice Transaction"> <portal_type id="Sale Invoice Transaction">
<item>SlapOSAccountingSaleInvoiceTransactionConstraint</item> <item>SlapOSAccountingSaleInvoiceTransactionConstraint</item>
......
...@@ -11,6 +11,10 @@ ...@@ -11,6 +11,10 @@
<type>Computer Consumption TioXML File</type> <type>Computer Consumption TioXML File</type>
<workflow>document_conversion_interaction_workflow, document_publication_workflow, edit_workflow</workflow> <workflow>document_conversion_interaction_workflow, document_publication_workflow, edit_workflow</workflow>
</chain> </chain>
<chain>
<type>Hosting Subscription</type>
<workflow>slapos_accounting_interaction_workflow</workflow>
</chain>
<chain> <chain>
<type>Instance Tree</type> <type>Instance Tree</type>
<workflow>slapos_accounting_interaction_workflow, slapos_api_invoicing_workflow</workflow> <workflow>slapos_accounting_interaction_workflow, slapos_api_invoicing_workflow</workflow>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Property Sheet" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SlapOSAccountingHostingSubscriptionConstraint</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>SlapOSAccountingInstanceTreeConstraint</string> </value> <value> <string>SlapOSAccountingOpenSaleOrderConstraint</string> </value>
</item> </item>
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TALES Constraint" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_identity_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_range_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>expression</string> </key>
<value> <string>python: len(context.contentValues(portal_type="Open Sale Order Line")) == 1</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>open_sale_order_line_uniq_constraint</string> </value>
</item>
<item>
<key> <string>message_expression_false</string> </key>
<value> <string>Only a single Open Sale Order Line must exist</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>TALES Constraint</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Property Sheet" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SlapOSAccountingOpenSaleOrderLineConstraint</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Category Membership Arity Constraint" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_identity_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_range_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>constraint_base_category</string> </key>
<value>
<tuple>
<string>aggregate</string>
</tuple>
</value>
</item>
<item>
<key> <string>constraint_portal_type</string> </key>
<value> <string>python: (\'Hosting Subscription\',)</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>aggregate_hosting_subscription_existence_constraint</string> </value>
</item>
<item>
<key> <string>max_arity</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>min_arity</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Category Membership Arity Constraint</string> </value>
</item>
<item>
<key> <string>use_acquisition</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Category Membership Arity Constraint" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_identity_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_range_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>constraint_base_category</string> </key>
<value>
<tuple>
<string>aggregate</string>
</tuple>
</value>
</item>
<item>
<key> <string>constraint_portal_type</string> </key>
<value> <string>python: (\'Instance Tree\',)</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>aggregate_instance_tree_existence_constraint</string> </value>
</item>
<item>
<key> <string>max_arity</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>min_arity</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Category Membership Arity Constraint</string> </value>
</item>
<item>
<key> <string>use_acquisition</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
from erp5.component.module.DateUtils import getClosestDate
hosting_subscription = context
assert hosting_subscription.getPortalType() == "Hosting Subscription"
return getClosestDate(target_date=hosting_subscription.getCreationDate(), precision='day')
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>InstanceTree_calculateSubscriptionStartDate</string> </value> <value> <string>HostingSubscription_calculateSubscriptionStartDate</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
from erp5.component.module.DateUtils import getClosestDate
instance_tree = context
portal = context.getPortalObject()
workflow_item_list = portal.portal_workflow.getInfoFor(
ob=instance_tree,
name='history',
wf_id='instance_slap_interface_workflow')
start_date = None
for item in workflow_item_list:
start_date = item.get('time')
if start_date:
break
if start_date is None:
# Compatibility with old Instance tree
start_date = instance_tree.getCreationDate()
start_date = getClosestDate(target_date=start_date, precision='day')
return start_date
...@@ -2,11 +2,137 @@ from zExceptions import Unauthorized ...@@ -2,11 +2,137 @@ from zExceptions import Unauthorized
if REQUEST is not None: if REQUEST is not None:
raise Unauthorized raise Unauthorized
if context.getCausalityState() == 'diverged': from DateTime import DateTime
person = context.getDestinationSectionValue(portal_type="Person") portal = context.getPortalObject()
instance_tree = context
now = DateTime()
tag = '%s_%s' % (instance_tree.getUid(), script.id)
activate_kw = {'tag': tag}
if portal.portal_activities.countMessageWithTag(tag) > 0:
# nothing to do
return
def storeWorkflowComment(document, comment):
portal.portal_workflow.doActionFor(document, 'edit_action', comment=comment)
def newOpenOrder(open_sale_order):
open_sale_order_template = portal.restrictedTraverse(
portal.portal_preferences.getPreferredOpenSaleOrderTemplate())
open_order_edit_kw = {
'effective_date': DateTime(),
'activate_kw': activate_kw,
'source': open_sale_order_template.getSource(),
'source_section': open_sale_order_template.getSourceSection()
}
if open_sale_order is None:
new_open_sale_order = open_sale_order_template.Base_createCloneDocument(batch_mode=1)
open_order_edit_kw.update({
'destination': person.getRelativeUrl(),
'destination_decision': person.getRelativeUrl(),
'title': "%s SlapOS Subscription" % person.getTitle(),
})
else:
new_open_sale_order = open_sale_order.Base_createCloneDocument(batch_mode=1)
open_sale_order.setExpirationDate(now, activate_kw=activate_kw)
new_open_sale_order.edit(**open_order_edit_kw)
new_open_sale_order.order(activate_kw=activate_kw)
new_open_sale_order.validate(activate_kw=activate_kw)
return new_open_sale_order
if instance_tree.getCausalityState() == 'diverged':
person = instance_tree.getDestinationSectionValue(portal_type="Person")
# Template document does not have person relation # Template document does not have person relation
if person is not None: if person is not None:
person.Person_storeOpenSaleOrderJournal()
# Search an existing related open order
open_order_line = portal.portal_catalog.getResultValue(
portal_type='Open Sale Order Line',
default_aggregate_uid=instance_tree.getUid())
is_open_order_creation_needed = False
# Simply check that it has never been simulated
if instance_tree.getSlapState() == 'destroy_requested':
# Line should be deleted
if (open_order_line is not None) and (open_order_line.getValidationState() == "invalidated"):
instance_tree.converge(comment="Last open order: %s" % open_order_line.getRelativeUrl())
elif open_order_line is None:
# User has no Open Sale Order (likely).
# No need to charge, as it was never allocated
is_open_order_creation_needed = False
instance_tree.converge(comment="No open order needed as it was never allocated")
elif open_order_line is None:
# Let's add
is_open_order_creation_needed = True
# Let's create the open order
if is_open_order_creation_needed:
open_sale_order = newOpenOrder(None)
open_order_explanation = ""
# Add lines
open_sale_order_line_template = portal.restrictedTraverse(
portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate())
open_order_line = open_sale_order_line_template.Base_createCloneDocument(batch_mode=1,
destination=open_sale_order)
hosting_subscription = portal.hosting_subscription_module.newContent(
portal_type="Hosting Subscription",
title=instance_tree.getTitle()
)
hosting_subscription.validate()
start_date = hosting_subscription.HostingSubscription_calculateSubscriptionStartDate()
edit_kw = {}
subscription_request = instance_tree.getAggregateRelatedValue(portal_type="Subscription Request")
# Define the start date of the period, this can variates with the time.
# start_date_delta = 0
if subscription_request is not None:
# Copy from Subscription Condition the source and Source Section into the line
# RAFAEL: As the model is use single Open Order, it isn't possible to use multiple
# companies per region, so we rely on Subscription Conditions to Describe the
# providers.
edit_kw["source"] = subscription_request.getSource()
edit_kw["source_section"] = subscription_request.getSourceSection()
# Quantity is double because the first invoice has to
# charge for 2 months
edit_kw['quantity'] = subscription_request.getQuantity()
edit_kw['price'] = subscription_request.getPrice()
edit_kw['price_currency'] = subscription_request.getPriceCurrency()
# While create move the start date to be at least 1 months
# So we can charge 3 months at once
# You can increase 65 days to generate 3 months
# You can increase 32 days to generate 2 months
# You can increase 0 days to keep generating one month only
# start_date_delta = 0
open_order_line.edit(
activate_kw=activate_kw,
title=instance_tree.getTitle(),
start_date=start_date,
# Ensure stop date value is higher than start date
# it will be updated by OpenSaleOrder_updatePeriod
stop_date=start_date + 1,
# stop_date=calculateOpenOrderLineStopDate(open_sale_order_line,
# instance_tree, start_date_delta=start_date_delta),
aggregate_value_list=[hosting_subscription, instance_tree],
**edit_kw
)
storeWorkflowComment(open_order_line, "Created for %s" % instance_tree.getRelativeUrl())
# instance_tree.converge(comment="Last open order: %s" % open_sale_order_line.getRelativeUrl())
open_order_explanation = "Added %s." % str(open_order_line.getId())
storeWorkflowComment(open_sale_order, open_order_explanation)
if open_order_line is not None:
open_order_line.getParentValue().OpenSaleOrder_updatePeriod()
# Person_storeOpenSaleOrderJournal should fix all divergent Instance Tree in one run # Person_storeOpenSaleOrderJournal should fix all divergent Instance Tree in one run
assert context.getCausalityState() == 'solved' assert instance_tree.getCausalityState() == 'solved'
...@@ -2,7 +2,87 @@ from zExceptions import Unauthorized ...@@ -2,7 +2,87 @@ from zExceptions import Unauthorized
if REQUEST is not None: if REQUEST is not None:
raise Unauthorized raise Unauthorized
if context.getValidationState() == 'validated': from erp5.component.module.DateUtils import addToDate
person = context.getDestinationDecisionValue(portal_type="Person") from DateTime import DateTime
portal = context.getPortalObject()
open_sale_order = context
now = DateTime()
tag = '%s_%s' % (open_sale_order.getUid(), script.id)
activate_kw = {'tag': tag}
if portal.portal_activities.countMessageWithTag(tag) > 0:
# nothing to do
return
def storeWorkflowComment(document, comment):
portal.portal_workflow.doActionFor(document, 'edit_action', comment=comment)
def calculateOpenOrderLineStopDate(open_order_line, hosting_subscription, instance_tree, start_date_delta, next_stop_date_delta=0):
end_date = instance_tree.InstanceTree_calculateSubscriptionStopDate()
if end_date is None:
# Be sure that start date is different from stop date
# Consider the first period longer (delta), this allow us to change X days/months
# On a first invoice.
next_stop_date = hosting_subscription.getNextPeriodicalDate(
hosting_subscription.HostingSubscription_calculateSubscriptionStartDate() + start_date_delta)
current_stop_date = next_stop_date
# Ensure the invoice is generated 15 days in advance of the next period.
while next_stop_date < now + next_stop_date_delta:
# Return result should be < now, it order to provide stability in simulation (destruction if it happen should be >= now)
current_stop_date = next_stop_date
next_stop_date = \
hosting_subscription.getNextPeriodicalDate(current_stop_date)
return addToDate(current_stop_date, to_add={'second': -1})
else:
stop_date = end_date
return stop_date
if open_sale_order.getValidationState() == 'validated':
person = open_sale_order.getDestinationDecisionValue(portal_type="Person")
if person is not None: if person is not None:
person.Person_storeOpenSaleOrderJournal()
for open_order_line in open_sale_order.contentValues(
portal_type='Open Sale Order Line'):
current_start_date = open_order_line.getStartDate()
current_stop_date = open_order_line.getStopDate()
# Prevent mistakes
assert current_start_date is not None
assert current_stop_date is not None
assert current_start_date < current_stop_date
hosting_subscription = open_order_line.getAggregateValue(portal_type='Hosting Subscription')
instance_tree = open_order_line.getAggregateValue(portal_type='Instance Tree')
assert current_start_date == hosting_subscription.HostingSubscription_calculateSubscriptionStartDate()
subscription_request = instance_tree.getAggregateRelatedValue(portal_type="Subscription Request")
# Define the start date of the period, this can variates with the time.
next_stop_date_delta = 0
if subscription_request is not None:
next_stop_date_delta = 46
# First check if the instance tree has been correctly simulated (this script may run only once per year...)
stop_date = calculateOpenOrderLineStopDate(open_order_line, hosting_subscription, instance_tree,
start_date_delta=0, next_stop_date_delta=next_stop_date_delta)
if current_stop_date < stop_date:
# Bingo, new subscription to generate
open_order_line.edit(
stop_date=stop_date,
activate_kw=activate_kw)
storeWorkflowComment(open_order_line,
'Stop date updated to %s' % stop_date)
if instance_tree.getSlapState() == 'destroy_requested':
# Line should be deleted
assert instance_tree.getCausalityState() == 'diverged'
instance_tree.converge(comment="Last open order: %s" % open_order_line.getRelativeUrl())
open_sale_order.archive()
storeWorkflowComment(open_sale_order, "Instance Tree destroyed: %s" % instance_tree.getRelativeUrl())
elif (instance_tree.getCausalityState() == 'diverged'):
instance_tree.converge(comment="Nothing to do on open order.")
...@@ -34,6 +34,10 @@ select_kw.update( ...@@ -34,6 +34,10 @@ select_kw.update(
default_aggregate_portal_type=ComplexQuery(NegatedQuery(Query(default_aggregate_portal_type='Compute Node')), default_aggregate_portal_type=ComplexQuery(NegatedQuery(Query(default_aggregate_portal_type='Compute Node')),
Query(default_aggregate_portal_type=None),logical_operator="OR"), Query(default_aggregate_portal_type=None),logical_operator="OR"),
grouping_reference=None, grouping_reference=None,
# XXX SELECT DISTINCT uses default_aggregate_portal_type as parameter
# leading to return movement with 2 aggregate values twice
# Use group_by to workaround the issue
group_by_list=['uid'],
sort_on=(('modification_date', 'ASC'),) # the highest chance to find movement which can be delivered sort_on=(('modification_date', 'ASC'),) # the highest chance to find movement which can be delivered
) )
movement_list = portal.portal_catalog(**select_kw) movement_list = portal.portal_catalog(**select_kw)
......
from erp5.component.module.DateUtils import addToDate
from DateTime import DateTime
portal = context.getPortalObject()
now = DateTime()
person = context
tag = '%s_%s' % (person.getUid(), script.id)
activate_kw = {'tag': tag}
if portal.portal_activities.countMessageWithTag(tag) > 0:
# nothing to do
return
def newOpenOrder(open_sale_order):
open_sale_order_template = portal.restrictedTraverse(
portal.portal_preferences.getPreferredOpenSaleOrderTemplate())
open_order_edit_kw = {
'effective_date': DateTime(),
'activate_kw': activate_kw,
'source': open_sale_order_template.getSource(),
'source_section': open_sale_order_template.getSourceSection()
}
if open_sale_order is None:
new_open_sale_order = open_sale_order_template.Base_createCloneDocument(batch_mode=1)
open_order_edit_kw.update({
'destination': person.getRelativeUrl(),
'destination_decision': person.getRelativeUrl(),
'title': "%s SlapOS Subscription" % person.getTitle(),
})
else:
new_open_sale_order = open_sale_order.Base_createCloneDocument(batch_mode=1)
open_sale_order.setExpirationDate(now, activate_kw=activate_kw)
new_open_sale_order.edit(**open_order_edit_kw)
new_open_sale_order.order(activate_kw=activate_kw)
new_open_sale_order.validate(activate_kw=activate_kw)
return new_open_sale_order
def storeWorkflowComment(document, comment):
portal.portal_workflow.doActionFor(document, 'edit_action', comment=comment)
def calculateOpenOrderLineStopDate(open_order_line, instance_tree, start_date_delta, next_stop_date_delta=0):
end_date = instance_tree.InstanceTree_calculateSubscriptionStopDate()
if end_date is None:
# Be sure that start date is different from stop date
# Consider the first period longer (delta), this allow us to change X days/months
# On a first invoice.
next_stop_date = instance_tree.getNextPeriodicalDate(
instance_tree.InstanceTree_calculateSubscriptionStartDate() + start_date_delta)
current_stop_date = next_stop_date
# Ensure the invoice is generated 15 days in advance of the next period.
while next_stop_date < now + next_stop_date_delta:
# Return result should be < now, it order to provide stability in simulation (destruction if it happen should be >= now)
current_stop_date = next_stop_date
next_stop_date = \
instance_tree.getNextPeriodicalDate(current_stop_date)
return addToDate(current_stop_date, to_add={'second': -1})
else:
stop_date = end_date
return stop_date
# Prevent concurrent transaction to update the open order
context.serialize()
# First, check the existing open order. Does some lines need to be removed, updated?
open_sale_order_list = portal.portal_catalog(
default_destination_uid=person.getUid(),
portal_type="Open Sale Order",
validation_state="validated",
limit=2,
)
open_sale_order_count = len(open_sale_order_list)
if open_sale_order_count == 0:
open_sale_order = None
elif open_sale_order_count == 1:
open_sale_order = open_sale_order_list[0].getObject()
else:
raise ValueError, "Too many open order '%s' found: %s" % (person.getRelativeUrl(), [x.path for x in open_sale_order_list])
delete_line_list = []
add_line_list = []
updated_instance_tree_dict = {}
deleted_instance_tree_dict = {}
if open_sale_order is not None:
for open_order_line in open_sale_order.contentValues(
portal_type='Open Sale Order Line'):
current_start_date = open_order_line.getStartDate()
current_stop_date = open_order_line.getStopDate()
# Prevent mistakes
assert current_start_date is not None
assert current_stop_date is not None
assert current_start_date < current_stop_date
instance_tree = open_order_line.getAggregateValue(portal_type='Instance Tree')
assert current_start_date == instance_tree.InstanceTree_calculateSubscriptionStartDate()
subscription_request = instance_tree.getAggregateRelatedValue(portal_type="Subscription Request")
# Define the start date of the period, this can variates with the time.
next_stop_date_delta = 0
if subscription_request is not None:
next_stop_date_delta = 46
# First check if the instance tree has been correctly simulated (this script may run only once per year...)
stop_date = calculateOpenOrderLineStopDate(open_order_line, instance_tree,
start_date_delta=0, next_stop_date_delta=next_stop_date_delta)
if current_stop_date < stop_date:
# Bingo, new subscription to generate
open_order_line.edit(
stop_date=stop_date,
activate_kw=activate_kw)
storeWorkflowComment(open_order_line,
'Stop date updated to %s' % stop_date)
if instance_tree.getSlapState() == 'destroy_requested':
# Line should be deleted
assert instance_tree.getCausalityState() == 'diverged'
delete_line_list.append(open_order_line.getId())
instance_tree.converge(comment="Last open order: %s" % open_order_line.getRelativeUrl())
deleted_instance_tree_dict[instance_tree.getRelativeUrl()] = None
updated_instance_tree_dict[instance_tree.getRelativeUrl()] = None
elif (instance_tree.getCausalityState() == 'diverged'):
instance_tree.converge(comment="Nothing to do on open order.")
updated_instance_tree_dict[instance_tree.getRelativeUrl()] = None
# Time to check the open order line to add (remaining diverged Hosting
# Subscription normally)
for instance_tree in portal.portal_catalog(
portal_type='Instance Tree',
default_destination_section_uid=context.getUid(),
causality_state="diverged"):
instance_tree = instance_tree.getObject()
if instance_tree.getCausalityState() == 'diverged':
# Simply check that it has never been simulated
if instance_tree.getSlapState() == 'destroy_requested':
# Line should be deleted
open_order_line = portal.portal_catalog.getResultValue(
portal_type='Open Sale Order Line',
default_aggregate_uid=instance_tree.getUid())
if open_order_line is not None and open_order_line.getValidationState() == "invalidated":
instance_tree.converge(comment="Last open order: %s" % open_order_line.getRelativeUrl())
elif open_order_line is None:
# User has no Open Sale Order (likely), so we add the line to remove later. This allow us to charge
# eventual usage between the runs of the alarm.
add_line_list.append(instance_tree)
else:
assert len(portal.portal_catalog(
portal_type='Open Sale Order Line',
default_aggregate_uid=instance_tree.getUid(),
limit=1)) == 0
# Let's add
add_line_list.append(instance_tree)
else:
# Should be in the list of lines to remove
assert (instance_tree.getRelativeUrl() in deleted_instance_tree_dict) or \
(instance_tree.getRelativeUrl() in updated_instance_tree_dict)
manual_archive = False
if (add_line_list):
# No need to create a new open order to add lines
if open_sale_order is None:
open_sale_order = newOpenOrder(None)
manual_archive = True
open_order_explanation = ""
# Add lines
added_line_list = []
open_sale_order_line_template = portal.restrictedTraverse(
portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate())
for instance_tree in add_line_list:
open_sale_order_line = open_sale_order_line_template.Base_createCloneDocument(batch_mode=1,
destination=open_sale_order)
start_date = instance_tree.InstanceTree_calculateSubscriptionStartDate()
edit_kw = {}
subscription_request = instance_tree.getAggregateRelatedValue(portal_type="Subscription Request")
# Define the start date of the period, this can variates with the time.
start_date_delta = 0
if subscription_request is not None:
# Copy from Subscription Condition the source and Source Section into the line
# RAFAEL: As the model is use single Open Order, it isn't possible to use multiple
# companies per region, so we rely on Subscription Conditions to Describe the
# providers.
edit_kw["source"] = subscription_request.getSource()
edit_kw["source_section"] = subscription_request.getSourceSection()
# Quantity is double because the first invoice has to
# charge for 2 months
edit_kw['quantity'] = subscription_request.getQuantity()
edit_kw['price'] = subscription_request.getPrice()
edit_kw['price_currency'] = subscription_request.getPriceCurrency()
# While create move the start date to be at least 1 months
# So we can charge 3 months at once
# You can increase 65 days to generate 3 months
# You can increase 32 days to generate 2 months
# You can increase 0 days to keep generating one month only
start_date_delta = 0
open_sale_order_line.edit(
activate_kw=activate_kw,
title=instance_tree.getTitle(),
start_date=start_date,
stop_date=calculateOpenOrderLineStopDate(open_sale_order_line,
instance_tree, start_date_delta=start_date_delta),
aggregate_value=instance_tree,
**edit_kw
)
storeWorkflowComment(open_sale_order_line, "Created for %s" % instance_tree.getRelativeUrl())
if (instance_tree.getSlapState() == 'destroy_requested'):
# Added line to delete immediately
delete_line_list.append(open_sale_order_line.getId())
instance_tree.converge(comment="Last open order: %s" % open_sale_order_line.getRelativeUrl())
else:
instance_tree.converge(comment="First open order: %s" % open_sale_order_line.getRelativeUrl())
added_line_list.append(open_sale_order_line.getId())
open_order_explanation += "Added %s." % str(added_line_list)
new_open_sale_order = None
if (delete_line_list):
# All Verifications done. Time to clone/create open order
new_open_sale_order = newOpenOrder(open_sale_order)
if manual_archive == True:
open_sale_order.archive()
open_order_explanation = ""
# Remove lines
new_open_sale_order.deleteContent(delete_line_list)
open_order_explanation += "Removed %s." % str(delete_line_list)
storeWorkflowComment(new_open_sale_order, open_order_explanation)
open_sale_order = new_open_sale_order
if open_sale_order is not None:
if not len(open_sale_order.contentValues(
portal_type='Open Sale Order Line')):
open_sale_order.archive()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_storeOpenSaleOrderJournal</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>InstanceTree_getRuleReference</string> </value> <value> <string>HostingSubscription_getRuleReference</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -6,6 +6,8 @@ root_applied_rule = context.getRootAppliedRule() ...@@ -6,6 +6,8 @@ root_applied_rule = context.getRootAppliedRule()
root_applied_rule_path = root_applied_rule.getPath() root_applied_rule_path = root_applied_rule.getPath()
business_link = context.getCausalityValue(portal_type='Business Link') business_link = context.getCausalityValue(portal_type='Business Link')
if business_link is None:
raise ValueError('Movement without business link: %s' % context.getRelativeUrl())
lock_tag = 'build_in_progress_%s_%s' % (business_link.getUid(), root_applied_rule.getUid()) lock_tag = 'build_in_progress_%s_%s' % (business_link.getUid(), root_applied_rule.getUid())
if context.getPortalObject().portal_activities.countMessageWithTag(lock_tag) == 0: if context.getPortalObject().portal_activities.countMessageWithTag(lock_tag) == 0:
business_link.build(path='%s/%%' % root_applied_rule_path, activate_kw={'tag': tag}) business_link.build(path='%s/%%' % root_applied_rule_path, activate_kw={'tag': tag})
......
...@@ -35,7 +35,7 @@ from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixin, withAbo ...@@ -35,7 +35,7 @@ from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixin, withAbo
import os import os
import tempfile import tempfile
from DateTime import DateTime from DateTime import DateTime
from erp5.component.module.DateUtils import addToDate, getClosestDate from erp5.component.module.DateUtils import addToDate#, getClosestDate
from zExceptions import Unauthorized from zExceptions import Unauthorized
class Simulator: class Simulator:
...@@ -128,22 +128,6 @@ class TestOpenSaleOrderAlarm(SlapOSTestCaseMixin): ...@@ -128,22 +128,6 @@ class TestOpenSaleOrderAlarm(SlapOSTestCaseMixin):
default_destination_uid=person.getUid() default_destination_uid=person.getUid()
)) ))
def test_OSO_after_Person_updateOpenSaleOrder(self):
person = self.portal.person_module.template_member\
.Base_createCloneDocument(batch_mode=1)
self.tic()
person.Person_storeOpenSaleOrderJournal()
self.tic()
open_sale_order_list = self.portal.portal_catalog(
validation_state='validated',
portal_type='Open Sale Order',
default_destination_uid=person.getUid()
)
# No need to create any open order without instance tree
self.assertEqual(0, len(open_sale_order_list))
@simulateByEditWorkflowMark('InstanceTree_requestUpdateOpenSaleOrder') @simulateByEditWorkflowMark('InstanceTree_requestUpdateOpenSaleOrder')
def test_alarm_HS_diverged(self): def test_alarm_HS_diverged(self):
subscription = self.portal.instance_tree_module\ subscription = self.portal.instance_tree_module\
...@@ -205,7 +189,12 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -205,7 +189,12 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
self.assertEqual(1, len(open_sale_order_line_list)) self.assertEqual(1, len(open_sale_order_line_list))
line = open_sale_order_line_list[0].getObject() line = open_sale_order_line_list[0].getObject()
self.assertEqual(subscription.getRelativeUrl(), line.getAggregate()) hosting_subscription = line.getAggregateValueList()[0]
self.assertEqual("Hosting Subscription",
hosting_subscription.getPortalType())
self.assertEqual("validated",
hosting_subscription.getValidationState())
self.assertEqual(subscription.getRelativeUrl(), line.getAggregateList()[1])
open_sale_order_line_template = self.portal.restrictedTraverse( open_sale_order_line_template = self.portal.restrictedTraverse(
self.portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate()) self.portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate())
self.assertEqual(open_sale_order_line_template.getResource(), self.assertEqual(open_sale_order_line_template.getResource(),
...@@ -218,7 +207,7 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -218,7 +207,7 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
line.getPrice()) line.getPrice())
self.assertEqual(DateTime().earliestTime(), line.getStartDate()) self.assertEqual(DateTime().earliestTime(), line.getStartDate())
self.assertEqual(min(DateTime().day(), 28), self.assertEqual(min(DateTime().day(), 28),
subscription.getPeriodicityMonthDay()) hosting_subscription.getPeriodicityMonthDay())
start_date = addToDate(line.getStartDate(), to_add={'month': 1}) start_date = addToDate(line.getStartDate(), to_add={'month': 1})
start_date = addToDate(start_date, to_add={'second': -1}) start_date = addToDate(start_date, to_add={'second': -1})
while start_date.day() >= 28: while start_date.day() >= 28:
...@@ -245,9 +234,6 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -245,9 +234,6 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
'time': request_time, 'time': request_time,
'action': 'request_instance' 'action': 'request_instance'
}] }]
subscription.edit(periodicity_month_day_list=[])
subscription.fixConsistency()
self.assertEqual(subscription.getPeriodicityMonthDay(), 1)
self.tic() self.tic()
subscription.InstanceTree_requestUpdateOpenSaleOrder() subscription.InstanceTree_requestUpdateOpenSaleOrder()
...@@ -269,15 +255,13 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -269,15 +255,13 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
self.assertEqual(1, len(open_sale_order_line_list)) self.assertEqual(1, len(open_sale_order_line_list))
line = open_sale_order_line_list[0].getObject() line = open_sale_order_line_list[0].getObject()
# calculate stop date to be after now, begin with start date with precision hosting_subscription = line.getAggregateValueList()[0]
# of month # self.assertEqual(hosting_subscription.getPeriodicityMonthDay(), 1)
now = DateTime() self.assertEqual("Hosting Subscription",
now = now.toZone(request_time.timezone()) hosting_subscription.getPortalType())
stop_date = getClosestDate(target_date=now, precision='month') self.assertEqual("validated",
stop_date = addToDate(stop_date, to_add={'second': -1}) hosting_subscription.getValidationState())
self.assertEqual(stop_date, line.getStopDate()) self.assertEqual(subscription.getRelativeUrl(), line.getAggregateList()[1])
self.assertEqual(subscription.getRelativeUrl(), line.getAggregate())
open_sale_order_line_template = self.portal.restrictedTraverse( open_sale_order_line_template = self.portal.restrictedTraverse(
self.portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate()) self.portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate())
self.assertTrue(all([q in line.getCategoryList() \ self.assertTrue(all([q in line.getCategoryList() \
...@@ -288,8 +272,14 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -288,8 +272,14 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
line.getQuantity()) line.getQuantity())
self.assertEqual(open_sale_order_line_template.getPrice(), self.assertEqual(open_sale_order_line_template.getPrice(),
line.getPrice()) line.getPrice())
self.assertEqual(request_time, line.getStartDate()) self.assertEqual(DateTime().earliestTime(), line.getStartDate())
self.assertEqual(stop_date, line.getStopDate()) self.assertEqual(min(DateTime().day(), 28),
hosting_subscription.getPeriodicityMonthDay())
start_date = addToDate(line.getStartDate(), to_add={'month': 1})
start_date = addToDate(start_date, to_add={'second': -1})
while start_date.day() >= 28:
start_date = addToDate(start_date, to_add={'day': -1})
self.assertEqual(start_date, line.getStopDate())
destroy_time = DateTime('2112/02/01') destroy_time = DateTime('2112/02/01')
subscription.workflow_history['instance_slap_interface_workflow'].append({ subscription.workflow_history['instance_slap_interface_workflow'].append({
...@@ -312,28 +302,22 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -312,28 +302,22 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
default_destination_uid=person.getUid() default_destination_uid=person.getUid()
) )
self.assertEqual(2, len(open_sale_order_list)) self.assertEqual(1, len(open_sale_order_list))
validated_open_sale_order_list = [q for q in open_sale_order_list validated_open_sale_order_list = [q for q in open_sale_order_list
if q.getValidationState() == 'validated'] if q.getValidationState() == 'validated']
archived_open_sale_order_list = [q for q in open_sale_order_list archived_open_sale_order_list = [q for q in open_sale_order_list
if q.getValidationState() == 'archived'] if q.getValidationState() == 'archived']
self.assertEqual(0, len(validated_open_sale_order_list)) self.assertEqual(0, len(validated_open_sale_order_list))
self.assertEqual(2, len(archived_open_sale_order_list)) self.assertEqual(1, len(archived_open_sale_order_list))
archived_open_sale_order_list.sort(key=lambda x: x.getCreationDate())
last_open_sale_order = archived_open_sale_order_list[-1].getObject()
archived_open_sale_order = archived_open_sale_order_list[0]\ archived_open_sale_order = archived_open_sale_order_list[0]\
.getObject() .getObject()
self.assertEqual(open_sale_order.getRelativeUrl(), self.assertEqual(open_sale_order.getRelativeUrl(),
archived_open_sale_order.getRelativeUrl()) archived_open_sale_order.getRelativeUrl())
last_line_list = last_open_sale_order.contentValues(
portal_type='Open Sale Order Line')
archived_line_list = archived_open_sale_order.contentValues( archived_line_list = archived_open_sale_order.contentValues(
portal_type='Open Sale Order Line') portal_type='Open Sale Order Line')
self.assertEqual(0, len(last_line_list))
self.assertEqual(1, len(archived_line_list)) self.assertEqual(1, len(archived_line_list))
archived_line = archived_line_list[0].getObject() archived_line = archived_line_list[0].getObject()
...@@ -341,7 +325,7 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -341,7 +325,7 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
self.assertEqual(line.getRelativeUrl(), archived_line.getRelativeUrl()) self.assertEqual(line.getRelativeUrl(), archived_line.getRelativeUrl())
self.assertEqual(subscription.getRelativeUrl(), self.assertEqual(subscription.getRelativeUrl(),
archived_line.getAggregate()) archived_line.getAggregateList()[1])
self.assertTrue(all([q in archived_line.getCategoryList() \ self.assertTrue(all([q in archived_line.getCategoryList() \
for q in open_sale_order_line_template.getCategoryList()])) for q in open_sale_order_line_template.getCategoryList()]))
self.assertEqual(open_sale_order_line_template.getResource(), self.assertEqual(open_sale_order_line_template.getResource(),
...@@ -350,7 +334,7 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -350,7 +334,7 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
line.getQuantity()) line.getQuantity())
self.assertEqual(open_sale_order_line_template.getPrice(), self.assertEqual(open_sale_order_line_template.getPrice(),
line.getPrice()) line.getPrice())
self.assertEqual(request_time, archived_line.getStartDate()) self.assertEqual(DateTime().earliestTime(), archived_line.getStartDate())
self.assertEqual(DateTime('2112/02/02'), line.getStopDate()) self.assertEqual(DateTime('2112/02/02'), line.getStopDate())
def test_lateAnalysed_InstanceTree(self): def test_lateAnalysed_InstanceTree(self):
...@@ -397,52 +381,7 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -397,52 +381,7 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
default_destination_uid=person.getUid() default_destination_uid=person.getUid()
) )
self.assertEqual(2, len(open_sale_order_list)) self.assertEqual(0, len(open_sale_order_list))
archived_open_sale_order_list = [x for x in open_sale_order_list \
if x.getValidationState() != 'validated' and \
len(x.objectValues()) > 0]
self.assertEqual(1, len(archived_open_sale_order_list))
open_sale_order = archived_open_sale_order_list[0].getObject()
self.assertEqual('archived', open_sale_order.getValidationState())
open_sale_order_line_list = open_sale_order.contentValues(
portal_type='Open Sale Order Line')
self.assertEqual(1, len(open_sale_order_line_list))
line = open_sale_order_line_list[0].getObject()
self.assertEqual(subscription.getRelativeUrl(), line.getAggregate())
open_sale_order_line_template = self.portal.restrictedTraverse(
self.portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate())
self.assertTrue(all([q in line.getCategoryList() \
for q in open_sale_order_line_template.getCategoryList()]))
self.assertEqual(open_sale_order_line_template.getResource(),
line.getResource())
self.assertEqual(open_sale_order_line_template.getQuantity(),
line.getQuantity())
self.assertEqual(open_sale_order_line_template.getPrice(),
line.getPrice())
self.assertEqual(request_time, line.getStartDate())
self.assertEqual(DateTime('2012/02/02'), line.getStopDate())
new_validated_open_sale_order_list = [x for x in open_sale_order_list \
if x.getValidationState() == 'validated']
self.assertEqual(0, len(new_validated_open_sale_order_list))
archived_open_sale_order_list = [x for x in open_sale_order_list \
if x.getValidationState() != 'validated']
archived_open_sale_order_list.sort(key=lambda x: x.getCreationDate(), reverse=True)
new_open_sale_order = archived_open_sale_order_list[0]
# The OSO is archived as soon it has no lines anymore.
self.assertEqual('archived', new_open_sale_order.getValidationState())
open_sale_order_line_list = new_open_sale_order.contentValues(
portal_type='Open Sale Order Line')
self.assertEqual(0, len(open_sale_order_line_list))
def test_two_InstanceTree(self): def test_two_InstanceTree(self):
person = self.portal.person_module.template_member\ person = self.portal.person_module.template_member\
...@@ -464,8 +403,6 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -464,8 +403,6 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
'time': request_time, 'time': request_time,
'action': 'request_instance' 'action': 'request_instance'
}] }]
subscription.edit(periodicity_month_day_list=[])
subscription.fixConsistency()
self.tic() self.tic()
subscription.InstanceTree_requestUpdateOpenSaleOrder() subscription.InstanceTree_requestUpdateOpenSaleOrder()
...@@ -486,7 +423,11 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -486,7 +423,11 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
self.assertEqual(1, len(open_sale_order_line_list)) self.assertEqual(1, len(open_sale_order_line_list))
line = open_sale_order_line_list[0].getObject() line = open_sale_order_line_list[0].getObject()
self.assertEqual(subscription.getRelativeUrl(), line.getAggregate()) self.assertEqual("Hosting Subscription",
line.getAggregateValueList()[0].getPortalType())
self.assertEqual("validated",
line.getAggregateValueList()[0].getValidationState())
self.assertEqual(subscription.getRelativeUrl(), line.getAggregateList()[1])
open_sale_order_line_template = self.portal.restrictedTraverse( open_sale_order_line_template = self.portal.restrictedTraverse(
self.portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate()) self.portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate())
self.assertTrue(all([q in line.getCategoryList() \ self.assertTrue(all([q in line.getCategoryList() \
...@@ -497,18 +438,6 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -497,18 +438,6 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
line.getQuantity()) line.getQuantity())
self.assertEqual(open_sale_order_line_template.getPrice(), self.assertEqual(open_sale_order_line_template.getPrice(),
line.getPrice()) line.getPrice())
self.assertEqual(request_time, line.getStartDate())
# calculate stop date to be after now, begin with start date with precision
# of month
stop_date = request_time
next_stop_date = stop_date
now = DateTime()
while next_stop_date < now:
stop_date = next_stop_date
next_stop_date = addToDate(stop_date, to_add={'month': 1})
stop_date = addToDate(stop_date, to_add={'second': -1})
self.assertEqual(stop_date, line.getStopDate())
subscription2 = self.portal.instance_tree_module\ subscription2 = self.portal.instance_tree_module\
.template_instance_tree.Base_createCloneDocument(batch_mode=1) .template_instance_tree.Base_createCloneDocument(batch_mode=1)
...@@ -538,36 +467,35 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -538,36 +467,35 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
default_destination_uid=person.getUid() default_destination_uid=person.getUid()
) )
self.assertEqual(1, len(open_sale_order_list)) self.assertEqual(2, len(open_sale_order_list))
validated_open_sale_order_list = [q for q in open_sale_order_list validated_open_sale_order_list = [q for q in open_sale_order_list
if q.getValidationState() == 'validated'] if q.getValidationState() == 'validated']
archived_open_sale_order_list = [q for q in open_sale_order_list archived_open_sale_order_list = [q for q in open_sale_order_list
if q.getValidationState() == 'archived'] if q.getValidationState() == 'archived']
self.assertEqual(1, len(validated_open_sale_order_list)) self.assertEqual(2, len(validated_open_sale_order_list))
self.assertEqual(0, len(archived_open_sale_order_list)) self.assertEqual(0, len(archived_open_sale_order_list))
validated_open_sale_order = validated_open_sale_order_list[0].getObject()
validated_line_list = validated_open_sale_order.contentValues(
open_sale_order_2 = [x for x in validated_open_sale_order_list if x.getRelativeUrl() != open_sale_order.getRelativeUrl()][0]
self.assertEqual(open_sale_order.getRelativeUrl(), [x for x in validated_open_sale_order_list if x.getRelativeUrl() == open_sale_order.getRelativeUrl()][0].getRelativeUrl())
validated_line_list = open_sale_order_2.contentValues(
portal_type='Open Sale Order Line') portal_type='Open Sale Order Line')
self.assertEqual(2, len(validated_line_list)) self.assertEqual(1, len(validated_line_list))
validated_line_2 = validated_line_list[0]
validated_line_1 = line
self.assertEqual(open_sale_order_line_template.getQuantity(), self.assertEqual(open_sale_order_line_template.getQuantity(),
line.getQuantity()) line.getQuantity())
self.assertEqual(open_sale_order_line_template.getPrice(), self.assertEqual(open_sale_order_line_template.getPrice(),
line.getPrice()) line.getPrice())
stop_date_2 = request_time_2 hosting_subscription_2 = validated_line_2.getAggregateValueList()[0]
next_stop_date_2 = stop_date_2 self.assertEqual("Hosting Subscription",
now = DateTime() hosting_subscription_2.getPortalType())
while next_stop_date_2 < now: self.assertEqual("validated",
stop_date_2 = next_stop_date_2 hosting_subscription_2.getValidationState())
next_stop_date_2 = addToDate(stop_date_2, to_add={'month': 1}) self.assertEqual(subscription2.getRelativeUrl(), validated_line_2.getAggregateList()[1])
stop_date_2 = addToDate(stop_date_2, to_add={'second': -1})
validated_line_1 = [q for q in validated_line_list if q.getAggregate() == \
subscription.getRelativeUrl()][0]
validated_line_2 = [q for q in validated_line_list if q.getAggregate() == \
subscription2.getRelativeUrl()][0]
self.assertTrue(all([q in validated_line_1.getCategoryList() \ self.assertTrue(all([q in validated_line_1.getCategoryList() \
for q in open_sale_order_line_template.getCategoryList()])) for q in open_sale_order_line_template.getCategoryList()]))
...@@ -577,8 +505,8 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -577,8 +505,8 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
line.getQuantity()) line.getQuantity())
self.assertEqual(open_sale_order_line_template.getPrice(), self.assertEqual(open_sale_order_line_template.getPrice(),
line.getPrice()) line.getPrice())
self.assertEqual(request_time, validated_line_1.getStartDate()) #self.assertEqual(request_time, validated_line_1.getStartDate())
self.assertEqual(stop_date, validated_line_1.getStopDate()) #self.assertEqual(stop_date, validated_line_1.getStopDate())
self.assertTrue(all([q in validated_line_2.getCategoryList() \ self.assertTrue(all([q in validated_line_2.getCategoryList() \
for q in open_sale_order_line_template.getCategoryList()])) for q in open_sale_order_line_template.getCategoryList()]))
...@@ -588,8 +516,8 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -588,8 +516,8 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
line.getQuantity()) line.getQuantity())
self.assertEqual(open_sale_order_line_template.getPrice(), self.assertEqual(open_sale_order_line_template.getPrice(),
line.getPrice()) line.getPrice())
self.assertEqual(request_time_2, validated_line_2.getStartDate()) #self.assertEqual(request_time_2, validated_line_2.getStartDate())
self.assertEqual(stop_date_2, validated_line_2.getStopDate()) #self.assertEqual(stop_date_2, validated_line_2.getStopDate())
def test_instance_tree_start_date_not_changed(self): def test_instance_tree_start_date_not_changed(self):
# if there was no request_instance the getCreationDate has been used # if there was no request_instance the getCreationDate has been used
...@@ -688,50 +616,8 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin): ...@@ -688,50 +616,8 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
default_destination_uid=person.getUid() default_destination_uid=person.getUid()
) )
self.assertEqual(2,len(open_sale_order_list)) self.assertEqual(0,len(open_sale_order_list))
archived_open_sale_order_list = [x for x in open_sale_order_list \
if x.getValidationState() != 'validated']
archived_open_sale_order_list.sort(key=lambda x: x.getCreationDate())
open_sale_order = archived_open_sale_order_list[0].getObject()
self.assertEqual('archived', open_sale_order.getValidationState())
open_sale_order_line_list = open_sale_order.contentValues(
portal_type='Open Sale Order Line')
self.assertEqual(1, len(open_sale_order_line_list))
effective_date = open_sale_order.getEffectiveDate()
line = open_sale_order_line_list[0].getObject()
self.assertEqual(subscription.getRelativeUrl(), line.getAggregate())
open_sale_order_line_template = self.portal.restrictedTraverse(
self.portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate())
self.assertEqual(open_sale_order_line_template.getResource(),
line.getResource())
self.assertTrue(all([q in line.getCategoryList() \
for q in open_sale_order_line_template.getCategoryList()]))
self.assertEqual(open_sale_order_line_template.getQuantity(),
line.getQuantity())
self.assertEqual(open_sale_order_line_template.getPrice(),
line.getPrice())
self.assertEqual(DateTime().earliestTime(), line.getStartDate())
self.assertEqual(addToDate(line.getStartDate(), to_add={'day': 1}),
line.getStopDate())
archived_open_sale_order_list = [x for x in open_sale_order_list \
if x.getValidationState() != 'validated']
archived_open_sale_order_list.sort(key=lambda x: x.getCreationDate())
new_open_sale_order = archived_open_sale_order_list[-1].getObject()
self.assertEqual('archived', new_open_sale_order.getValidationState())
new_effective_date = new_open_sale_order.getEffectiveDate()
open_sale_order_line_list = new_open_sale_order.contentValues(
portal_type='Open Sale Order Line')
self.assertEqual(0, len(open_sale_order_line_list))
self.assertTrue(new_effective_date > effective_date,
"%s <= %s" % (new_effective_date, effective_date))
class TestSlapOSTriggerBuildAlarm(SlapOSTestCaseMixin): class TestSlapOSTriggerBuildAlarm(SlapOSTestCaseMixin):
@simulateByTitlewMark('SimulationMovement_buildSlapOS') @simulateByTitlewMark('SimulationMovement_buildSlapOS')
...@@ -1284,11 +1170,10 @@ class TestSlapOSUpdateOpenSaleOrderPeriod(SlapOSTestCaseMixin): ...@@ -1284,11 +1170,10 @@ class TestSlapOSUpdateOpenSaleOrderPeriod(SlapOSTestCaseMixin):
open_order.edit( open_order.edit(
destination_decision_value=person, destination_decision_value=person,
) )
open_order.newContent(
open_order.OpenSaleOrder_updatePeriod() portal_type="Open Sale Order Line"
self.assertEqual( )
'Visited by Person_storeOpenSaleOrderJournal', self.assertRaises(AssertionError, open_order.OpenSaleOrder_updatePeriod)
person.workflow_history['edit_workflow'][-1]['comment'])
@simulateByEditWorkflowMark('Person_storeOpenSaleOrderJournal') @simulateByEditWorkflowMark('Person_storeOpenSaleOrderJournal')
def test_updatePeriod_invalidated(self): def test_updatePeriod_invalidated(self):
...@@ -1298,9 +1183,11 @@ class TestSlapOSUpdateOpenSaleOrderPeriod(SlapOSTestCaseMixin): ...@@ -1298,9 +1183,11 @@ class TestSlapOSUpdateOpenSaleOrderPeriod(SlapOSTestCaseMixin):
open_order.edit( open_order.edit(
destination_decision_value=person, destination_decision_value=person,
) )
open_order.newContent(
portal_type="Open Sale Order Line"
)
open_order.invalidate() open_order.invalidate()
open_order.OpenSaleOrder_updatePeriod() open_order.OpenSaleOrder_updatePeriod()
self.assertNotEqual( self.assertNotEqual(
'Visited by Person_storeOpenSaleOrderJournal', 'Visited by Person_storeOpenSaleOrderJournal',
person.workflow_history['edit_workflow'][-1]['comment']) person.workflow_history['edit_workflow'][-1]['comment'])
...@@ -1502,4 +1389,4 @@ class TestSlapOSCancelSaleTnvoiceTransactionPaiedPaymentListAlarm(SlapOSTestCase ...@@ -1502,4 +1389,4 @@ class TestSlapOSCancelSaleTnvoiceTransactionPaiedPaymentListAlarm(SlapOSTestCase
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionisLettered') @simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionisLettered')
def test_payment_is_started_wechat(self): def test_payment_is_started_wechat(self):
self._test_payment_is_started(payment_mode="wechat") self._test_payment_is_started(payment_mode="wechat")
\ No newline at end of file
...@@ -11,13 +11,13 @@ from unittest import skip ...@@ -11,13 +11,13 @@ from unittest import skip
import transaction import transaction
class TestInstanceTree(TestSlapOSConstraintMixin): class TestHostingSubscription(TestSlapOSConstraintMixin):
# use decrator in order to avoid fixing consistency of new object # use decrator in order to avoid fixing consistency of new object
@WorkflowMethod.disable @WorkflowMethod.disable
def _createInstanceTree(self): def _createInstanceTree(self):
self.subscription = self.portal.instance_tree_module.newContent( self.subscription = self.portal.hosting_subscription_module.newContent(
portal_type='Instance Tree') portal_type='Hosting Subscription')
def afterSetUp(self): def afterSetUp(self):
TestSlapOSConstraintMixin.afterSetUp(self) TestSlapOSConstraintMixin.afterSetUp(self)
......
...@@ -9,8 +9,8 @@ class TestSlapOSAccountingInteractionWorkflow(SlapOSTestCaseMixin): ...@@ -9,8 +9,8 @@ class TestSlapOSAccountingInteractionWorkflow(SlapOSTestCaseMixin):
def beforeTearDown(self): def beforeTearDown(self):
transaction.abort() transaction.abort()
def _simulateInstanceTree_calculateSubscriptionStartDate(self, date): def _simulateHostingSubscription_calculateSubscriptionStartDate(self, date):
script_name = 'InstanceTree_calculateSubscriptionStartDate' script_name = 'HostingSubscription_calculateSubscriptionStartDate'
if script_name in self.portal.portal_skins.custom.objectIds(): if script_name in self.portal.portal_skins.custom.objectIds():
raise ValueError('Precondition failed: %s exists in custom' % script_name) raise ValueError('Precondition failed: %s exists in custom' % script_name)
createZODBPythonScript(self.portal.portal_skins.custom, createZODBPythonScript(self.portal.portal_skins.custom,
...@@ -21,17 +21,17 @@ class TestSlapOSAccountingInteractionWorkflow(SlapOSTestCaseMixin): ...@@ -21,17 +21,17 @@ class TestSlapOSAccountingInteractionWorkflow(SlapOSTestCaseMixin):
return DateTime('%s') """ % date.ISO()) return DateTime('%s') """ % date.ISO())
transaction.commit() transaction.commit()
def _dropInstanceTree_calculateSubscriptionStartDate(self): def _dropHostingSubscription_calculateSubscriptionStartDate(self):
script_name = 'InstanceTree_calculateSubscriptionStartDate' script_name = 'HostingSubscription_calculateSubscriptionStartDate'
if script_name in self.portal.portal_skins.custom.objectIds(): if script_name in self.portal.portal_skins.custom.objectIds():
self.portal.portal_skins.custom.manage_delObjects(script_name) self.portal.portal_skins.custom.manage_delObjects(script_name)
transaction.commit() transaction.commit()
def test_InstanceTree_fixConsistency(self, def test_HostingSubscription_fixConsistency(self,
date=DateTime('2012/01/15'), day=15): date=DateTime('2012/01/15'), day=15):
new_id = self.generateNewId() new_id = self.generateNewId()
item = self.portal.instance_tree_module.newContent( item = self.portal.hosting_subscription_module.newContent(
portal_type='Instance Tree', portal_type='Hosting Subscription',
title="Subscription %s" % new_id, title="Subscription %s" % new_id,
reference="TESTSUB-%s" % new_id, reference="TESTSUB-%s" % new_id,
periodicity_hour_list=None, periodicity_hour_list=None,
...@@ -43,48 +43,48 @@ return DateTime('%s') """ % date.ISO()) ...@@ -43,48 +43,48 @@ return DateTime('%s') """ % date.ISO())
self.assertEqual(item.getPeriodicityMinute(), None) self.assertEqual(item.getPeriodicityMinute(), None)
self.assertEqual(item.getPeriodicityMonthDay(), None) self.assertEqual(item.getPeriodicityMonthDay(), None)
self._simulateInstanceTree_calculateSubscriptionStartDate(date) self._simulateHostingSubscription_calculateSubscriptionStartDate(date)
try: try:
item.fixConsistency() item.fixConsistency()
finally: finally:
self._dropInstanceTree_calculateSubscriptionStartDate() self._dropHostingSubscription_calculateSubscriptionStartDate()
self.assertEqual(item.getPeriodicityHourList(), [0]) self.assertEqual(item.getPeriodicityHourList(), [0])
self.assertEqual(item.getPeriodicityMinuteList(), [0]) self.assertEqual(item.getPeriodicityMinuteList(), [0])
self.assertEqual(item.getPeriodicityMonthDay(), day) self.assertEqual(item.getPeriodicityMonthDay(), day)
def test_InstanceTree_fixConsistency_today_after_28(self): def test_HostingSubscription_fixConsistency_today_after_28(self):
self.test_InstanceTree_fixConsistency(DateTime('2012/01/29'), 28) self.test_HostingSubscription_fixConsistency(DateTime('2012/01/29'), 28)
def test_InstanceTree_manageAfter(self): def test_HostingSubscription_manageAfter(self):
class DummyTestException(Exception): class DummyTestException(Exception):
pass pass
def verify_fixConsistency_call(self): def verify_fixConsistency_call(self):
# Check that fixConsistency is called on instance tree # Check that fixConsistency is called on instance tree
if self.getRelativeUrl().startswith('instance_tree_module/'): if self.getRelativeUrl().startswith('hosting_subscription_module/'):
raise DummyTestException raise DummyTestException
else: else:
return self.fixConsistency_call() return self.fixConsistency_call()
# Replace serialize by a dummy method # Replace serialize by a dummy method
InstanceTreeClass = self.portal.portal_types.getPortalTypeClass( HostingSubscriptionClass = self.portal.portal_types.getPortalTypeClass(
'Instance Tree') 'Hosting Subscription')
InstanceTreeClass.fixConsistency_call = InstanceTreeClass.\ HostingSubscriptionClass.fixConsistency_call = HostingSubscriptionClass.\
fixConsistency fixConsistency
InstanceTreeClass.fixConsistency = verify_fixConsistency_call HostingSubscriptionClass.fixConsistency = verify_fixConsistency_call
try: try:
# manage_afterAdd # manage_afterAdd
self.assertRaises( self.assertRaises(
DummyTestException, DummyTestException,
self.portal.instance_tree_module.newContent, self.portal.hosting_subscription_module.newContent,
portal_type='Instance Tree') portal_type='Hosting Subscription')
# manage_afterClone # manage_afterClone
self.assertRaises( self.assertRaises(
DummyTestException, DummyTestException,
self.portal.instance_tree_module.\ self.portal.hosting_subscription_module.\
template_instance_tree.Base_createCloneDocument, template_hosting_subscription.Base_createCloneDocument,
batch_mode=1) batch_mode=1)
finally: finally:
self.portal.portal_types.resetDynamicDocumentsOnceAtTransactionBoundary() self.portal.portal_types.resetDynamicDocumentsOnceAtTransactionBoundary()
......
...@@ -417,11 +417,12 @@ class TestDefaultPaymentRule(SlapOSTestCaseMixin): ...@@ -417,11 +417,12 @@ class TestDefaultPaymentRule(SlapOSTestCaseMixin):
SimulationMovement.getSimulationState = SimulationMovement\ SimulationMovement.getSimulationState = SimulationMovement\
.original_getSimulationState .original_getSimulationState
class TestInstanceTreeSimulation(SlapOSTestCaseMixin): class TestHostingSubscriptionSimulation(SlapOSTestCaseMixin):
def _prepare(self): def _prepare(self):
person = self.portal.person_module.template_member\ person = self.portal.person_module.template_member\
.Base_createCloneDocument(batch_mode=1) .Base_createCloneDocument(batch_mode=1)
self.subscription = self.portal.instance_tree_module\ self.subscription = self.portal.hosting_subscription_module.newContent()
self.instance_tree = self.portal.instance_tree_module\
.template_instance_tree.Base_createCloneDocument(batch_mode=1) .template_instance_tree.Base_createCloneDocument(batch_mode=1)
self.initial_date = DateTime('2011/02/16') self.initial_date = DateTime('2011/02/16')
stop_date = DateTime('2011/04/16') stop_date = DateTime('2011/04/16')
...@@ -432,6 +433,7 @@ class TestInstanceTreeSimulation(SlapOSTestCaseMixin): ...@@ -432,6 +433,7 @@ class TestInstanceTreeSimulation(SlapOSTestCaseMixin):
destination_section=person.getRelativeUrl() destination_section=person.getRelativeUrl()
) )
self.portal.portal_workflow._jumpToStateFor(self.subscription, 'validated') self.portal.portal_workflow._jumpToStateFor(self.subscription, 'validated')
self.portal.portal_workflow._jumpToStateFor(self.instance_tree, 'validated')
open_sale_order_template = self.portal.restrictedTraverse( open_sale_order_template = self.portal.restrictedTraverse(
self.portal.portal_preferences.getPreferredOpenSaleOrderTemplate()) self.portal.portal_preferences.getPreferredOpenSaleOrderTemplate())
......
...@@ -34,12 +34,20 @@ import time ...@@ -34,12 +34,20 @@ import time
class TestSlapOSAccounting(SlapOSTestCaseMixin): class TestSlapOSAccounting(SlapOSTestCaseMixin):
def createHostingSubscription(self):
new_id = self.generateNewId()
return self.portal.hosting_subscription_module.newContent(
portal_type='Hosting Subscription',
title="Subscription %s" % new_id,
reference="TESTHS-%s" % new_id,
)
def createInstanceTree(self): def createInstanceTree(self):
new_id = self.generateNewId() new_id = self.generateNewId()
return self.portal.instance_tree_module.newContent( return self.portal.instance_tree_module.newContent(
portal_type='Instance Tree', portal_type='Instance Tree',
title="Subscription %s" % new_id, title="Subscription %s" % new_id,
reference="TESTHS-%s" % new_id, reference="TESTIT-%s" % new_id,
) )
def createOpenSaleOrder(self): def createOpenSaleOrder(self):
...@@ -82,46 +90,44 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin): ...@@ -82,46 +90,44 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin):
return invoice return invoice
@withAbort @withAbort
def test_IT_calculateSubscriptionStartDate_REQUEST_disallowed(self): def test_HS_calculateSubscriptionStartDate_REQUEST_disallowed(self):
item = self.createInstanceTree() item = self.createHostingSubscription()
self.assertRaises( self.assertRaises(
Unauthorized, Unauthorized,
item.InstanceTree_calculateSubscriptionStartDate, item.HostingSubscription_calculateSubscriptionStartDate,
REQUEST={}) REQUEST={})
@withAbort @withAbort
def test_IT_calculateSubscriptionStartDate_noWorkflow(self): def test_HS_calculateSubscriptionStartDate_noWorkflow(self):
item = self.createInstanceTree() item = self.createHostingSubscription()
item.workflow_history['instance_slap_interface_workflow'] = [] item.workflow_history['instance_slap_interface_workflow'] = []
date = item.InstanceTree_calculateSubscriptionStartDate() date = item.HostingSubscription_calculateSubscriptionStartDate()
self.assertEqual(date, item.getCreationDate().earliestTime()) self.assertEqual(date, item.getCreationDate().earliestTime())
@withAbort @withAbort
def test_IT_calculateSubscriptionStartDate_withRequest(self): def test_HS_calculateSubscriptionStartDate_withRequest(self):
item = self.createInstanceTree() item = self.createHostingSubscription()
item.workflow_history['instance_slap_interface_workflow'] = [{ item.workflow_history['edit_workflow'] = [{
'comment':'Directly request the instance', 'comment':'Directly request the instance',
'error_message': '', 'error_message': '',
'actor': 'ERP5TypeTestCase', 'actor': 'ERP5TypeTestCase',
'slap_state': 'draft',
'time': DateTime('2012/11/15 11:11'), 'time': DateTime('2012/11/15 11:11'),
'action': 'request_instance' 'action': 'edit'
}] }]
date = item.InstanceTree_calculateSubscriptionStartDate() date = item.HostingSubscription_calculateSubscriptionStartDate()
self.assertEqual(date, DateTime('2012/11/15')) self.assertEqual(date, DateTime('2012/11/15'))
@withAbort @withAbort
def test_IT_calculateSubscriptionStartDate_withRequestEndOfMonth(self): def test_HS_calculateSubscriptionStartDate_withRequestEndOfMonth(self):
item = self.createInstanceTree() item = self.createHostingSubscription()
item.workflow_history['instance_slap_interface_workflow'] = [{ item.workflow_history['edit_workflow'] = [{
'comment':'Directly request the instance', 'comment':'Directly request the instance',
'error_message': '', 'error_message': '',
'actor': 'ERP5TypeTestCase', 'actor': 'ERP5TypeTestCase',
'slap_state': 'draft',
'time': DateTime('2012/11/30 11:11'), 'time': DateTime('2012/11/30 11:11'),
'action': 'request_instance' 'action': 'edit'
}] }]
date = item.InstanceTree_calculateSubscriptionStartDate() date = item.HostingSubscription_calculateSubscriptionStartDate()
self.assertEqual(date, DateTime('2012/11/30')) self.assertEqual(date, DateTime('2012/11/30'))
@withAbort @withAbort
...@@ -150,7 +156,7 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin): ...@@ -150,7 +156,7 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin):
self.assertEqual(date, DateTime('2012/10/30')) self.assertEqual(date, DateTime('2012/10/30'))
@withAbort @withAbort
def test_IT_calculateSubscriptionStopDate_REQUEST_disallowed(self): def test_HS_calculateSubscriptionStopDate_REQUEST_disallowed(self):
item = self.createInstanceTree() item = self.createInstanceTree()
self.assertRaises( self.assertRaises(
Unauthorized, Unauthorized,
...@@ -843,5 +849,4 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin): ...@@ -843,5 +849,4 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin):
payment_transaction.PaymentTransaction_start() payment_transaction.PaymentTransaction_start()
self.assertEqual("started", self.assertEqual("started",
payment_transaction.getSimulationState()) payment_transaction.getSimulationState())
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>interaction_InstanceTree_afterAddClone</string> </value> <value> <string>interaction_HostingSubscription_afterAddClone</string> </value>
</item> </item>
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
<key> <string>portal_type_filter</string> </key> <key> <string>portal_type_filter</string> </key>
<value> <value>
<tuple> <tuple>
<string>Instance Tree</string> <string>Hosting Subscription</string>
</tuple> </tuple>
</value> </value>
</item> </item>
...@@ -46,6 +46,10 @@ ...@@ -46,6 +46,10 @@
<key> <string>temporary_document_disallowed</string> </key> <key> <string>temporary_document_disallowed</string> </key>
<value> <int>1</int> </value> <value> <int>1</int> </value>
</item> </item>
<item>
<key> <string>title</string> </key>
<value> <string>HostingSubscription_afterAddClone</string> </value>
</item>
<item> <item>
<key> <string>trigger_method_id</string> </key> <key> <string>trigger_method_id</string> </key>
<value> <value>
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<key> <string>categories</string> </key> <key> <string>categories</string> </key>
<value> <value>
<tuple> <tuple>
<string>before_script/portal_workflow/slapos_accounting_interaction_workflow/script_InstanceTree_fixPeriodicity</string> <string>before_script/portal_workflow/slapos_accounting_interaction_workflow/script_HostingSubscription_fixPeriodicity</string>
</tuple> </tuple>
</value> </value>
</item> </item>
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>interaction_InstanceTree_fixConsistency</string> </value> <value> <string>interaction_HostingSubscription_fixConsistency</string> </value>
</item> </item>
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
<key> <string>portal_type_filter</string> </key> <key> <string>portal_type_filter</string> </key>
<value> <value>
<tuple> <tuple>
<string>Instance Tree</string> <string>Hosting Subscription</string>
</tuple> </tuple>
</value> </value>
</item> </item>
...@@ -46,6 +46,10 @@ ...@@ -46,6 +46,10 @@
<key> <string>temporary_document_disallowed</string> </key> <key> <string>temporary_document_disallowed</string> </key>
<value> <int>1</int> </value> <value> <int>1</int> </value>
</item> </item>
<item>
<key> <string>title</string> </key>
<value> <string>HostingSubscription_fixConsistency</string> </value>
</item>
<item> <item>
<key> <string>trigger_method_id</string> </key> <key> <string>trigger_method_id</string> </key>
<value> <value>
......
from erp5.component.module.DateUtils import addToDate, getClosestDate from erp5.component.module.DateUtils import addToDate, getClosestDate
instance_tree = state_change['object'] hosting_subscription = state_change['object']
edit_kw = {} edit_kw = {}
if instance_tree.getPeriodicityHour() is None: if hosting_subscription.getPeriodicityHour() is None:
edit_kw['periodicity_hour_list'] = [0] edit_kw['periodicity_hour_list'] = [0]
if instance_tree.getPeriodicityMinute() is None: if hosting_subscription.getPeriodicityMinute() is None:
edit_kw['periodicity_minute_list'] = [0] edit_kw['periodicity_minute_list'] = [0]
if instance_tree.getPeriodicityMonthDay() is None: if hosting_subscription.getPeriodicityMonthDay() is None:
start_date = instance_tree.InstanceTree_calculateSubscriptionStartDate() start_date = hosting_subscription.HostingSubscription_calculateSubscriptionStartDate()
start_date = getClosestDate(target_date=start_date, precision='day') start_date = getClosestDate(target_date=start_date, precision='day')
while start_date.day() >= 29: while start_date.day() >= 29:
start_date = addToDate(start_date, to_add={'day': -1}) start_date = addToDate(start_date, to_add={'day': -1})
edit_kw['periodicity_month_day_list'] = [start_date.day()] edit_kw['periodicity_month_day_list'] = [start_date.day()]
if edit_kw: if edit_kw:
instance_tree.edit(**edit_kw) hosting_subscription.edit(**edit_kw)
...@@ -60,9 +60,15 @@ ...@@ -60,9 +60,15 @@
</tuple> </tuple>
</value> </value>
</item> </item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>script_InstanceTree_fixPeriodicity</string> </value> <value> <string>script_HostingSubscription_fixPeriodicity</string> </value>
</item> </item>
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
......
...@@ -5,8 +5,8 @@ Compute Node | jump_to_consumption_report_view ...@@ -5,8 +5,8 @@ Compute Node | jump_to_consumption_report_view
Computer Consumption TioXML File | download Computer Consumption TioXML File | download
Computer Consumption TioXML File | view Computer Consumption TioXML File | view
Consumption Document Module | view Consumption Document Module | view
Hosting Subscription | periodicity
Instance Tree | jump_to_related_open_order_line Instance Tree | jump_to_related_open_order_line
Instance Tree | periodicity
Payment Transaction | related_payzen_event Payment Transaction | related_payzen_event
Person | create_new_cloud_contract Person | create_new_cloud_contract
Person | jump_to_cloud_contract Person | jump_to_cloud_contract
......
Cloud Contract Line | SlapOSCloudContractLineAccounting Cloud Contract Line | SlapOSCloudContractLineAccounting
Cloud Contract | SlapOSCloudContractAccounting Cloud Contract | SlapOSCloudContractAccounting
Computer Consumption TioXML File | SortIndex Computer Consumption TioXML File | SortIndex
Instance Tree | SlapOSAccountingInstanceTreeConstraint Hosting Subscription | SlapOSAccountingHostingSubscriptionConstraint
Open Sale Order Line | SlapOSAccountingOpenSaleOrderLineConstraint
Open Sale Order | SlapOSAccountingOpenSaleOrderConstraint
Sale Invoice Transaction | SlapOSAccountingSaleInvoiceTransactionConstraint Sale Invoice Transaction | SlapOSAccountingSaleInvoiceTransactionConstraint
Sale Packing List Line | SlapOSAccountingSalePackingListLineConstraint Sale Packing List Line | SlapOSAccountingSalePackingListLineConstraint
Sale Packing List | SlapOSAccountingSalePackingListConstraint Sale Packing List | SlapOSAccountingSalePackingListConstraint
......
...@@ -4,6 +4,7 @@ Cloud Contract | item_workflow ...@@ -4,6 +4,7 @@ Cloud Contract | item_workflow
Computer Consumption TioXML File | document_conversion_interaction_workflow Computer Consumption TioXML File | document_conversion_interaction_workflow
Computer Consumption TioXML File | document_publication_workflow Computer Consumption TioXML File | document_publication_workflow
Computer Consumption TioXML File | edit_workflow Computer Consumption TioXML File | edit_workflow
Hosting Subscription | slapos_accounting_interaction_workflow
Instance Tree | slapos_accounting_interaction_workflow Instance Tree | slapos_accounting_interaction_workflow
Instance Tree | slapos_api_invoicing_workflow Instance Tree | slapos_api_invoicing_workflow
Sale Invoice Transaction | slapos_accounting_interaction_workflow Sale Invoice Transaction | slapos_accounting_interaction_workflow
......
InstanceAccountingSynchronisation InstanceAccountingSynchronisation
SlapOSAccountingInstanceTreeConstraint SlapOSAccountingOpenSaleOrderLineConstraint
SlapOSAccountingOpenSaleOrderConstraint
SlapOSAccountingHostingSubscriptionConstraint
SlapOSAccountingSaleInvoiceTransactionConstraint SlapOSAccountingSaleInvoiceTransactionConstraint
SlapOSAccountingSalePackingListConstraint SlapOSAccountingSalePackingListConstraint
SlapOSAccountingSalePackingListLineConstraint SlapOSAccountingSalePackingListLineConstraint
......
...@@ -175,9 +175,7 @@ def HostingSubscription_checkInstanceTreeMigrationConsistency(self, fixit=False) ...@@ -175,9 +175,7 @@ def HostingSubscription_checkInstanceTreeMigrationConsistency(self, fixit=False)
mod = __import__('erp5.portal_type', globals(), locals(), ['Instance Tree']) mod = __import__('erp5.portal_type', globals(), locals(), ['Instance Tree'])
klass = getattr(mod, 'Instance Tree') klass = getattr(mod, 'Instance Tree')
if ((getattr(self, 'workflow_history', None) is not None) and if (self.__class__ == klass) or \
('hosting_subscription_workflow' in self.workflow_history)) or \
(self.__class__ == klass) or \
(self.getProperty('sla_xml', None) is not None) or \ (self.getProperty('sla_xml', None) is not None) or \
([x for x in self.getCategoryList() if (x.startswith('predecessor/') or ([x for x in self.getCategoryList() if (x.startswith('predecessor/') or
x.startswith('successor/'))]): x.startswith('successor/'))]):
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Hosting Subscription" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Delete_objects_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>template_hosting_subscription</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>periodicity_hour</string> </key>
<value>
<tuple>
<int>0</int>
</tuple>
</value>
</item>
<item>
<key> <string>periodicity_minute</string> </key>
<value>
<tuple>
<int>0</int>
</tuple>
</value>
</item>
<item>
<key> <string>periodicity_month_day</string> </key>
<value>
<tuple>
<int>17</int>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Hosting Subscription</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
</item> </item>
<item> <item>
<key> <string>type_class</string> </key> <key> <string>type_class</string> </key>
<value> <string>SubscriptionItem</string> </value> <value> <string>Item</string> </value>
</item> </item>
<item> <item>
<key> <string>type_interface</string> </key> <key> <string>type_interface</string> </key>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
</chain> </chain>
<chain> <chain>
<type>Hosting Subscription</type> <type>Hosting Subscription</type>
<workflow>edit_workflow</workflow> <workflow>edit_workflow, hosting_subscription_workflow</workflow>
</chain> </chain>
<chain> <chain>
<type>Instance Tree</type> <type>Instance Tree</type>
......
...@@ -98,13 +98,16 @@ ...@@ -98,13 +98,16 @@
<value> <value>
<list> <list>
<string>my_title</string> <string>my_title</string>
<string>my_reference</string>
</list> </list>
</value> </value>
</item> </item>
<item> <item>
<key> <string>right</string> </key> <key> <string>right</string> </key>
<value> <value>
<list/> <list>
<string>my_translated_validation_state_title</string>
</list>
</value> </value>
</item> </item>
</dictionary> </dictionary>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>display_width</string>
<string>editable</string>
<string>enabled</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_reference</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_reference</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Reference</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_translated_validation_state_title</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_translated_workflow_state_title</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -601,35 +601,36 @@ class DefaultScenarioMixin(TestSlapOSSecurityMixin): ...@@ -601,35 +601,36 @@ class DefaultScenarioMixin(TestSlapOSSecurityMixin):
self.assertEqual(0, len(open_sale_order_list)) self.assertEqual(0, len(open_sale_order_list))
return return
self.assertEqual(2, len(open_sale_order_list)) self.assertEqual(len(instance_tree_list), len(open_sale_order_list))
archived_open_sale_order_list = [q for q in open_sale_order_list archived_open_sale_order_list = [q for q in open_sale_order_list
if q.getValidationState() == 'archived'] if q.getValidationState() == 'archived']
self.assertEqual(len(instance_tree_list), len(archived_open_sale_order_list))
archived_open_sale_order_list.sort(key=lambda x: x.getCreationDate()) line_list = []
for open_sale_order in archived_open_sale_order_list:
# Select the first archived archived_line_list = open_sale_order.contentValues(
open_sale_order = archived_open_sale_order_list[0] portal_type='Open Sale Order Line')
self.assertEqual(1, len(archived_line_list))
line_list.extend(archived_line_list)
line_list = open_sale_order.contentValues(
portal_type='Open Sale Order Line')
self.assertEqual(len(instance_tree_list), len(line_list)) self.assertEqual(len(instance_tree_list), len(line_list))
self.assertSameSet( self.assertSameSet(
[q.getRelativeUrl() for q in instance_tree_list], [q.getRelativeUrl() for q in instance_tree_list],
[q.getAggregate() for q in line_list] [q.getAggregate(portal_type="Instance Tree") for q in line_list]
) )
# Every line must have 2 aggregate categories:
# one Instance Tree and one Hosting Subscription
for line in line_list:
self.assertEqual(2, len(line.getAggregateList()))
self.assertEqual(1, len(line.getAggregateList(portal_type="Hosting Subscription")))
validated_open_sale_order_list = [q for q in open_sale_order_list validated_open_sale_order_list = [q for q in open_sale_order_list
if q.getValidationState() == 'validated'] if q.getValidationState() == 'validated']
# if no line, all open orders are kept archived # if no line, all open orders are kept archived
self.assertEqual(len(validated_open_sale_order_list), 0) self.assertEqual(len(validated_open_sale_order_list), 0)
latest_open_sale_order = archived_open_sale_order_list[-1]
line_list = latest_open_sale_order.contentValues(
portal_type='Open Sale Order Line')
self.assertEqual(len(line_list), 0)
def findMessage(self, email, body): def findMessage(self, email, body):
for candidate in reversed(self.portal.MailHost.getMessageList()): for candidate in reversed(self.portal.MailHost.getMessageList()):
if [q for q in candidate[1] if email in q] and body in candidate[2]: if [q for q in candidate[1] if email in q] and body in candidate[2]:
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>source/portal_workflow/hosting_subscription_workflow/state_draft</string>
</tuple>
</value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>instance_tree_workflow</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>hosting_subscription_workflow</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>manager_bypass</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow</string> </value>
</item>
<item>
<key> <string>state_variable</string> </key>
<value> <string>validation_state</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Hosting Subscription Workflow</string> </value>
</item>
<item>
<key> <string>workflow_managed_permission</string> </key>
<value>
<tuple>
<string>Access contents information</string>
<string>View</string>
<string>Add portal content</string>
<string>Modify portal content</string>
<string>Delete objects</string>
</tuple>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow State" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>acquire_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>state_archived</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow State</string> </value>
</item>
<item>
<key> <string>state_permission_role_list_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>state_type</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Archived</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>Access contents information</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Add portal content</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Delete objects</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Modify portal content</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>View</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
</tuple>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow State" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>acquire_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>destination/portal_workflow/hosting_subscription_workflow/transition_validate</string>
<string>destination/portal_workflow/hosting_subscription_workflow/transition_validate_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>state_draft</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow State</string> </value>
</item>
<item>
<key> <string>state_permission_role_list_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>state_type</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Draft</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>Access contents information</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>Add portal content</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>Delete objects</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>Modify portal content</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>View</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow State" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>acquire_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>destination/portal_workflow/hosting_subscription_workflow/transition_archive</string>
<string>destination/portal_workflow/hosting_subscription_workflow/transition_archive_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>state_validated</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow State</string> </value>
</item>
<item>
<key> <string>state_permission_role_list_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>state_type</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Validated</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>Access contents information</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Add portal content</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Delete objects</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Modify portal content</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>View</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
</tuple>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Transition" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>action_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/workflow</string>
<string>destination/portal_workflow/hosting_subscription_workflow/state_archived</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>transition_archive</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Transition</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Archive</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Transition" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string encoding="cdata"><![CDATA[
%(content_url)s/BaseWorkflow_viewWorkflowActionDialog?workflow_action=archive_action&cancel_url=%(content_url)s
]]></string> </value>
</item>
<item>
<key> <string>action_name</string> </key>
<value> <string>Archive</string> </value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/workflow</string>
<string>after_script/portal_workflow/hosting_subscription_workflow/transition_archive</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>guard_group</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>guard_role</string> </key>
<value>
<tuple>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>transition_archive_action</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Transition</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>ArchiveAction</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Transition" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>action_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/workflow</string>
<string>destination/portal_workflow/hosting_subscription_workflow/state_validated</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>transition_validate</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Transition</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Validate</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Transition" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string encoding="cdata"><![CDATA[
%(content_url)s/BaseWorkflow_viewWorkflowActionDialog?workflow_action=validate_action&cancel_url=%(content_url)s
]]></string> </value>
</item>
<item>
<key> <string>action_name</string> </key>
<value> <string>Validate</string> </value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/workflow</string>
<string>after_script/portal_workflow/hosting_subscription_workflow/transition_validate</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>guard_group</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>guard_role</string> </key>
<value>
<tuple>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>transition_validate_action</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Transition</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>ValidateAction</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>The last transition</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_action</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>variable_default_expression</string> </key>
<value> <string>transition/getReference|nothing</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>The name of the user who performed the last transition</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_actor</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>variable_default_expression</string> </key>
<value> <string>user/getUserName</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Comments about the last transition</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_comment</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>variable_default_expression</string> </key>
<value> <string>python:state_change.kwargs.get(\'comment\', \'\')</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Error message if validation failed</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_error_message</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Provides access to workflow history</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_history</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>variable_default_expression</string> </key>
<value> <string>state_change/getHistory</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>portal type (use as filter for worklists)</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_portal_type</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Time of the last transition</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_time</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>variable_default_expression</string> </key>
<value> <string>state_change/getDateTime</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -5,6 +5,7 @@ compute_node_module/template_compute_node ...@@ -5,6 +5,7 @@ compute_node_module/template_compute_node
compute_node_module/template_compute_node/** compute_node_module/template_compute_node/**
computer_model_module/template_computer_model computer_model_module/template_computer_model
computer_model_module/template_computer_model/** computer_model_module/template_computer_model/**
hosting_subscription_module/template_hosting_subscription
instance_tree_module/template_instance_tree instance_tree_module/template_instance_tree
person_module/template_member person_module/template_member
person_module/template_member/** person_module/template_member/**
...@@ -25,4 +26,4 @@ portal_caches/last_stored_data_cache_factory/volatile_cache_plugin ...@@ -25,4 +26,4 @@ portal_caches/last_stored_data_cache_factory/volatile_cache_plugin
product_module/compute_node product_module/compute_node
software_installation_module/template_software_installation software_installation_module/template_software_installation
software_instance_module/template_slave_instance software_instance_module/template_slave_instance
software_instance_module/template_software_instance software_instance_module/template_software_instance
\ No newline at end of file
...@@ -3,6 +3,7 @@ Compute Node | slapos_cloud_interaction_workflow ...@@ -3,6 +3,7 @@ Compute Node | slapos_cloud_interaction_workflow
Compute Partition | compute_partition_slap_interface_workflow Compute Partition | compute_partition_slap_interface_workflow
Computer Network | network_slap_interface_workflow Computer Network | network_slap_interface_workflow
Hosting Subscription | edit_workflow Hosting Subscription | edit_workflow
Hosting Subscription | hosting_subscription_workflow
Instance Tree | edit_workflow Instance Tree | edit_workflow
Instance Tree | instance_slap_interface_workflow Instance Tree | instance_slap_interface_workflow
Instance Tree | instance_tree_workflow Instance Tree | instance_tree_workflow
......
audit_validation_workflow audit_validation_workflow
compute_node_slap_interface_workflow compute_node_slap_interface_workflow
compute_partition_slap_interface_workflow compute_partition_slap_interface_workflow
hosting_subscription_workflow
installation_slap_interface_workflow installation_slap_interface_workflow
instance_slap_interface_workflow instance_slap_interface_workflow
instance_tree_workflow instance_tree_workflow
......
...@@ -294,11 +294,18 @@ class TestSlapOSDefaultCRMEscalation(DefaultScenarioMixin): ...@@ -294,11 +294,18 @@ class TestSlapOSDefaultCRMEscalation(DefaultScenarioMixin):
line_list = open_sale_order.contentValues( line_list = open_sale_order.contentValues(
portal_type='Open Sale Order Line') portal_type='Open Sale Order Line')
self.assertEqual(len(instance_tree_list), len(line_list)) self.assertEqual(len(instance_tree_list), len(line_list))
self.assertSameSet( self.assertSameSet(
[q.getRelativeUrl() for q in instance_tree_list], [q.getRelativeUrl() for q in instance_tree_list],
[q.getAggregate() for q in line_list] [q.getAggregate(portal_type="Instance Tree") for q in line_list]
) )
# Every line must have 2 aggregate categories:
# one Instance Tree and one Hosting Subscription
for line in line_list:
self.assertEqual(2, len(line.getAggregateList()))
self.assertEqual(1, len(line.getAggregateList(portal_type="Hosting Subscription")))
def assertAggregatedSalePackingList(self, delivery): def assertAggregatedSalePackingList(self, delivery):
self.assertEqual('delivered', delivery.getSimulationState()) self.assertEqual('delivered', delivery.getSimulationState())
self.assertEqual('solved', delivery.getCausalityState()) self.assertEqual('solved', delivery.getCausalityState())
......
...@@ -1629,8 +1629,12 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans ...@@ -1629,8 +1629,12 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
software_type="default", software_type="default",
partition_reference="_test_subscription_scenario_with_existing_user_extra_instance", partition_reference="_test_subscription_scenario_with_existing_user_extra_instance",
) )
self.non_subscription_related_instance_amount = 1 self.non_subscription_related_instance_amount = 1
# Trigger open order creation
self.portal.portal_alarms.slapos_request_update_instance_tree_open_sale_order.activeSense()
self.tic()
self.login() self.login()
self.requestAndCheckInstanceTree( self.requestAndCheckInstanceTree(
amount, name, default_email_text) amount, name, default_email_text)
...@@ -1802,7 +1806,9 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans ...@@ -1802,7 +1806,9 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
# Ensure periodicity is correct # Ensure periodicity is correct
for subscription_request in subscription_request_list: for subscription_request in subscription_request_list:
instance_tree = subscription_request.getAggregateValue() instance_tree = subscription_request.getAggregateValue()
self.assertEqual(instance_tree.getPeriodicityMonthDay(), open_order_line = instance_tree.getAggregateRelatedValue(portal_type="Open Sale Order Line")
hosting_subscription = open_order_line.getAggregateValue(portal_type="Hosting Subscription")
self.assertEqual(hosting_subscription.getPeriodicityMonthDay(),
min(DateTime().day(), 28)) min(DateTime().day(), 28))
self.pinDateTime(DateTime(DateTime().asdatetime() + datetime.timedelta(days=17))) self.pinDateTime(DateTime(DateTime().asdatetime() + datetime.timedelta(days=17)))
......
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