Commit c80d86a0 authored by Romain Courteaud's avatar Romain Courteaud

WIP: change Request

slapos_subscription_request: skeleton of Subscription Change Request

slapos_subscription_request: init reference

slapos_subscription_request: copy property sheets from Subscription Request

slapos_erp5: Subscription Change Request roles

slapos_erp5: business application on Subscription Change Request Module

slapos_subscription_request: add workflow on Subscription Change Request

slapos_erp5: sale only need Auditor role

slapos_subscription_request: add alarm to handle submitted Subscription Change Request

slapos_subscription_request: up

slapos_subscription_request: detect unexpected changes

slapos_subscription_request: unauthorized call

slapos_erp5: wip subscription change request scenario

slapos_subscription_request: archive previous open sale order

slapos_accounting: allow to archive Open Sale Order, even if the item is still valid

slapos_subscription_request: WIP: stop using discount resource, and reduce the quantity of the consumed software product / service

slapos_subscription_request: create discount with a standalone script

slapos_subscription_request: typo

slapos_subscription_request: create discount when closing the open sale order

slapos_subscription_request: comment

slapos_subscription_request: improve titles

slapos_accounting: expand Subscription Change Request discount SPL

slapos_subscription_request: keep subscription request causality link

slapos_subscription_request: fixup project migration

slapos_erp5: test Subscription Change Request local roles

slapos_subscription_request: fixup Subscription Change Request workflow chain

slapos_subscription_request: factorize to create Subscription Change Request

slapos_subscription_request: add one more assertion

slapos_erp5: test: reuse existing script

