From 6bcb0fc450d4131ef6b133c096b2af1e3f7986c2 Mon Sep 17 00:00:00 2001
From: Romain Courteaud <romain@nexedi.com>
Date: Wed, 6 Mar 2013 14:28:05 +0100
Subject: [PATCH] New alarm to automatically create registration request.

This alarm send the first invoice reminder to the user.
---
 ...apos_crm_create_regularisation_request.xml | 103 +++++++++++
 .../Alarm_createRegularisationRequest.xml     |  79 ++++++++
 ...son_checkToCreateRegularisationRequest.xml | 145 +++++++++++++++
 .../TestTemplateItem/testSlapOSCRMAlarm.py    | 169 ++++++++++++++++++
 .../TestTemplateItem/testSlapOSCRMSkins.py    | 153 ++++++++++++++++
 master/bt5/slapos_crm/bt/revision             |   2 +-
 master/bt5/slapos_crm/bt/template_path_list   |   1 +
 .../bt5/slapos_crm/bt/template_test_id_list   |   2 +
 8 files changed, 653 insertions(+), 1 deletion(-)
 create mode 100644 master/bt5/slapos_crm/PathTemplateItem/portal_alarms/slapos_crm_create_regularisation_request.xml
 create mode 100644 master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Alarm_createRegularisationRequest.xml
 create mode 100644 master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Person_checkToCreateRegularisationRequest.xml
 create mode 100644 master/bt5/slapos_crm/TestTemplateItem/testSlapOSCRMAlarm.py
 create mode 100644 master/bt5/slapos_crm/TestTemplateItem/testSlapOSCRMSkins.py
 create mode 100644 master/bt5/slapos_crm/bt/template_test_id_list

