Commit 31f973e6 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 30b17a26
......@@ -8,8 +8,14 @@
<portal_type id="Computer Consumption TioXML File">
<item>SortIndex</item>
</portal_type>
<portal_type id="Instance Tree">
<item>SlapOSAccountingInstanceTreeConstraint</item>
<portal_type id="Hosting Subscription">
<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 id="Sale Invoice Transaction">
<item>SlapOSAccountingSaleInvoiceTransactionConstraint</item>
......
......@@ -11,6 +11,10 @@
<type>Computer Consumption TioXML File</type>
<workflow>document_conversion_interaction_workflow, document_publication_workflow, edit_workflow</workflow>
</chain>
<chain>
<type>Hosting Subscription</type>
<workflow>slapos_accounting_interaction_workflow</workflow>
</chain>
<chain>
<type>Instance Tree</type>
<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 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SlapOSAccountingInstanceTreeConstraint</string> </value>
<value> <string>SlapOSAccountingOpenSaleOrderConstraint</string> </value>
</item>
<item>
<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 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>InstanceTree_calculateSubscriptionStartDate</string> </value>
<value> <string>HostingSubscription_calculateSubscriptionStartDate</string> </value>
</item>
</dictionary>
</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
if REQUEST is not None:
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
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
assert context.getCausalityState() == 'solved'
assert instance_tree.getCausalityState() == 'solved'
......@@ -2,7 +2,87 @@ from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
if context.getValidationState() == 'validated':
person = context.getDestinationDecisionValue(portal_type="Person")
from erp5.component.module.DateUtils import addToDate
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:
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(
default_aggregate_portal_type=ComplexQuery(NegatedQuery(Query(default_aggregate_portal_type='Compute Node')),
Query(default_aggregate_portal_type=None),logical_operator="OR"),
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
)
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 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>InstanceTree_getRuleReference</string> </value>
<value> <string>HostingSubscription_getRuleReference</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -6,6 +6,8 @@ root_applied_rule = context.getRootAppliedRule()
root_applied_rule_path = root_applied_rule.getPath()
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())
if context.getPortalObject().portal_activities.countMessageWithTag(lock_tag) == 0:
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
import os
import tempfile
from DateTime import DateTime
from erp5.component.module.DateUtils import addToDate, getClosestDate
from erp5.component.module.DateUtils import addToDate#, getClosestDate
from zExceptions import Unauthorized
class Simulator:
......@@ -128,22 +128,6 @@ class TestOpenSaleOrderAlarm(SlapOSTestCaseMixin):
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')
def test_alarm_HS_diverged(self):
subscription = self.portal.instance_tree_module\
......@@ -205,7 +189,12 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
self.assertEqual(1, len(open_sale_order_line_list))
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(
self.portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate())
self.assertEqual(open_sale_order_line_template.getResource(),
......@@ -218,7 +207,7 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
line.getPrice())
self.assertEqual(DateTime().earliestTime(), line.getStartDate())
self.assertEqual(min(DateTime().day(), 28),
subscription.getPeriodicityMonthDay())
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:
......@@ -245,9 +234,6 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
'time': request_time,
'action': 'request_instance'
}]
subscription.edit(periodicity_month_day_list=[])
subscription.fixConsistency()
self.assertEqual(subscription.getPeriodicityMonthDay(), 1)
self.tic()
subscription.InstanceTree_requestUpdateOpenSaleOrder()
......@@ -269,15 +255,13 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
self.assertEqual(1, len(open_sale_order_line_list))
line = open_sale_order_line_list[0].getObject()
# calculate stop date to be after now, begin with start date with precision
# of month
now = DateTime()
now = now.toZone(request_time.timezone())
stop_date = getClosestDate(target_date=now, precision='month')
stop_date = addToDate(stop_date, to_add={'second': -1})
self.assertEqual(stop_date, line.getStopDate())
self.assertEqual(subscription.getRelativeUrl(), line.getAggregate())
hosting_subscription = line.getAggregateValueList()[0]
# self.assertEqual(hosting_subscription.getPeriodicityMonthDay(), 1)
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(
self.portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate())
self.assertTrue(all([q in line.getCategoryList() \
......@@ -288,8 +272,14 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
line.getQuantity())
self.assertEqual(open_sale_order_line_template.getPrice(),
line.getPrice())
self.assertEqual(request_time, line.getStartDate())
self.assertEqual(stop_date, line.getStopDate())
self.assertEqual(DateTime().earliestTime(), line.getStartDate())
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')
subscription.workflow_history['instance_slap_interface_workflow'].append({
......@@ -312,28 +302,22 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
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
if q.getValidationState() == 'validated']
archived_open_sale_order_list = [q for q in open_sale_order_list
if q.getValidationState() == 'archived']
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]\
.getObject()
self.assertEqual(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(
portal_type='Open Sale Order Line')
self.assertEqual(0, len(last_line_list))
self.assertEqual(1, len(archived_line_list))
archived_line = archived_line_list[0].getObject()
......@@ -341,7 +325,7 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
self.assertEqual(line.getRelativeUrl(), archived_line.getRelativeUrl())
self.assertEqual(subscription.getRelativeUrl(),
archived_line.getAggregate())
archived_line.getAggregateList()[1])
self.assertTrue(all([q in archived_line.getCategoryList() \
for q in open_sale_order_line_template.getCategoryList()]))
self.assertEqual(open_sale_order_line_template.getResource(),
......@@ -350,7 +334,7 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
line.getQuantity())
self.assertEqual(open_sale_order_line_template.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())
def test_lateAnalysed_InstanceTree(self):
......@@ -397,52 +381,7 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
default_destination_uid=person.getUid()
)
self.assertEqual(2, 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))
self.assertEqual(0, len(open_sale_order_list))
def test_two_InstanceTree(self):
person = self.portal.person_module.template_member\
......@@ -464,8 +403,6 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
'time': request_time,
'action': 'request_instance'
}]
subscription.edit(periodicity_month_day_list=[])
subscription.fixConsistency()
self.tic()
subscription.InstanceTree_requestUpdateOpenSaleOrder()
......@@ -486,7 +423,11 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
self.assertEqual(1, len(open_sale_order_line_list))
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(
self.portal.portal_preferences.getPreferredOpenSaleOrderLineTemplate())
self.assertTrue(all([q in line.getCategoryList() \
......@@ -497,18 +438,6 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
line.getQuantity())
self.assertEqual(open_sale_order_line_template.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\
.template_instance_tree.Base_createCloneDocument(batch_mode=1)
......@@ -538,36 +467,35 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
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
if q.getValidationState() == 'validated']
archived_open_sale_order_list = [q for q in open_sale_order_list
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))
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')
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(),
line.getQuantity())
self.assertEqual(open_sale_order_line_template.getPrice(),
line.getPrice())
stop_date_2 = request_time_2
next_stop_date_2 = stop_date_2
now = DateTime()
while next_stop_date_2 < now:
stop_date_2 = next_stop_date_2
next_stop_date_2 = addToDate(stop_date_2, to_add={'month': 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]
hosting_subscription_2 = validated_line_2.getAggregateValueList()[0]
self.assertEqual("Hosting Subscription",
hosting_subscription_2.getPortalType())
self.assertEqual("validated",
hosting_subscription_2.getValidationState())
self.assertEqual(subscription2.getRelativeUrl(), validated_line_2.getAggregateList()[1])
self.assertTrue(all([q in validated_line_1.getCategoryList() \
for q in open_sale_order_line_template.getCategoryList()]))
......@@ -577,8 +505,8 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
line.getQuantity())
self.assertEqual(open_sale_order_line_template.getPrice(),
line.getPrice())
self.assertEqual(request_time, validated_line_1.getStartDate())
self.assertEqual(stop_date, validated_line_1.getStopDate())
#self.assertEqual(request_time, validated_line_1.getStartDate())
#self.assertEqual(stop_date, validated_line_1.getStopDate())
self.assertTrue(all([q in validated_line_2.getCategoryList() \
for q in open_sale_order_line_template.getCategoryList()]))
......@@ -588,8 +516,8 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
line.getQuantity())
self.assertEqual(open_sale_order_line_template.getPrice(),
line.getPrice())
self.assertEqual(request_time_2, validated_line_2.getStartDate())
self.assertEqual(stop_date_2, validated_line_2.getStopDate())
#self.assertEqual(request_time_2, validated_line_2.getStartDate())
#self.assertEqual(stop_date_2, validated_line_2.getStopDate())
def test_instance_tree_start_date_not_changed(self):
# if there was no request_instance the getCreationDate has been used
......@@ -688,50 +616,8 @@ class TestInstanceTree_requestUpdateOpenSaleOrder(SlapOSTestCaseMixin):
default_destination_uid=person.getUid()
)
self.assertEqual(2,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())
self.assertEqual(0,len(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))
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):
@simulateByTitlewMark('SimulationMovement_buildSlapOS')
......@@ -1284,11 +1170,10 @@ class TestSlapOSUpdateOpenSaleOrderPeriod(SlapOSTestCaseMixin):
open_order.edit(
destination_decision_value=person,
)
open_order.OpenSaleOrder_updatePeriod()
self.assertEqual(
'Visited by Person_storeOpenSaleOrderJournal',
person.workflow_history['edit_workflow'][-1]['comment'])
open_order.newContent(
portal_type="Open Sale Order Line"
)
self.assertRaises(AssertionError, open_order.OpenSaleOrder_updatePeriod)
@simulateByEditWorkflowMark('Person_storeOpenSaleOrderJournal')
def test_updatePeriod_invalidated(self):
......@@ -1298,9 +1183,11 @@ class TestSlapOSUpdateOpenSaleOrderPeriod(SlapOSTestCaseMixin):
open_order.edit(
destination_decision_value=person,
)
open_order.newContent(
portal_type="Open Sale Order Line"
)
open_order.invalidate()
open_order.OpenSaleOrder_updatePeriod()
self.assertNotEqual(
'Visited by Person_storeOpenSaleOrderJournal',
person.workflow_history['edit_workflow'][-1]['comment'])
......@@ -1502,4 +1389,4 @@ class TestSlapOSCancelSaleTnvoiceTransactionPaiedPaymentListAlarm(SlapOSTestCase
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionisLettered')
def test_payment_is_started_wechat(self):
self._test_payment_is_started(payment_mode="wechat")
\ No newline at end of file
self._test_payment_is_started(payment_mode="wechat")
......@@ -11,13 +11,13 @@ from unittest import skip
import transaction
class TestInstanceTree(TestSlapOSConstraintMixin):
class TestHostingSubscription(TestSlapOSConstraintMixin):
# use decrator in order to avoid fixing consistency of new object
@WorkflowMethod.disable
def _createInstanceTree(self):
self.subscription = self.portal.instance_tree_module.newContent(
portal_type='Instance Tree')
self.subscription = self.portal.hosting_subscription_module.newContent(
portal_type='Hosting Subscription')
def afterSetUp(self):
TestSlapOSConstraintMixin.afterSetUp(self)
......
......@@ -9,8 +9,8 @@ class TestSlapOSAccountingInteractionWorkflow(SlapOSTestCaseMixin):
def beforeTearDown(self):
transaction.abort()
def _simulateInstanceTree_calculateSubscriptionStartDate(self, date):
script_name = 'InstanceTree_calculateSubscriptionStartDate'
def _simulateHostingSubscription_calculateSubscriptionStartDate(self, date):
script_name = 'HostingSubscription_calculateSubscriptionStartDate'
if script_name in self.portal.portal_skins.custom.objectIds():
raise ValueError('Precondition failed: %s exists in custom' % script_name)
createZODBPythonScript(self.portal.portal_skins.custom,
......@@ -21,17 +21,17 @@ class TestSlapOSAccountingInteractionWorkflow(SlapOSTestCaseMixin):
return DateTime('%s') """ % date.ISO())
transaction.commit()
def _dropInstanceTree_calculateSubscriptionStartDate(self):
script_name = 'InstanceTree_calculateSubscriptionStartDate'
def _dropHostingSubscription_calculateSubscriptionStartDate(self):
script_name = 'HostingSubscription_calculateSubscriptionStartDate'
if script_name in self.portal.portal_skins.custom.objectIds():
self.portal.portal_skins.custom.manage_delObjects(script_name)
transaction.commit()
def test_InstanceTree_fixConsistency(self,
def test_HostingSubscription_fixConsistency(self,
date=DateTime('2012/01/15'), day=15):
new_id = self.generateNewId()
item = self.portal.instance_tree_module.newContent(
portal_type='Instance Tree',
item = self.portal.hosting_subscription_module.newContent(
portal_type='Hosting Subscription',
title="Subscription %s" % new_id,
reference="TESTSUB-%s" % new_id,
periodicity_hour_list=None,
......@@ -43,48 +43,48 @@ return DateTime('%s') """ % date.ISO())
self.assertEqual(item.getPeriodicityMinute(), None)
self.assertEqual(item.getPeriodicityMonthDay(), None)
self._simulateInstanceTree_calculateSubscriptionStartDate(date)
self._simulateHostingSubscription_calculateSubscriptionStartDate(date)
try:
item.fixConsistency()
finally:
self._dropInstanceTree_calculateSubscriptionStartDate()
self._dropHostingSubscription_calculateSubscriptionStartDate()
self.assertEqual(item.getPeriodicityHourList(), [0])
self.assertEqual(item.getPeriodicityMinuteList(), [0])
self.assertEqual(item.getPeriodicityMonthDay(), day)
def test_InstanceTree_fixConsistency_today_after_28(self):
self.test_InstanceTree_fixConsistency(DateTime('2012/01/29'), 28)
def test_HostingSubscription_fixConsistency_today_after_28(self):
self.test_HostingSubscription_fixConsistency(DateTime('2012/01/29'), 28)
def test_InstanceTree_manageAfter(self):
def test_HostingSubscription_manageAfter(self):
class DummyTestException(Exception):
pass
def verify_fixConsistency_call(self):
# Check that fixConsistency is called on instance tree
if self.getRelativeUrl().startswith('instance_tree_module/'):
if self.getRelativeUrl().startswith('hosting_subscription_module/'):
raise DummyTestException
else:
return self.fixConsistency_call()
# Replace serialize by a dummy method
InstanceTreeClass = self.portal.portal_types.getPortalTypeClass(
'Instance Tree')
InstanceTreeClass.fixConsistency_call = InstanceTreeClass.\
HostingSubscriptionClass = self.portal.portal_types.getPortalTypeClass(
'Hosting Subscription')
HostingSubscriptionClass.fixConsistency_call = HostingSubscriptionClass.\
fixConsistency
InstanceTreeClass.fixConsistency = verify_fixConsistency_call
HostingSubscriptionClass.fixConsistency = verify_fixConsistency_call
try:
# manage_afterAdd
self.assertRaises(
DummyTestException,
self.portal.instance_tree_module.newContent,
portal_type='Instance Tree')
self.portal.hosting_subscription_module.newContent,
portal_type='Hosting Subscription')
# manage_afterClone
self.assertRaises(
DummyTestException,
self.portal.instance_tree_module.\
template_instance_tree.Base_createCloneDocument,
self.portal.hosting_subscription_module.\
template_hosting_subscription.Base_createCloneDocument,
batch_mode=1)
finally:
self.portal.portal_types.resetDynamicDocumentsOnceAtTransactionBoundary()
......
......@@ -417,11 +417,12 @@ class TestDefaultPaymentRule(SlapOSTestCaseMixin):
SimulationMovement.getSimulationState = SimulationMovement\
.original_getSimulationState
class TestInstanceTreeSimulation(SlapOSTestCaseMixin):
class TestHostingSubscriptionSimulation(SlapOSTestCaseMixin):
def _prepare(self):
person = self.portal.person_module.template_member\
.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)
self.initial_date = DateTime('2011/02/16')
stop_date = DateTime('2011/04/16')
......@@ -432,6 +433,7 @@ class TestInstanceTreeSimulation(SlapOSTestCaseMixin):
destination_section=person.getRelativeUrl()
)
self.portal.portal_workflow._jumpToStateFor(self.subscription, 'validated')
self.portal.portal_workflow._jumpToStateFor(self.instance_tree, 'validated')
open_sale_order_template = self.portal.restrictedTraverse(
self.portal.portal_preferences.getPreferredOpenSaleOrderTemplate())
......
......@@ -34,12 +34,20 @@ import time
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):
new_id = self.generateNewId()
return self.portal.instance_tree_module.newContent(
portal_type='Instance Tree',
title="Subscription %s" % new_id,
reference="TESTHS-%s" % new_id,
reference="TESTIT-%s" % new_id,
)
def createOpenSaleOrder(self):
......@@ -82,46 +90,44 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin):
return invoice
@withAbort
def test_IT_calculateSubscriptionStartDate_REQUEST_disallowed(self):
item = self.createInstanceTree()
def test_HS_calculateSubscriptionStartDate_REQUEST_disallowed(self):
item = self.createHostingSubscription()
self.assertRaises(
Unauthorized,
item.InstanceTree_calculateSubscriptionStartDate,
item.HostingSubscription_calculateSubscriptionStartDate,
REQUEST={})
@withAbort
def test_IT_calculateSubscriptionStartDate_noWorkflow(self):
item = self.createInstanceTree()
def test_HS_calculateSubscriptionStartDate_noWorkflow(self):
item = self.createHostingSubscription()
item.workflow_history['instance_slap_interface_workflow'] = []
date = item.InstanceTree_calculateSubscriptionStartDate()
date = item.HostingSubscription_calculateSubscriptionStartDate()
self.assertEqual(date, item.getCreationDate().earliestTime())
@withAbort
def test_IT_calculateSubscriptionStartDate_withRequest(self):
item = self.createInstanceTree()
item.workflow_history['instance_slap_interface_workflow'] = [{
def test_HS_calculateSubscriptionStartDate_withRequest(self):
item = self.createHostingSubscription()
item.workflow_history['edit_workflow'] = [{
'comment':'Directly request the instance',
'error_message': '',
'actor': 'ERP5TypeTestCase',
'slap_state': 'draft',
'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'))
@withAbort
def test_IT_calculateSubscriptionStartDate_withRequestEndOfMonth(self):
item = self.createInstanceTree()
item.workflow_history['instance_slap_interface_workflow'] = [{
def test_HS_calculateSubscriptionStartDate_withRequestEndOfMonth(self):
item = self.createHostingSubscription()
item.workflow_history['edit_workflow'] = [{
'comment':'Directly request the instance',
'error_message': '',
'actor': 'ERP5TypeTestCase',
'slap_state': 'draft',
'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'))
@withAbort
......@@ -150,7 +156,7 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin):
self.assertEqual(date, DateTime('2012/10/30'))
@withAbort
def test_IT_calculateSubscriptionStopDate_REQUEST_disallowed(self):
def test_HS_calculateSubscriptionStopDate_REQUEST_disallowed(self):
item = self.createInstanceTree()
self.assertRaises(
Unauthorized,
......@@ -843,5 +849,4 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin):
payment_transaction.PaymentTransaction_start()
self.assertEqual("started",
payment_transaction.getSimulationState())
......@@ -22,7 +22,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>interaction_InstanceTree_afterAddClone</string> </value>
<value> <string>interaction_HostingSubscription_afterAddClone</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
......@@ -32,7 +32,7 @@
<key> <string>portal_type_filter</string> </key>
<value>
<tuple>
<string>Instance Tree</string>
<string>Hosting Subscription</string>
</tuple>
</value>
</item>
......@@ -46,6 +46,10 @@
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>HostingSubscription_afterAddClone</string> </value>
</item>
<item>
<key> <string>trigger_method_id</string> </key>
<value>
......
......@@ -10,7 +10,7 @@
<key> <string>categories</string> </key>
<value>
<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>
</value>
</item>
......@@ -22,7 +22,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>interaction_InstanceTree_fixConsistency</string> </value>
<value> <string>interaction_HostingSubscription_fixConsistency</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
......@@ -32,7 +32,7 @@
<key> <string>portal_type_filter</string> </key>
<value>
<tuple>
<string>Instance Tree</string>
<string>Hosting Subscription</string>
</tuple>
</value>
</item>
......@@ -46,6 +46,10 @@
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>HostingSubscription_fixConsistency</string> </value>
</item>
<item>
<key> <string>trigger_method_id</string> </key>
<value>
......
from erp5.component.module.DateUtils import addToDate, getClosestDate
instance_tree = state_change['object']
hosting_subscription = state_change['object']
edit_kw = {}
if instance_tree.getPeriodicityHour() is None:
if hosting_subscription.getPeriodicityHour() is None:
edit_kw['periodicity_hour_list'] = [0]
if instance_tree.getPeriodicityMinute() is None:
if hosting_subscription.getPeriodicityMinute() is None:
edit_kw['periodicity_minute_list'] = [0]
if instance_tree.getPeriodicityMonthDay() is None:
start_date = instance_tree.InstanceTree_calculateSubscriptionStartDate()
if hosting_subscription.getPeriodicityMonthDay() is None:
start_date = hosting_subscription.HostingSubscription_calculateSubscriptionStartDate()
start_date = getClosestDate(target_date=start_date, precision='day')
while start_date.day() >= 29:
start_date = addToDate(start_date, to_add={'day': -1})
edit_kw['periodicity_month_day_list'] = [start_date.day()]
if edit_kw:
instance_tree.edit(**edit_kw)
hosting_subscription.edit(**edit_kw)
......@@ -60,9 +60,15 @@
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>script_InstanceTree_fixPeriodicity</string> </value>
<value> <string>script_HostingSubscription_fixPeriodicity</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
......
......@@ -5,8 +5,8 @@ Compute Node | jump_to_consumption_report_view
Computer Consumption TioXML File | download
Computer Consumption TioXML File | view
Consumption Document Module | view
Hosting Subscription | periodicity
Instance Tree | jump_to_related_open_order_line
Instance Tree | periodicity
Payment Transaction | related_payzen_event
Person | create_new_cloud_contract
Person | jump_to_cloud_contract
......
Cloud Contract Line | SlapOSCloudContractLineAccounting
Cloud Contract | SlapOSCloudContractAccounting
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 Packing List Line | SlapOSAccountingSalePackingListLineConstraint
Sale Packing List | SlapOSAccountingSalePackingListConstraint
......
......@@ -4,6 +4,7 @@ Cloud Contract | item_workflow
Computer Consumption TioXML File | document_conversion_interaction_workflow
Computer Consumption TioXML File | document_publication_workflow
Computer Consumption TioXML File | edit_workflow
Hosting Subscription | slapos_accounting_interaction_workflow
Instance Tree | slapos_accounting_interaction_workflow
Instance Tree | slapos_api_invoicing_workflow
Sale Invoice Transaction | slapos_accounting_interaction_workflow
......
InstanceAccountingSynchronisation
SlapOSAccountingInstanceTreeConstraint
SlapOSAccountingOpenSaleOrderLineConstraint
SlapOSAccountingOpenSaleOrderConstraint
SlapOSAccountingHostingSubscriptionConstraint
SlapOSAccountingSaleInvoiceTransactionConstraint
SlapOSAccountingSalePackingListConstraint
SlapOSAccountingSalePackingListLineConstraint
......
......@@ -175,9 +175,7 @@ def HostingSubscription_checkInstanceTreeMigrationConsistency(self, fixit=False)
mod = __import__('erp5.portal_type', globals(), locals(), ['Instance Tree'])
klass = getattr(mod, 'Instance Tree')
if ((getattr(self, 'workflow_history', None) is not None) and
('hosting_subscription_workflow' in self.workflow_history)) or \
(self.__class__ == klass) or \
if (self.__class__ == klass) or \
(self.getProperty('sla_xml', None) is not None) or \
([x for x in self.getCategoryList() if (x.startswith('predecessor/') or
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 @@
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>SubscriptionItem</string> </value>
<value> <string>Item</string> </value>
</item>
<item>
<key> <string>type_interface</string> </key>
......
......@@ -13,7 +13,7 @@
</chain>
<chain>
<type>Hosting Subscription</type>
<workflow>edit_workflow</workflow>
<workflow>edit_workflow, hosting_subscription_workflow</workflow>
</chain>
<chain>
<type>Instance Tree</type>
......
......@@ -98,13 +98,16 @@
<value>
<list>
<string>my_title</string>
<string>my_reference</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
<list>
<string>my_translated_validation_state_title</string>
</list>
</value>
</item>
</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):
self.assertEqual(0, len(open_sale_order_list))
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
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())
# Select the first archived
open_sale_order = archived_open_sale_order_list[0]
line_list = []
for open_sale_order in archived_open_sale_order_list:
archived_line_list = open_sale_order.contentValues(
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.assertSameSet(
[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
if q.getValidationState() == 'validated']
# if no line, all open orders are kept archived
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):
for candidate in reversed(self.portal.MailHost.getMessageList()):
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
compute_node_module/template_compute_node/**
computer_model_module/template_computer_model
computer_model_module/template_computer_model/**
hosting_subscription_module/template_hosting_subscription
instance_tree_module/template_instance_tree
person_module/template_member
person_module/template_member/**
......@@ -25,4 +26,4 @@ portal_caches/last_stored_data_cache_factory/volatile_cache_plugin
product_module/compute_node
software_installation_module/template_software_installation
software_instance_module/template_slave_instance
software_instance_module/template_software_instance
\ No newline at end of file
software_instance_module/template_software_instance
......@@ -3,6 +3,7 @@ Compute Node | slapos_cloud_interaction_workflow
Compute Partition | compute_partition_slap_interface_workflow
Computer Network | network_slap_interface_workflow
Hosting Subscription | edit_workflow
Hosting Subscription | hosting_subscription_workflow
Instance Tree | edit_workflow
Instance Tree | instance_slap_interface_workflow
Instance Tree | instance_tree_workflow
......
audit_validation_workflow
compute_node_slap_interface_workflow
compute_partition_slap_interface_workflow
hosting_subscription_workflow
installation_slap_interface_workflow
instance_slap_interface_workflow
instance_tree_workflow
......
......@@ -294,11 +294,18 @@ class TestSlapOSDefaultCRMEscalation(DefaultScenarioMixin):
line_list = open_sale_order.contentValues(
portal_type='Open Sale Order Line')
self.assertEqual(len(instance_tree_list), len(line_list))
self.assertSameSet(
[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):
self.assertEqual('delivered', delivery.getSimulationState())
self.assertEqual('solved', delivery.getCausalityState())
......
......@@ -1629,8 +1629,12 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
software_type="default",
partition_reference="_test_subscription_scenario_with_existing_user_extra_instance",
)
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.requestAndCheckInstanceTree(
amount, name, default_email_text)
......@@ -1802,7 +1806,9 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
# Ensure periodicity is correct
for subscription_request in subscription_request_list:
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))
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