slapos_subscription_request: user script parameter to set the expected values
parent 8ba08ed6
......@@ -19,7 +19,7 @@ for open_order_line in open_sale_order.contentValues(
if item is None:
raise AssertionError('No matching item on: %s' % open_order_cell.getRelativeUrl())
if item.getValidationState() not in ['invalidated', 'archived']:
if check_unused_item and (item.getValidationState() not in ['invalidated', 'archived']):
# Do not touch if the item is not clean yet
return
......
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
<value> <string>check_unused_item=True</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
......@@ -22,7 +22,8 @@ portal.portal_catalog.searchAndActivate(
# Only Packing List created to generate deposit Invoice need to be expanded
'Payment Transaction',
# Discount Sale Packing List
'Subscription Request'
'Subscription Request',
'Subscription Change Request'
],
**kw
)
......
<local_roles_item>
<local_roles>
<role id='F-SALE*'>
<item>Auditor</item>
<item>Author</item>
</role>
</local_roles>
<local_role_group_ids>
<local_role_group_id id='function'>
<principal id='F-SALE*'>Auditor</principal>
<principal id='F-SALE*'>Author</principal>
</local_role_group_id>
</local_role_group_ids>
</local_roles_item>
\ No newline at end of file
<type_roles>
<role id='Author; Auditor'>
<property id='title'>Sale</property>
<multi_property id='categories'>local_role_group/function</multi_property>
<multi_property id='category'>function/sale*</multi_property>
<multi_property id='base_category'>function</multi_property>
</role>
</type_roles>
\ No newline at end of file
<type_roles>
<role id='Auditor'>
<property id='title'>Sale Agent</property>
<multi_property id='categories'>local_role_group/function</multi_property>
<multi_property id='category'>function/sale/agent</multi_property>
<multi_property id='base_category'>function</multi_property>
</role>
<role id='Auditor'>
<property id='title'>Sale Manager</property>
<multi_property id='categories'>local_role_group/function</multi_property>
<multi_property id='category'>function/sale/manager</multi_property>
<multi_property id='base_category'>function</multi_property>
</role>
</type_roles>
\ No newline at end of file
......@@ -40,6 +40,7 @@ for business_application_id, module_name_list in [
"software_installation_module",
"software_instance_module",
"subscription_request_module",
"subscription_change_request_module",
]],
["crm", [ # Customer Relation Management
"campaign_module",
......
......@@ -1638,6 +1638,26 @@ class TestSubscriptionRequest(TestSlapOSGroupRoleSecurityMixin):
self.assertRoles(delivery, person.getUserId(), ['Associate'])
self.assertRoles(delivery, 'R-SHADOW-PERSON', ['Auditor'])
class TestSubscriptionChangeRequestModule(TestSlapOSGroupRoleSecurityMixin):
def test_SubscriptionChangeRequestModule(self):
module = self.portal.subscription_change_request_module
self.assertSecurityGroup(module,
['F-SALE*', module.Base_getOwnerId()], False)
self.assertRoles(module, 'F-SALE*', ['Auditor', 'Author'])
self.assertRoles(module, module.Base_getOwnerId(), ['Owner'])
class TestSubscriptionChangeRequest(TestSlapOSGroupRoleSecurityMixin):
def test_SubscriptionChangeRequest_default(self):
delivery = self.portal.subscription_change_request_module.newContent(
portal_type='Subscription Change Request')
self.assertSecurityGroup(delivery,
[self.user_id, 'F-SALEAGT', 'F-SALEMAN'], False)
self.assertRoles(delivery, self.user_id, ['Owner'])
self.assertRoles(delivery, 'F-SALEAGT', ['Auditor'])
self.assertRoles(delivery, 'F-SALEMAN', ['Auditor'])
class TestOrganisationModule(TestSlapOSGroupRoleSecurityMixin):
def test_OrganisationModule(self):
module = self.portal.organisation_module
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2022 Nexedi SA and Contributors. All Rights Reserved.
#
##############################################################################
from erp5.component.test.testSlapOSERP5VirtualMasterScenario import TestSlapOSVirtualMasterScenarioMixin
class TestSlapOSSubscriptionChangeRequestScenarioMixin(TestSlapOSVirtualMasterScenarioMixin):
pass
class TestSlapOSSubscriptionChangeRequestScenario(TestSlapOSSubscriptionChangeRequestScenarioMixin):
def test_subscription_change_request_change_instance_destination_without_accounting_scenario(self):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
self.tic()
self.logout()
# lets join as slapos administrator, which will own few compute_nodes
owner_reference = 'owner-%s' % self.generateNewId()
self.joinSlapOS(self.web_site, owner_reference)
self.login()
owner_person = self.portal.portal_catalog.getResultValue(
portal_type="ERP5 Login",
reference=owner_reference).getParentValue()
#owner_person.setCareerSubordinationValue(seller_organisation)
self.tic()
# hooray, now it is time to create compute_nodes
self.logout()
self.login(sale_person.getUserId())
# create a default project
project_relative_url = self.addProject(person=owner_person, currency=currency)
self.logout()
self.login()
project = self.portal.restrictedTraverse(project_relative_url)
preference = self.portal.portal_preferences.slapos_default_system_preference
preference.edit(
preferred_subscription_assignment_category_list=[
'function/customer',
'role/client',
'destination_project/%s' % project.getRelativeUrl()
]
)
self.tic()
self.logout()
self.login(owner_person.getUserId())
"""
public_server_title = 'Public Server for %s' % owner_reference
public_server_id = self.requestComputeNode(public_server_title, project.getReference())
public_server = self.portal.portal_catalog.getResultValue(
portal_type='Compute Node', reference=public_server_id)
self.setAccessToMemcached(public_server)
self.assertNotEqual(None, public_server)
self.setServerOpenPublic(public_server)
public_server.generateCertificate()
"""
# and install some software on them
public_server_software = self.generateNewSoftwareReleaseUrl()
public_instance_type = 'public type'
"""
self.supplySoftware(public_server, public_server_software)
# format the compute_nodes
self.formatComputeNode(public_server)
software_product, release_variation, type_variation = self.addSoftwareProduct(
"""
self.addSoftwareProduct(
"instance product", project, public_server_software, public_instance_type
)
"""
self.addAllocationSupply("for compute node", public_server, software_product,
release_variation, type_variation)
self.tic()
self.logout()
"""
"""
self.login()
self.checkServiceSubscriptionRequest(public_server)
"""
# join as the another visitor and request software instance on public
# compute_node
self.logout()
public_reference = 'public-%s' % self.generateNewId()
self.joinSlapOS(self.web_site, public_reference)
public_reference2 = 'public2-%s' % self.generateNewId()
self.joinSlapOS(self.web_site, public_reference2)
self.login()
public_person = self.portal.portal_catalog.getResultValue(
portal_type="ERP5 Login",
reference=public_reference).getParentValue()
public_person2 = self.portal.portal_catalog.getResultValue(
portal_type="ERP5 Login",
reference=public_reference2).getParentValue()
person_user_id = public_person.getUserId()
software_release = public_server_software
software_type = public_instance_type
project_reference = project.getReference()
public_instance_title = 'Public title %s' % self.generateNewId()
self.login(person_user_id)
self.personRequestInstanceNotReady(
software_release=software_release,
software_type=software_type,
partition_reference=public_instance_title,
project_reference=project_reference
)
self.tic()
# XXX search only for this user
instance_tree = self.portal.portal_catalog.getResultValue(
portal_type="Instance Tree",
title=public_instance_title,
follow_up__reference=project_reference
)
self.checkServiceSubscriptionRequest(instance_tree)
self.tic()
"""
self.login(person_user_id)
self.personRequestInstance(
software_release=software_release,
software_type=software_type,
partition_reference=public_instance_title,
project_reference=project_reference
)
"""
"""
self.checkInstanceAllocation(public_person.getUserId(),
public_reference, public_instance_title,
public_server_software, public_instance_type,
public_server, project.getReference())
"""
self.login(sale_person.getUserId())
open_sale_order_cell = self.portal.portal_catalog.getResultValue(
portal_type='Open Sale Order Cell',
aggregate__uid=instance_tree.getUid(),
validation_state='validated'
)
open_sale_order_line = open_sale_order_cell.getParentValue()
open_sale_order = open_sale_order_line.getParentValue()
subscription_change_request = open_sale_order_cell.getResourceValue().Resource_createSubscriptionRequest(
public_person2,
# [software_type, software_release],
open_sale_order_cell.getVariationCategoryList(),
open_sale_order.getSourceProjectValue(),
currency_value=open_sale_order.getPriceCurrencyValue(),
portal_type='Subscription Change Request'
)
subscription_change_request.setAggregateValue(instance_tree)
subscription_change_request.setCausalityValue(open_sale_order)
self.tic()
self.logout()
self.login()
self.assertEquals(instance_tree.getDestinationSection(),
public_person2.getRelativeUrl())
# Total of quantity should be zero
inventory_list_kw = {
'group_by_section': False,
'group_by_node': False,
'group_by_variation': False,
'group_by_resource': True,
'resource_uid': subscription_change_request.getResourceUid(),
}
inventory_list = self.portal.portal_simulation.getCurrentInventoryList(**inventory_list_kw)
self.assertEquals(1, len(inventory_list))
self.assertEquals(0, inventory_list[0].total_quantity)
# Seller only sold 1 month
inventory_list_kw = {
'node_uid': subscription_change_request.getSourceUid(),
'group_by_section': False,
'group_by_node': False,
'group_by_variation': False,
'group_by_resource': True,
'resource_uid': subscription_change_request.getResourceUid(),
}
inventory_list = self.portal.portal_simulation.getCurrentInventoryList(**inventory_list_kw)
self.assertEquals(1, len(inventory_list))
self.assertEquals(-1, inventory_list[0].total_quantity)
inventory_list_kw = {
'group_by_section': False,
'group_by_node': True,
'group_by_variation': True,
'resource_uid': subscription_change_request.getResourceUid(),
}
inventory_list = self.portal.portal_simulation.getCurrentInventoryList(**inventory_list_kw)
self.assertEquals(3, len(inventory_list))
tracking_list = instance_tree.Item_getTrackingList()
self.assertEquals(2, len(tracking_list))
self.assertEquals(None, self.portal.portal_simulation.getInventoryList())
# Ensure no unexpected object has been created
# 2 credential request
# 1 instance tree
# 7 open sale order
# 4 assignment
# 3 simulation movement
# 4 sale packing list / line
# 2 sale trade condition ( a 3rd trade condition is not linked to the project)
# 1 software instance
# 1 software product
# 1 subscription change request
# 2 subscription request
self.assertRelatedObjectCount(project, 28)
self.checkERP5StateBeforeExit()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testSlapOSERP5SubscriptionChangeRequestScenario</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testSlapOSERP5SubscriptionChangeRequestScenario</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</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>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -75,6 +75,7 @@ service_module/zero_emission_ratio
software_installation_module
software_instance_module
software_product_module
subscription_change_request_module
subscription_request_module
support_request_module
system_event_module
......
......@@ -87,6 +87,8 @@ Software Instance
Software Instance Module
Software Product
Software Product Module
Subscription Change Request
Subscription Change Request Module
Subscription Request
Subscription Request Module
Support Request
......
......@@ -7,6 +7,7 @@ test.erp5.testSlapOSERP5InteractionWorkflow
test.erp5.testSlapOSERP5LocalPermissionSlapOSInteractionWorkflow
test.erp5.testSlapOSERP5SiteDump
test.erp5.testSlapOSERP5SkinSelection
test.erp5.testSlapOSERP5SubscriptionChangeRequestScenario
test.erp5.testSlapOSERP5VirtualMasterScenario
test.erp5.testSlapOSPrecacheManifest
test.erp5.testSlapOSPrecacheManifest
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_list</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_list</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>View</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/SubscriptionChangeRequestModule_viewSubscriptionChangeRequestList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>View</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/SubscriptionChangeRequest_view</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<module>
<id>subscription_change_request_module</id>
<permission_list>
<permission type='tuple'>
<name>Access Transient Objects</name>
<role>Assignee</role>
<role>Assignor</role>
<role>Associate</role>
<role>Auditor</role>
<role>Author</role>
<role>Manager</role>
</permission>
<permission type='tuple'>
<name>Access contents information</name>
<role>Assignee</role>
<role>Assignor</role>
<role>Associate</role>
<role>Auditor</role>
<role>Author</role>
<role>Manager</role>
</permission>
<permission type='tuple'>
<name>Access session data</name>
<role>Assignee</role>
<role>Assignor</role>
<role>Associate</role>
<role>Auditor</role>
<role>Author</role>
<role>Manager</role>
</permission>
<permission type='tuple'>
<name>Add portal content</name>
<role>Assignor</role>
<role>Author</role>
<role>Manager</role>
</permission>
<permission type='tuple'>
<name>Add portal folders</name>
<role>Assignor</role>
<role>Author</role>
<role>Manager</role>
</permission>
<permission type='tuple'>
<name>Change local roles</name>
<role>Assignor</role>
<role>Manager</role>
</permission>
<permission type='tuple'>
<name>Copy or Move</name>
<role>Assignee</role>
<role>Assignor</role>
<role>Associate</role>
<role>Auditor</role>
<role>Author</role>
<role>Manager</role>
</permission>
<permission type='tuple'>
<name>Delete objects</name>
<role>Assignor</role>
<role>Manager</role>
</permission>
<permission type='tuple'>
<name>List folder contents</name>
<role>Assignee</role>
<role>Assignor</role>
<role>Associate</role>
<role>Auditor</role>
<role>Author</role>
<role>Manager</role>
</permission>
<permission type='tuple'>
<name>Modify portal content</name>
<role>Assignor</role>
<role>Manager</role>
</permission>
<permission type='tuple'>
<name>View</name>
<role>Assignee</role>
<role>Assignor</role>
<role>Associate</role>
<role>Auditor</role>
<role>Manager</role>
</permission>
<permission type='tuple'>
<name>View History</name>
<role>Assignee</role>
<role>Assignor</role>
<role>Associate</role>
<role>Auditor</role>
<role>Author</role>
<role>Manager</role>
</permission>
</permission_list>
<portal_type>Subscription Change Request Module</portal_type>
<title>Subscription Change Requests</title>
</module>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Alarm" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>active_sense_method_id</string> </key>
<value> <string>Alarm_validateSubmittedSubscriptionChangeRequest</string> </value>
</item>
<item>
<key> <string>automatic_solve</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>slapos_subscription_change_request_validate_submitted</string> </value>
</item>
<item>
<key> <string>periodicity_day_frequency</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>periodicity_hour</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_minute</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_minute_frequency</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>periodicity_month</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_month_day</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_start_date</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1677632460.0</float>
<string>GMT</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>periodicity_week</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Alarm</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Validate submitted Subscription Change Request</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<allowed_content_type_list>
<portal_type id="Subscription Change Request Module">
<item>Subscription Change Request</item>
</portal_type>
<portal_type id="Subscription Request">
<item>Email</item>
</portal_type>
......
<base_category_list>
<portal_type id="Subscription Change Request Module">
<item>business_application</item>
</portal_type>
<portal_type id="Subscription Request">
<item>aggregate</item>
<item>causality</item>
......
<property_sheet_list>
<portal_type id="Subscription Change Request">
<item>Amount</item>
<item>CredentialRequest</item>
<item>DublinCore</item>
<item>InstanceTree</item>
<item>Person</item>
<item>Price</item>
<item>Reference</item>
<item>SlapOSSaleProfileConstraint</item>
<item>SlapOSSubscriptionRequestConstraint</item>
<item>Task</item>
<item>Url</item>
<item>VariationRange</item>
</portal_type>
<portal_type id="Subscription Request">
<item>Amount</item>
<item>CredentialRequest</item>
......
<type_roles>
<role id='Auditor'>
<property id='title'>Member</property>
<multi_property id='categories'>local_role_group/function</multi_property>
<multi_property id='category'>function/customer</multi_property>
<multi_property id='base_category'>function</multi_property>
</role>
<role id='Auditor; Author'>
<property id='title'>Sale</property>
<multi_property id='categories'>local_role_group/function</multi_property>
<multi_property id='category'>function/sale*</multi_property>
<multi_property id='base_category'>function</multi_property>
</role>
</type_roles>
\ No newline at end of file
<type_roles>
<role id='Associate'>
<property id='title'>Customer</property>
<property id='condition'>python: context.getDestinationDecision('', portal_type='Person') != ""
</property>
<property id='base_category_script'>ERP5Type_getSecurityCategoryFromContent</property>
<multi_property id='categories'>local_role_group/user</multi_property>
<multi_property id='base_category'>destination_decision</multi_property>
</role>
<role id='Auditor'>
<property id='title'>Sale Agent</property>
<multi_property id='categories'>local_role_group/function</multi_property>
<multi_property id='category'>function/sale*</multi_property>
<multi_property id='base_category'>function</multi_property>
</role>
</type_roles>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Base Type" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_property_domain_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>content_icon</string> </key>
<value> <string>folder_icon.gif</string> </value>
</item>
<item>
<key> <string>factory</string> </key>
<value> <string>addFolder</string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<tuple>
<string>module</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Subscription Change Request Module</string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>Folder</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>short_title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value> <string>erp5_ui</string> </value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>short_title</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value> <string>erp5_ui</string> </value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>title</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Base Type" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>content_icon</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>factory</string> </key>
<value> <string>addXMLObject</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Subscription Change Request</string> </value>
</item>
<item>
<key> <string>init_script</string> </key>
<value> <string>SubscriptionChangeRequest_init</string> </value>
</item>
<item>
<key> <string>permission</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>CredentialRequest</string> </value>
</item>
<item>
<key> <string>type_interface</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -27,6 +27,10 @@
<type>Software Product</type>
<workflow>slapos_subscription_request_interaction_workflow</workflow>
</chain>
<chain>
<type>Subscription Change Request</type>
<workflow>edit_workflow, slapos_subscription_request_interaction_workflow, subscription_request_workflow</workflow>
</chain>
<chain>
<type>Subscription Request</type>
<workflow>edit_workflow, slapos_subscription_request_interaction_workflow, subscription_request_workflow</workflow>
......
portal = context.getPortalObject()
portal.portal_catalog.searchAndActivate(
method_id='SubscriptionChangeRequest_validateIfSubmitted',
# Project are created only from UI for now
portal_type=["Subscription Change Request"],
simulation_state='submitted',
packet_size=1, # Separate calls to many transactions
activate_kw={'tag': tag}
)
context.activate(after_tag=tag).getId()
<?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>tag, fixit, params</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Alarm_validateSubmittedSubscriptionChangeRequest</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -57,12 +57,11 @@ if destination_decision_value is None:
return
try:
subscription_request = service.Resource_createSubscriptionRequest(destination_decision_value, resource_vcl, project_value, currency_value=currency_value, default_price=default_price)
subscription_request = service.Resource_createSubscriptionRequest(destination_decision_value, resource_vcl, project_value, currency_value=currency_value, default_price=default_price,
item_value=item, causality_value=item)
except AssertionError as error:
storeWorkflowComment(item, str(error))
return
subscription_request.setAggregateValue(item)
subscription_request.setCausalityValue(item)
subscription_request.reindexObject(activate_kw=activate_kw)
item.reindexObject(activate_kw=activate_kw)
......
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
portal = context.getPortalObject()
open_sale_order_cell = context
hosting_subscription = open_sale_order_cell.getAggregateValue(portal_type='Hosting Subscription')
open_sale_order = open_sale_order_cell.getParentValue()
if open_sale_order_cell.getPortalType() == 'Open Sale Order Cell':
open_sale_order = open_sale_order.getParentValue()
start_date = open_sale_order.getStartDate()
next_period_date = hosting_subscription.getNextPeriodicalDate(current_date)
if open_sale_order.getValidationState() == 'validated':
unused_day_count = current_date - start_date
elif open_sale_order.getValidationState() == 'archived':
unused_day_count = next_period_date - current_date
else:
raise NotImplementedError('Unhandled open order state: %s' % open_sale_order.getValidationState())
sale_packing_list_edit_kw = dict(
title=title,
start_date=start_date,
# It should match the first open order invoice
stop_date=next_period_date,
specialise_value=open_sale_order.getSpecialiseValue(),
source_value=open_sale_order.getSourceValue(),
source_section_value=open_sale_order.getSourceSectionValue(),
source_decision_value=open_sale_order.getSourceDecisionValue(),
source_project_value=open_sale_order.getSourceProjectValue(),
destination_value=open_sale_order.getDestinationValue(),
destination_section_value=open_sale_order.getDestinationSectionValue(),
destination_decision_value=open_sale_order.getDestinationDecisionValue(),
destination_project_value=open_sale_order.getDestinationProjectValue(),
ledger_value=open_sale_order.getLedgerValue(),
causality_value=causality_value,
price_currency_value=open_sale_order.getPriceCurrencyValue(),
activate_kw=activate_kw
)
if (0 < unused_day_count):#(subscription_request.getPrice() != 0) and (0 < unused_day_count):
# If the open order starts before today,
# generate a discount to the user on his next invoice
sale_packing_list = portal.sale_packing_list_module.newContent(
portal_type="Sale Packing List",
# description='XXX unused day %s - prevision %s' % (unused_day_count, context.getQuantityPrecisionFromResource(subscription_request.getResourceValue())),
**sale_packing_list_edit_kw
)
"""
price = -subscription_request.getPrice() * (unused_day_count / (next_period_date - start_date))
precision = context.getQuantityPrecisionFromResource(subscription_request.getPriceCurrencyValue())
# Use currency precision to reduce the float length
price = float(('%%0.%sf' % precision) % price)
discount_service = portal.restrictedTraverse('service_module/slapos_discount')
sale_packing_list.newContent(
portal_type="Sale Packing List Line",
resource_value=discount_service,
# Use a quantity of 1 to be able to count how many discount were distributed
quantity=1,
price=price,
quantity_unit_value=discount_service.getQuantityUnitValue(),
base_contribution_list=discount_service.getBaseContributionList(),
use=discount_service.getUse(),
activate_kw=activate_kw
)
"""
variation_category_list = open_sale_order_cell.getVariationCategoryList()
sale_packing_list_line = sale_packing_list.newContent(
portal_type="Sale Packing List Line",
resource_value=open_sale_order_cell.getResourceValue(),
variation_category_list=variation_category_list,
quantity_unit_value=open_sale_order_cell.getQuantityUnitValue(),
base_contribution_list=open_sale_order_cell.getResourceValue().getBaseContributionList(),
use=open_sale_order_cell.getResourceValue().getUse(),
activate_kw=activate_kw
)
if variation_category_list:
base_id = 'movement'
cell_key = list(sale_packing_list_line.getCellKeyList(base_id=base_id))[0]
sale_packing_list_cell = sale_packing_list_line.newCell(
base_id=base_id,
portal_type="Sale Packing List Cell",
*cell_key
)
sale_packing_list_cell.edit(
mapped_value_property_list=['price','quantity'],
predicate_category_list=cell_key,
variation_category_list=cell_key,
activate_kw=activate_kw
)
else:
sale_packing_list_cell = sale_packing_list_line
quantity = open_sale_order_cell.getQuantity() * (unused_day_count / (next_period_date - start_date))
# precision = context.getQuantityPrecisionFromResource(subscription_request.getResourceValue())
# XXX use currency precision, to ensure accounting is readable?
precision = context.getQuantityPrecisionFromResource(open_sale_order_cell.getPriceCurrencyValue())
# precision = 3
# Use currency precision to reduce the float length
quantity = float(('%%0.%sf' % precision) % quantity)
# raise NotImplementedError('%s %s -- %s' % (precision, quantity, subscription_request.getResourceValue().getBaseUnitQuantity()))
price = -open_sale_order_cell.getPrice() * (unused_day_count / (next_period_date - start_date))
precision = context.getQuantityPrecisionFromResource(open_sale_order_cell.getPriceCurrencyValue())
# Use currency precision to reduce the float length
price = float(('%%0.%sf' % precision) % price)
aggregate_value_list = [x for x in open_sale_order_cell.getAggregateValue() if (x.getPortalType() != 'Hosting Subscription')]
sale_packing_list_cell.edit(
# Quantity is negative, to reduce the stock of the consumed product
quantity=-quantity,
price=open_sale_order_cell.getPrice(),
# quantity=1,
# price=price,
aggregate_value_list=aggregate_value_list,
activate_kw=activate_kw
)
# """
sale_packing_list.Delivery_fixBaseContributionTaxableRate()
sale_packing_list.Base_checkConsistency()
sale_packing_list.confirm()
sale_packing_list.stop()
sale_packing_list.deliver()
return sale_packing_list
<?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>current_date, title, causality_value, activate_kw=None, REQUEST=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>OpenSaleOrderCell_createDiscountSalePackingList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -7,12 +7,14 @@ if subscriber_person_value is None:
source_project_value = None
destination_project_value = None
trade_condition_type = None
item = None
if resource.getPortalType() == "Software Product":
source_project_value = project_value
trade_condition_type = "instance_tree"
assert item_value is not None
assert item_value.getPortalType() == 'Instance Tree'
elif resource.getPortalType() == "Service":
if resource.getRelativeUrl() == "service_module/slapos_compute_node_subscription":
if project_value is None:
......@@ -20,10 +22,15 @@ elif resource.getPortalType() == "Service":
source_project_value = project_value
trade_condition_type = "compute_node"
assert item_value is not None
assert item_value.getPortalType() == 'Compute Node'
elif resource.getRelativeUrl() == "service_module/slapos_virtual_master_subscription":
if project_value is None:
raise AssertionError('Project is required for %s %s' % (resource.getRelativeUrl(), project_value))
item = project_value
assert item_value is None
item_value = project_value
trade_condition_type = "virtual_master"
else:
raise NotImplementedError('Unsupported resource: %s' % resource.getRelativeUrl())
......@@ -110,8 +117,8 @@ else:
if not price:
raise AssertionError('Can not find a price to generate the Subscription Request (%s)' % tmp_sale_order.getSpecialiseValue())
subscription_request = portal.subscription_request_module.newContent(
portal_type='Subscription Request',
subscription_request = portal.getDefaultModuleValue(portal_type).newContent(
portal_type=portal_type,
temp_object=temp_object,
destination_value=subscriber_person_value,
# Do not set a default destination section if it is not defined on a trade condition
......@@ -128,7 +135,7 @@ subscription_request = portal.subscription_request_module.newContent(
effective_date=now,
resource_value=resource,
variation_category_list=variation_category_list,
aggregate_value=item,
aggregate_value=item_value,
quantity_unit=resource.getQuantityUnit(),
quantity=1,
ledger="automated",
......@@ -139,6 +146,7 @@ subscription_request = portal.subscription_request_module.newContent(
price_currency=tmp_sale_order.getPriceCurrency(),
price=price,
# XXX activate_kw=activate_kw
causality_value=causality_value,
)
if temp_object:
subscription_request.edit(reference="foo")
......
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>subscriber_person_value, variation_category_list, project_value, currency_value=None, default_price=None, temp_object=False</string> </value>
<value> <string>subscriber_person_value, variation_category_list, project_value, currency_value=None, default_price=None, temp_object=False, portal_type=\'Subscription Request\', item_value=None, causality_value=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>Base_doSelect</string> </value>
</item>
<item>
<key> <string>action_title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list>
<string>listbox</string>
</list>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SubscriptionChangeRequestModule_viewSubscriptionChangeRequestList</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>SubscriptionChangeRequestModule_viewSubscriptionChangeRequestList</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_list</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Subscription Change Requests</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </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>
<string>portal_type</string>
<string>selection_name</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox</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>field_id</string> </key>
<value> <string>my_list_mode_listbox</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value>
<list>
<tuple>
<string>Subscription Change Request</string>
<string>Subscription Change Request</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>selection_name</string> </key>
<value> <string>subscription_change_request_module_selection</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Subscription Change Requests</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
portal = context.getPortalObject()
reference = "SUBCHREQ-%s" % portal.portal_ids.generateNewId(
id_group='slap_subscription_change_request_reference',
id_generator='uid')
context.edit(reference=reference)
<?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>**kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SubscriptionChangeRequest_init</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from erp5.component.module.DateUtils import getClosestDate
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
subscription_change_request = context
assert subscription_change_request.getPortalType() == 'Subscription Change Request'
assert subscription_change_request.getSimulationState() == 'submitted'
def invalidate(document, comment):
context.validate()
context.invalidate(comment=comment)
# Subscription Change Request will change an ongoing Open Sale Order
open_sale_order = subscription_change_request.getCausalityValue(portal_type='Open Sale Order')
if (open_sale_order is None) or (open_sale_order.getValidationState() != 'validated'):
return invalidate(subscription_change_request, 'No Open Sale Order to update')
# Search the line/cell
open_order_movement = None
open_order_movement_list = open_sale_order.contentValues(portal_type='Open Sale Order Line')
if len(open_order_movement_list) == 1:
open_order_movement = open_order_movement_list[0]
open_order_movement_list = open_order_movement.contentValues(portal_type='Open Sale Order Cell')
if 1 < len(open_order_movement_list):
open_order_movement = None
elif 1 == len(open_order_movement_list):
open_order_movement = open_order_movement_list[0]
if open_order_movement is None:
return invalidate(subscription_change_request, 'Can not find the open order movement')
identical_order_base_category_list = [
'specialise',
# 'destination',
# 'destination_section',
# 'destination_decisition',
'destination_project',
'source',
'source_section',
'source_project',
'price_currency',
'resource',
'variation_category_list',
'quantity_unit',
'quantity',
# 'price'
]
for identical_order_base_category in identical_order_base_category_list:
if open_order_movement.getProperty(identical_order_base_category) != subscription_change_request.getProperty(identical_order_base_category):
return invalidate(subscription_change_request, 'Unhandled requested changes on: %s' % identical_order_base_category)
# Ensure the subscribed item is the same
subscribed_item = open_order_movement.getAggregateValue(portal_type=['Instance Tree', 'Compute Node', 'Project'])
if subscription_change_request.getAggregateUid() != subscribed_item.getUid():
return invalidate(subscription_change_request, 'Unhandled requested changes on: aggregate')
# Ensure destination is different
if subscription_change_request.getDestination() == open_sale_order.getDestination():
return invalidate(subscription_change_request, 'Expected change on: destination')
# Create new Open Sale Order
next_open_sale_order = subscription_change_request.SubscriptionRequest_createOpenSaleOrder()
current_date = getClosestDate(target_date=next_open_sale_order.getCreationDate(), precision='day')
# XXX Compensation
open_sale_order.OpenSaleOrder_archiveIfUnusedItem(check_unused_item=False)
# if we want to always activate a discount as soon as an open order is archived (outside subscription change request)
# it is needed to call OpenSaleOrderCell_createDiscountSalePackingList is an interaction workflow
# with more extra checks.
open_order_movement.OpenSaleOrderCell_createDiscountSalePackingList(
current_date,
'transfer discount from %s to %s' % (open_sale_order.getReference(), next_open_sale_order.getReference()),
subscription_change_request
)#, activate_kw=activate_kw)
# Change Subscripted Item user if needed
subscribed_item = open_order_movement.getAggregateValue(portal_type=['Instance Tree', 'Compute Node', 'Project'])
if subscribed_item is None:
raise NotImplementedError('Unsupported subscribed item')
elif subscribed_item.getPortalType() == 'Compute Node':
# No user is set on Compute Node
pass
elif subscribed_item.getPortalType() == 'Instance Tree':
subscribed_item.edit(destination_section=subscription_change_request.getDestination())
elif subscribed_item.getPortalType() == 'Project':
subscribed_item.edit(destination=subscription_change_request.getDestination())
else:
raise NotImplementedError('Not implemented subscribed item')
return invalidate(subscription_change_request, 'New open order: %s' % next_open_sale_order.getRelativeUrl())
<?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>REQUEST=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SubscriptionChangeRequest_validateIfSubmitted</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>Base_edit</string> </value>
</item>
<item>
<key> <string>action_title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>my_title</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SubscriptionChangeRequest_view</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>SubscriptionChangeRequest_view</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_view</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Subscription Change Request</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from erp5.component.module.DateUtils import getClosestDate, addToDate
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
portal = context.getPortalObject()
subscription_request = context
......@@ -126,38 +130,11 @@ open_sale_order.validate()
#######################################################
# Discount
unused_day_count = current_date - start_date
if (subscription_request.getPrice() != 0) and (0 < unused_day_count):
# If the open order starts before today,
# generate a discount to the user on his next invoice
open_order_edit_kw['title'] = "first invoice discount for %s" % open_sale_order.getReference()
sale_packing_list = portal.sale_packing_list_module.newContent(
portal_type="Sale Packing List",
# It should match the first open order invoice
stop_date=next_period_date,
**open_order_edit_kw
)
price = -subscription_request.getPrice() * (unused_day_count / (next_period_date - start_date))
precision = context.getQuantityPrecisionFromResource(subscription_request.getPriceCurrencyValue())
# Use currency precision to reduce the float length
price = float(('%%0.%sf' % precision) % price)
discount_service = portal.restrictedTraverse('service_module/slapos_discount')
sale_packing_list.newContent(
portal_type="Sale Packing List Line",
resource_value=discount_service,
# Use a quantity of 1 to be able to count how many discount were distributed
quantity=1,
price=price,
quantity_unit_value=discount_service.getQuantityUnitValue(),
base_contribution_list=discount_service.getBaseContributionList(),
use=discount_service.getUse(),
activate_kw=activate_kw
)
sale_packing_list.Delivery_fixBaseContributionTaxableRate()
sale_packing_list.Base_checkConsistency()
sale_packing_list.confirm()
sale_packing_list.stop()
sale_packing_list.deliver()
open_order_cell.OpenSaleOrderCell_createDiscountSalePackingList(
current_date,
"first invoice discount for %s" % open_sale_order.getReference(),
subscription_request,
activate_kw=activate_kw
)
return open_sale_order
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
<value> <string>REQUEST=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
subscription_request = context
portal = context.getPortalObject()
......
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
<value> <string>REQUEST=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Interaction Workflow Interaction" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>after_script/portal_workflow/slapos_subscription_request_interaction_workflow/script_Base_triggerSubscriptionChangeRequestValidationAlarm</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>interaction_SubscriptionChangeRequest_submit</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Interaction Workflow Interaction</string> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<value>
<tuple>
<string>Subscription Change Request</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>trigger_method_id</string> </key>
<value>
<tuple>
<string>submit</string>
</tuple>
</value>
</item>
<item>
<key> <string>trigger_once_per_transaction</string> </key>
<value> <int>0</int> </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 Script" module="erp5.portal_type"/>
</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>state_change</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>script_Base_triggerSubscriptionChangeRequestValidationAlarm</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Script</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
Instance Tree | jump_to_related_subscription_request
Person | jump_to_subscription_request
Sale Invoice Transaction | jump_to_related_subscription_request
Subscription Change Request Module | view
Subscription Change Request | view
Subscription Request Module | view
Subscription Request | view
\ No newline at end of file
subscription_change_request_module
subscription_request_module
\ No newline at end of file
portal_alarms/slapos_subscription_change_request_validate_submitted
portal_alarms/slapos_subscription_request_create_from_orphaned_item
portal_alarms/slapos_subscription_request_validate_submitted
service_module/slapos_reservation_fee_2
\ No newline at end of file
Subscription Change Request Module | Subscription Change Request
Subscription Request Module | Subscription Request
Subscription Request | Email
\ No newline at end of file
Subscription Change Request Module | business_application
Subscription Request Module | business_application
Subscription Request | aggregate
Subscription Request | causality
......
Subscription Change Request
Subscription Change Request Module
Subscription Request
Subscription Request Module
\ No newline at end of file
Subscription Change Request | Amount
Subscription Change Request | CredentialRequest
Subscription Change Request | DublinCore
Subscription Change Request | InstanceTree
Subscription Change Request | Person
Subscription Change Request | Price
Subscription Change Request | Reference
Subscription Change Request | SlapOSSaleProfileConstraint
Subscription Change Request | SlapOSSubscriptionRequestConstraint
Subscription Change Request | Task
Subscription Change Request | Url
Subscription Change Request | VariationRange
Subscription Request | Amount
Subscription Request | CredentialRequest
Subscription Request | DublinCore
......
Subscription Request
Subscription Request Module
\ No newline at end of file
......@@ -5,6 +5,9 @@ Open Sale Order | slapos_subscription_request_interaction_workflow
Sale Invoice Transaction Line | slapos_subscription_request_interaction_workflow
Sale Trade Condition | slapos_subscription_request_interaction_workflow
Software Product | slapos_subscription_request_interaction_workflow
Subscription Change Request | edit_workflow
Subscription Change Request | slapos_subscription_request_interaction_workflow
Subscription Change Request | subscription_request_workflow
Subscription Request | edit_workflow
Subscription Request | slapos_subscription_request_interaction_workflow
Subscription Request | subscription_request_workflow
\ No newline at end of file
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