diff --git a/master/bt5/slapos_crm/PathTemplateItem/portal_alarms/slapos_crm_create_regularisation_request.xml b/master/bt5/slapos_crm/PathTemplateItem/portal_alarms/slapos_crm_create_regularisation_request.xml
new file mode 100644
index 000000000..e8e4a50c7
--- /dev/null
+++ b/master/bt5/slapos_crm/PathTemplateItem/portal_alarms/slapos_crm_create_regularisation_request.xml
@@ -0,0 +1,103 @@
+<?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_createRegularisationRequest</string> </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_crm_create_regularisation_request</string> </value>
+        </item>
+        <item>
+            <key> <string>periodicity_hour</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>periodicity_hour_frequency</string> </key>
+            <value> <int>2</int> </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="DateTime" module="DateTime.DateTime"/>
+                </klass>
+                <tuple>
+                  <none/>
+                </tuple>
+                <state>
+                  <tuple>
+                    <float>1288051200.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>sense_method_id</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>title</string> </key>
+            <value> <string>Create regularisation request</string> </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Alarm_createRegularisationRequest.xml b/master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Alarm_createRegularisationRequest.xml
new file mode 100644
index 000000000..744a045ac
--- /dev/null
+++ b/master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Alarm_createRegularisationRequest.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>Script_magic</string> </key>
+            <value> <int>3</int> </value>
+        </item>
+        <item>
+            <key> <string>_bind_names</string> </key>
+            <value>
+              <object>
+                <klass>
+                  <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
+                </klass>
+                <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>_body</string> </key>
+            <value> <string>portal = context.getPortalObject()\n
+from Products.ZSQLCatalog.SQLCatalog import SimpleQuery, NegatedQuery\n
+\n
+# XXX TODO: use getInventory to directly fetch user with a wrong balance\n
+portal.portal_catalog.searchAndActivate(\n
+      portal_type="Person", \n
+      validation_state="validated",\n
+      reference=NegatedQuery(SimpleQuery(reference=None)),\n
+      default_email_text=NegatedQuery(SimpleQuery(default_email_text=None)),\n
+      method_id=\'Person_checkToCreateRegularisationRequest\',\n
+      activate_kw={\'tag\': tag}\n
+      )\n
+context.activate(after_tag=tag).getId()\n
+</string> </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_createRegularisationRequest</string> </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Person_checkToCreateRegularisationRequest.xml b/master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Person_checkToCreateRegularisationRequest.xml
new file mode 100644
index 000000000..1adcd7b35
--- /dev/null
+++ b/master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Person_checkToCreateRegularisationRequest.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>Script_magic</string> </key>
+            <value> <int>3</int> </value>
+        </item>
+        <item>
+            <key> <string>_bind_names</string> </key>
+            <value>
+              <object>
+                <klass>
+                  <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
+                </klass>
+                <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>_body</string> </key>
+            <value> <string encoding="cdata"><![CDATA[
+
+from zExceptions import Unauthorized\n
+if REQUEST is not None:\n
+  raise Unauthorized\n
+\n
+portal = context.getPortalObject()\n
+person = context\n
+ticket_portal_type = "Regularisation Request"\n
+\n
+# XXX TODO\n
+# # Prevent to create 2 tickets during the same transaction\n
+# transactional_variable = getTransactionalVariable()\n
+# if tag in transactional_variable:\n
+#   raise RuntimeError, \'ticket %s already exist\' % tag\n
+# else:\n
+#   transactional_variable[tag] = None\n
+\n
+ticket = portal.portal_catalog.getResultValue(\n
+  portal_type=ticket_portal_type,\n
+  default_source_project_uid=person.getUid(),\n
+  simulation_state=[\'suspended\', \'validated\'],\n
+)\n
+if (ticket is None) and int(person.Entity_statBalance()) > 0:\n
+\n
+  tag = "%s_addRegularisationRequest_inProgress" % person.getUid()\n
+  if (portal.portal_activities.countMessageWithTag(tag) > 0):\n
+    # The regularisation request is already under creation but can not be fetched from catalog\n
+    # As it is not possible to fetch informations, it is better to raise an error\n
+    return None, None\n
+\n
+  # Prevent concurrent transaction to create 2 tickets for the same person\n
+  person.serialize()\n
+\n
+  # Time to create the ticket\n
+  regularisation_request_template = portal.restrictedTraverse(\n
+    portal.portal_preferences.getPreferredRegularisationRequestTemplate())\n
+  ticket = regularisation_request_template.Base_createCloneDocument(batch_mode=1)\n
+  ticket.edit(\n
+    source_project_value=context,\n
+    title=\'Account regularisation expected for "%s"\' % context.getTitle(),\n
+    destination_decision_value=context,\n
+    start_date=DateTime(),\n
+    resource=portal.portal_preferences.getPreferredRegularisationRequestResource(),\n
+  )\n
+  ticket.validate(comment=\'New automatic ticket for %s\' % context.getTitle())\n
+  ticket.suspend(comment=\'New automatic ticket for %s\' % context.getTitle())\n
+\n
+  ticket.reindexObject(activate_kw={\'tag\': tag})\n
+\n
+  # Inform user that the ticket has been created.\n
+  mail_message = portal.event_module.newContent(\n
+    portal_type=\'Mail Message\',\n
+    start_date=DateTime(),\n
+    destination_value=person,\n
+    follow_up=ticket.getRelativeUrl(),\n
+    source_value=ticket.getSourceValue(),\n
+    title=\'Invoice payment requested\',\n
+    resource=ticket.getResource(),\n
+    text_content="""\n
+Dear user,\n
+\n
+A new invoice has been generated. \n
+You can access it in your invoice section at %s.\n
+\n
+Do not hesitate to visit the web forum (http://community.slapos.org/forum) in case of question.\n
+\n
+Regards,\n
+The slapos team\n
+""" % portal.portal_preferences.getPreferredSlaposWebSiteUrl())\n
+  portal.portal_workflow.doActionFor(mail_message, \'start_action\', send_mail=True, comment=\'Requested manual payment.\')\n
+  mail_message.stop(comment=\'Requested manual payment.\')\n
+  mail_message.deliver(comment=\'Requested manual payment.\')\n
+\n
+  return ticket, mail_message\n
+\n
+return ticket, None\n
+
+
+]]></string> </value>
+        </item>
+        <item>
+            <key> <string>_params</string> </key>
+            <value> <string>REQUEST=None</string> </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>Person_checkToCreateRegularisationRequest</string> </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/master/bt5/slapos_crm/TestTemplateItem/testSlapOSCRMAlarm.py b/master/bt5/slapos_crm/TestTemplateItem/testSlapOSCRMAlarm.py
new file mode 100644
index 000000000..df0a2af45
--- /dev/null
+++ b/master/bt5/slapos_crm/TestTemplateItem/testSlapOSCRMAlarm.py
@@ -0,0 +1,169 @@
+# Copyright (c) 2013 Nexedi SA and Contributors. All Rights Reserved.
+import transaction
+from Products.SlapOS.tests.testSlapOSMixin import \
+  testSlapOSMixin
+from Products.ERP5Type.tests.utils import createZODBPythonScript
+
+class TestSlapOSCRMCreateRegularisationRequest(testSlapOSMixin):
+
+  def _simulatePerson_checkToCreateRegularisationRequest(self):
+    script_name = 'Person_checkToCreateRegularisationRequest'
+    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,
+                        script_name,
+                        '*args, **kwargs',
+                        '# Script body\n'
+"""portal_workflow = context.portal_workflow
+portal_workflow.doActionFor(context, action='edit_action', comment='Visited by Person_checkToCreateRegularisationRequest') """ )
+    transaction.commit()
+
+  def _dropPerson_checkToCreateRegularisationRequest(self):
+    script_name = 'Person_checkToCreateRegularisationRequest'
+    if script_name in self.portal.portal_skins.custom.objectIds():
+      self.portal.portal_skins.custom.manage_delObjects(script_name)
+    transaction.commit()
+
+  def test_alarm_expected_person(self):
+    new_id = self.generateNewId()
+    person = self.portal.person_module.newContent(
+      portal_type='Person',
+      title="Test person %s" % new_id,
+      reference="TESTPERS_%s" % new_id,
+      default_email_text="%s@example.org" % new_id,
+      )
+    person.validate()
+
+    self.tic()
+    self._simulatePerson_checkToCreateRegularisationRequest()
+    try:
+      self.portal.portal_alarms.\
+          slapos_crm_create_regularisation_request.activeSense()
+      self.tic()
+    finally:
+      self._dropPerson_checkToCreateRegularisationRequest()
+    self.assertEqual(
+        'Visited by Person_checkToCreateRegularisationRequest',
+        person.workflow_history['edit_workflow'][-1]['comment'])
+
+  def test_alarm_no_email(self):
+    new_id = self.generateNewId()
+    person = self.portal.person_module.newContent(
+      portal_type='Person',
+      title="Test person %s" % new_id,
+      reference="TESTPERS_%s" % new_id,
+      )
+    person.validate()
+
+    self.tic()
+    self._simulatePerson_checkToCreateRegularisationRequest()
+    try:
+      self.portal.portal_alarms.\
+          slapos_crm_create_regularisation_request.activeSense()
+      self.tic()
+    finally:
+      self._dropPerson_checkToCreateRegularisationRequest()
+    self.assertNotEqual(
+        'Visited by Person_checkToCreateRegularisationRequest',
+        person.workflow_history['edit_workflow'][-1]['comment'])
+
+  def test_alarm_no_reference(self):
+    new_id = self.generateNewId()
+    person = self.portal.person_module.newContent(
+      portal_type='Person',
+      title="Test person %s" % new_id,
+      default_email_text="%s@example.org" % new_id,
+      )
+    person.validate()
+
+    self.tic()
+    self._simulatePerson_checkToCreateRegularisationRequest()
+    try:
+      self.portal.portal_alarms.\
+          slapos_crm_create_regularisation_request.activeSense()
+      self.tic()
+    finally:
+      self._dropPerson_checkToCreateRegularisationRequest()
+    self.assertNotEqual(
+        'Visited by Person_checkToCreateRegularisationRequest',
+        person.workflow_history['edit_workflow'][-1]['comment'])
+
+  def test_alarm_not_validated(self):
+    new_id = self.generateNewId()
+    person = self.portal.person_module.newContent(
+      portal_type='Person',
+      title="Test person %s" % new_id,
+      reference="TESTPERS_%s" % new_id,
+      default_email_text="%s@example.org" % new_id,
+      )
+    person.validate()
+    person.invalidate()
+
+    self.tic()
+    self._simulatePerson_checkToCreateRegularisationRequest()
+    try:
+      self.portal.portal_alarms.\
+          slapos_crm_create_regularisation_request.activeSense()
+      self.tic()
+    finally:
+      self._dropPerson_checkToCreateRegularisationRequest()
+    self.assertNotEqual(
+        'Visited by Person_checkToCreateRegularisationRequest',
+        person.workflow_history['edit_workflow'][-1]['comment'])
+
+#   def test_alarm_not_suspended_support_request(self):
+#     new_id = self.generateNewId()
+#     payment = self.portal.accounting_module.newContent(
+#       portal_type='Payment Transaction',
+#       title="Payment %s" % new_id,
+#       reference="TESTPAY-%s" % new_id,
+#       )
+#     new_id = self.generateNewId()
+#     ticket = self.portal.support_request_module.newContent(
+#       portal_type='Support Request',
+#       title="Ticket %s" % new_id,
+#       reference="TESTSUPREQ-%s" % new_id,
+#       source_project_value=payment,
+#       )
+#     ticket.validate()
+# 
+#     self.tic()
+#     self._simulatePerson_checkToCreateRegularisationRequest()
+#     try:
+#       self.portal.portal_alarms.\
+#           slapos_payzen_update_suspended_support_request.activeSense()
+#       self.tic()
+#     finally:
+#       self._dropPerson_checkToCreateRegularisationRequest()
+#     self.assertNotEqual(
+#         'Visited by Person_checkToCreateRegularisationRequest',
+#         ticket.workflow_history['edit_workflow'][-1]['comment'])
+# 
+#   def test_alarm_suspended_support_request(self):
+#     new_id = self.generateNewId()
+#     payment = self.portal.accounting_module.newContent(
+#       portal_type='Payment Transaction',
+#       title="Payment %s" % new_id,
+#       reference="TESTPAY-%s" % new_id,
+#       )
+#     new_id = self.generateNewId()
+#     ticket = self.portal.support_request_module.newContent(
+#       portal_type='Support Request',
+#       title="Ticket %s" % new_id,
+#       reference="TESTSUPREQ-%s" % new_id,
+#       source_project_value=payment,
+#       )
+#     ticket.validate()
+#     ticket.suspend()
+# 
+#     self.tic()
+#     self._simulatePerson_checkToCreateRegularisationRequest()
+#     try:
+#       self.portal.portal_alarms.\
+#           slapos_payzen_update_suspended_support_request.activeSense()
+#       self.tic()
+#     finally:
+#       self._dropPerson_checkToCreateRegularisationRequest()
+#     self.assertEqual(
+#         'Visited by Person_checkToCreateRegularisationRequest',
+#         ticket.workflow_history['edit_workflow'][-1]['comment'])
diff --git a/master/bt5/slapos_crm/TestTemplateItem/testSlapOSCRMSkins.py b/master/bt5/slapos_crm/TestTemplateItem/testSlapOSCRMSkins.py
new file mode 100644
index 000000000..3ed45abcb
--- /dev/null
+++ b/master/bt5/slapos_crm/TestTemplateItem/testSlapOSCRMSkins.py
@@ -0,0 +1,153 @@
+# Copyright (c) 2013 Nexedi SA and Contributors. All Rights Reserved.
+import transaction
+from Products.SlapOS.tests.testSlapOSMixin import \
+  testSlapOSMixin
+from zExceptions import Unauthorized
+from DateTime import DateTime
+from functools import wraps
+from Products.ERP5Type.tests.utils import createZODBPythonScript
+import difflib
+
+def simulate(script_id, params_string, code_string):
+  def upperWrap(f):
+    @wraps(f)
+    def decorated(self, *args, **kw):
+      if script_id in self.portal.portal_skins.custom.objectIds():
+        raise ValueError('Precondition failed: %s exists in custom' % script_id)
+      createZODBPythonScript(self.portal.portal_skins.custom,
+                          script_id, params_string, code_string)
+      try:
+        result = f(self, *args, **kw)
+      finally:
+        if script_id in self.portal.portal_skins.custom.objectIds():
+          self.portal.portal_skins.custom.manage_delObjects(script_id)
+        transaction.commit()
+      return result
+    return decorated
+  return upperWrap
+
+
+class TestSlapOSPerson_checkToCreateRegularisationRequest(testSlapOSMixin):
+
+  def beforeTearDown(self):
+    transaction.abort()
+
+  def createPerson(self):
+    new_id = self.generateNewId()
+    return self.portal.person_module.newContent(
+      portal_type='Person',
+      title="Person %s" % new_id,
+      reference="TESTPERS-%s" % new_id,
+      )
+
+  @simulate('Entity_statBalance', '*args, **kwargs', 'return "1"')
+  def test_addRegularisationRequest_payment_requested(self):
+    for preference in \
+      self.portal.portal_catalog(portal_type="System Preference"):
+      preference = preference.getObject()
+      if preference.getPreferenceState() == 'global':
+        preference.setPreferredSlaposWebSiteUrl('http://foobar.org/')
+
+    person = self.createPerson()
+    before_date = DateTime()
+    ticket, event = person.Person_checkToCreateRegularisationRequest()
+    after_date = DateTime()
+    self.assertEquals(ticket.getPortalType(), 'Regularisation Request')
+    self.assertEquals(ticket.getSimulationState(), 'suspended')
+    self.assertEquals(ticket.getSourceProject(), person.getRelativeUrl())
+    self.assertEquals(ticket.getTitle(),
+           'Account regularisation expected for "%s"' % person.getTitle())
+    self.assertEquals(event.getPortalType(), 'Mail Message')
+    self.assertTrue(event.getStartDate() >= before_date)
+    self.assertTrue(event.getStopDate() <= after_date)
+    self.assertEquals(event.getTitle(), "Invoice payment requested")
+    self.assertEquals(event.getDestination(),
+                      person.getRelativeUrl())
+    self.assertEquals(event.getSource(),
+                      ticket.getSource())
+    expected_text_content = """
+Dear user,
+
+A new invoice has been generated. 
+You can access it in your invoice section at http://foobar.org/.
+
+Do not hesitate to visit the web forum (http://community.slapos.org/forum) in case of question.
+
+Regards,
+The slapos team
+"""
+    self.assertEquals(event.getTextContent(), expected_text_content,
+                      '\n'.join([x for x in difflib.unified_diff(
+                                           event.getTextContent().splitlines(),
+                                           expected_text_content.splitlines())]))
+    self.assertEquals(event.getSimulationState(), 'delivered')
+
+
+#   def test_addRegularisationRequest_do_not_duplicate_ticket(self):
+#     person = self.createPerson()
+#     ticket = person.Person_checkToCreateRegularisationRequest()
+#     ticket2 = person.Person_checkToCreateRegularisationRequest()
+#     self.assertEquals(ticket.getRelativeUrl(), ticket2.getRelativeUrl())
+
+  @simulate('Entity_statBalance', '*args, **kwargs', 'return "1"')
+  def test_addRegularisationRequest_do_not_duplicate_ticket_if_not_reindexed(self):
+    person = self.createPerson()
+    ticket, event = person.Person_checkToCreateRegularisationRequest()
+    transaction.commit()
+    ticket2, event2 = person.Person_checkToCreateRegularisationRequest()
+    self.assertNotEquals(ticket, None)
+    self.assertNotEquals(event, None)
+    self.assertEquals(ticket2, None)
+    self.assertEquals(event2, None)
+
+  @simulate('Entity_statBalance', '*args, **kwargs', 'return "0"')
+  def test_addRegularisationRequest_balance_ok(self):
+    person = self.createPerson()
+    ticket, event = person.Person_checkToCreateRegularisationRequest()
+    self.assertEquals(ticket, None)
+    self.assertEquals(event, None)
+
+  @simulate('Entity_statBalance', '*args, **kwargs', 'return "1"')
+  def test_addRegularisationRequest_existing_suspended_ticket(self):
+    person = self.createPerson()
+    ticket, event = person.Person_checkToCreateRegularisationRequest()
+    transaction.commit()
+    self.tic()
+    ticket2, event2 = person.Person_checkToCreateRegularisationRequest()
+    self.assertNotEquals(ticket, None)
+    self.assertNotEquals(event, None)
+    self.assertEquals(ticket2.getRelativeUrl(), ticket.getRelativeUrl())
+    self.assertEquals(event2, None)
+
+  @simulate('Entity_statBalance', '*args, **kwargs', 'return "1"')
+  def test_addRegularisationRequest_existing_validated_ticket(self):
+    person = self.createPerson()
+    ticket, event = person.Person_checkToCreateRegularisationRequest()
+    ticket.validate()
+    transaction.commit()
+    self.tic()
+    ticket2, event2 = person.Person_checkToCreateRegularisationRequest()
+    self.assertNotEquals(ticket, None)
+    self.assertNotEquals(event, None)
+    self.assertEquals(ticket2.getRelativeUrl(), ticket.getRelativeUrl())
+    self.assertEquals(event2, None)
+
+  @simulate('Entity_statBalance', '*args, **kwargs', 'return "1"')
+  def test_addRegularisationRequest_existing_invalidated_ticket(self):
+    person = self.createPerson()
+    ticket, event = person.Person_checkToCreateRegularisationRequest()
+    ticket.invalidate()
+    transaction.commit()
+    self.tic()
+    ticket2, event2 = person.Person_checkToCreateRegularisationRequest()
+    self.assertNotEquals(ticket2.getRelativeUrl(), ticket.getRelativeUrl())
+    self.assertNotEquals(event2, None)
+
+  def test_addRegularisationRequest_REQUEST_disallowed(self):
+    date = DateTime()
+    person = self.createPerson()
+    self.assertRaises(
+      Unauthorized,
+      person.Person_checkToCreateRegularisationRequest,
+      REQUEST={})
+
diff --git a/master/bt5/slapos_crm/bt/revision b/master/bt5/slapos_crm/bt/revision
index f11c82a4c..9a037142a 100644
--- a/master/bt5/slapos_crm/bt/revision
+++ b/master/bt5/slapos_crm/bt/revision
@@ -1 +1 @@
-9
\ No newline at end of file
+10
\ No newline at end of file
diff --git a/master/bt5/slapos_crm/bt/template_path_list b/master/bt5/slapos_crm/bt/template_path_list
index c44811599..19f95f576 100644
--- a/master/bt5/slapos_crm/bt/template_path_list
+++ b/master/bt5/slapos_crm/bt/template_path_list
@@ -1,4 +1,5 @@
 event_module/slapos_crm_web_message_template
+portal_alarms/slapos_crm_create_regularisation_request
 regularisation_request_module/slapos_crm_regularisation_request_template
 service_module/slapos_crm_acknowledgement
 service_module/slapos_crm_complaint
diff --git a/master/bt5/slapos_crm/bt/template_test_id_list b/master/bt5/slapos_crm/bt/template_test_id_list
new file mode 100644
index 000000000..45716676f
--- /dev/null
+++ b/master/bt5/slapos_crm/bt/template_test_id_list
@@ -0,0 +1,2 @@
+testSlapOSCRMSkins
+testSlapOSCRMAlarm
\ No newline at end of file
-- 
2.30.9