Commit 8d7de9f2 authored by Romain Courteaud's avatar Romain Courteaud

Split SPL deliver alarm in 2.

It allows to propagate the date change in the simulation without having frozen
movement.
parent db27964f
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>active_sense_method_id</string> </key> <key> <string>active_sense_method_id</string> </key>
<value> <string>Alarm_deliverConfirmedAggregatedSalePackingList</string> </value> <value> <string>Alarm_deliverStartedAggregatedSalePackingList</string> </value>
</item> </item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>slapos_deliver_confirmed_aggregated_sale_packing_list</string> </value> <value> <string>slapos_deliver_started_aggregated_sale_packing_list</string> </value>
</item> </item>
<item> <item>
<key> <string>periodicity_day_frequency</string> </key> <key> <string>periodicity_day_frequency</string> </key>
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>Deliver confirmed aggregated Sale Packing List</string> </value> <value> <string>Deliver started aggregated Sale Packing List</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
<?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_startConfirmedAggregatedSalePackingList</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_start_confirmed_aggregated_sale_packing_list</string> </value>
</item>
<item>
<key> <string>periodicity_day_frequency</string> </key>
<value>
<none/>
</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> <int>1</int> </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>3660.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>Start confirmed aggregated Sale Packing List</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?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
portal.portal_catalog.searchAndActivate(\n
portal_type=\'Sale Packing List\',\n
simulation_state=\'started\',\n
causality_state=\'solved\',\n
specialise_uid=portal.restrictedTraverse(portal.portal_preferences.getPreferredAggregatedSaleTradeCondition()).getUid(),\n
method_id=\'Delivery_deliverStartedAggregatedSalePackingList\',\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_deliverStartedAggregatedSalePackingList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -75,7 +75,7 @@ portal.portal_catalog.searchAndActivate(\n ...@@ -75,7 +75,7 @@ portal.portal_catalog.searchAndActivate(\n
simulation_state=\'confirmed\',\n simulation_state=\'confirmed\',\n
causality_state=\'solved\',\n causality_state=\'solved\',\n
specialise_uid=portal.restrictedTraverse(portal.portal_preferences.getPreferredAggregatedSaleTradeCondition()).getUid(),\n specialise_uid=portal.restrictedTraverse(portal.portal_preferences.getPreferredAggregatedSaleTradeCondition()).getUid(),\n
method_id=\'Delivery_deliverConfirmedAggregatedSalePackingList\',\n method_id=\'Delivery_startConfirmedAggregatedSalePackingList\',\n
activate_kw={\'tag\': tag},\n activate_kw={\'tag\': tag},\n
**{\'delivery.start_date\': Query(range="max",\n **{\'delivery.start_date\': Query(range="max",\n
**{\'delivery.start_date\': getAccountingDate(accounting_date)})}\n **{\'delivery.start_date\': getAccountingDate(accounting_date)})}\n
...@@ -91,7 +91,7 @@ context.activate(after_tag=tag).getId()\n ...@@ -91,7 +91,7 @@ context.activate(after_tag=tag).getId()\n
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Alarm_deliverConfirmedAggregatedSalePackingList</string> </value> <value> <string>Alarm_startConfirmedAggregatedSalePackingList</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -55,14 +55,11 @@ portal = context.getPortalObject()\n ...@@ -55,14 +55,11 @@ portal = context.getPortalObject()\n
if context.getPortalType() != \'Sale Packing List\':\n if context.getPortalType() != \'Sale Packing List\':\n
raise TypeError(\'Incorrect delivery.\')\n raise TypeError(\'Incorrect delivery.\')\n
isTransitionPossible = portal.portal_workflow.isTransitionPossible\n isTransitionPossible = portal.portal_workflow.isTransitionPossible\n
if context.getSimulationState() == \'confirmed\' \\\n if context.getSimulationState() == \'started\' \\\n
and len(context.checkConsistency()) == 0 \\\n and len(context.checkConsistency()) == 0 \\\n
and context.getCausalityState() == \'solved\' \\\n and context.getCausalityState() == \'solved\' \\\n
and context.getSpecialise() == portal.portal_preferences.getPreferredAggregatedSaleTradeCondition():\n and context.getSpecialise() == portal.portal_preferences.getPreferredAggregatedSaleTradeCondition():\n
comment = \'Delivered by alarm as all actions in confirmed state are ready.\'\n comment = \'Delivered by alarm as all actions in started state are ready.\'\n
context.edit(start_date=DateTime().earliestTime())\n
if isTransitionPossible(context, \'start\'):\n
context.start(comment=comment)\n
if isTransitionPossible(context, \'stop\'):\n if isTransitionPossible(context, \'stop\'):\n
context.stop(comment=comment)\n context.stop(comment=comment)\n
if isTransitionPossible(context, \'deliver\'):\n if isTransitionPossible(context, \'deliver\'):\n
...@@ -75,7 +72,7 @@ if context.getSimulationState() == \'confirmed\' \\\n ...@@ -75,7 +72,7 @@ if context.getSimulationState() == \'confirmed\' \\\n
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Delivery_deliverConfirmedAggregatedSalePackingList</string> </value> <value> <string>Delivery_deliverStartedAggregatedSalePackingList</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
<?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>from DateTime import DateTime\n
portal = context.getPortalObject()\n
if context.getPortalType() != \'Sale Packing List\':\n
raise TypeError(\'Incorrect delivery.\')\n
isTransitionPossible = portal.portal_workflow.isTransitionPossible\n
if context.getSimulationState() == \'confirmed\' \\\n
and len(context.checkConsistency()) == 0 \\\n
and context.getCausalityState() == \'solved\' \\\n
and context.getSpecialise() == portal.portal_preferences.getPreferredAggregatedSaleTradeCondition():\n
comment = \'Start by alarm as all actions in confirmed state are ready.\'\n
date = DateTime().earliestTime()\n
context.edit(start_date=date, stop_date=date)\n
if isTransitionPossible(context, \'start\'):\n
context.start(comment=comment)\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Delivery_startConfirmedAggregatedSalePackingList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -1642,12 +1642,12 @@ class TestSlapOSConfirmedDeliveryMixin: ...@@ -1642,12 +1642,12 @@ class TestSlapOSConfirmedDeliveryMixin:
'sale_trade_condition_module/slapos_aggregated_trade_condition', 'sale_trade_condition_module/slapos_aggregated_trade_condition',
'confirmed', True) 'confirmed', True)
class TestSlapOSDeliverConfirmedAggregatedSalePackingListAlarm( class TestSlapOSStartConfirmedAggregatedSalePackingListAlarm(
testSlapOSMixin, TestSlapOSConfirmedDeliveryMixin): testSlapOSMixin, TestSlapOSConfirmedDeliveryMixin):
destination_state = 'delivered' destination_state = 'started'
script = 'Delivery_deliverConfirmedAggregatedSalePackingList' script = 'Delivery_startConfirmedAggregatedSalePackingList'
portal_type = 'Sale Packing List' portal_type = 'Sale Packing List'
alarm = 'slapos_deliver_confirmed_aggregated_sale_packing_list' alarm = 'slapos_start_confirmed_aggregated_sale_packing_list'
def test_previous_month(self): def test_previous_month(self):
self._test('confirmed', 'solved', self._test('confirmed', 'solved',
...@@ -1687,11 +1687,109 @@ class TestSlapOSDeliverConfirmedAggregatedSalePackingListAlarm( ...@@ -1687,11 +1687,109 @@ class TestSlapOSDeliverConfirmedAggregatedSalePackingListAlarm(
) )
self.portal.portal_workflow._jumpToStateFor(delivery, 'solved') self.portal.portal_workflow._jumpToStateFor(delivery, 'solved')
self.portal.portal_workflow._jumpToStateFor(delivery, 'confirmed') self.portal.portal_workflow._jumpToStateFor(delivery, 'confirmed')
delivery.Delivery_deliverConfirmedAggregatedSalePackingList() delivery.Delivery_startConfirmedAggregatedSalePackingList()
self.assertEquals(delivery.getStartDate(), self.assertEquals(delivery.getStartDate(),
DateTime().earliestTime()) DateTime().earliestTime())
self.assertEquals(delivery.getSimulationState(), 'delivered') self.assertEquals(delivery.getStopDate(),
DateTime().earliestTime())
self.assertEquals(delivery.getSimulationState(), 'started')
class TestSlapOSDeliverStartedAggregatedSalePackingListAlarm(
testSlapOSMixin):
destination_state = 'delivered'
script = 'Delivery_deliverStartedAggregatedSalePackingList'
portal_type = 'Sale Packing List'
alarm = 'slapos_deliver_started_aggregated_sale_packing_list'
def _test(self, simulation_state, causality_state, specialise, positive,
delivery_date=DateTime('2012/04/22'),
accounting_date=DateTime('2012/04/28')):
@simulateByTitlewMark(self.script)
def _real(self, simulation_state, causality_state, specialise, positive,
delivery_date,
accounting_date):
not_visited = 'Not visited by %s' % self.script
visited = 'Visited by %s' % self.script
module = self.portal.getDefaultModule(portal_type=self.portal_type)
delivery = module.newContent(title=not_visited, start_date=delivery_date,
portal_type=self.portal_type, specialise=specialise)
_jumpToStateFor = self.portal.portal_workflow._jumpToStateFor
_jumpToStateFor(delivery, simulation_state)
_jumpToStateFor(delivery, causality_state)
self.tic()
alarm = getattr(self.portal.portal_alarms, self.alarm)
alarm.activeSense(params=dict(accounting_date=accounting_date))
self.tic()
if positive:
self.assertEqual(visited, delivery.getTitle())
else:
self.assertEqual(not_visited, delivery.getTitle())
_real(self, simulation_state, causality_state, specialise, positive,
delivery_date, accounting_date)
def test_typical(self):
self._test('started', 'solved',
'sale_trade_condition_module/slapos_aggregated_trade_condition', True)
def test_bad_specialise(self):
self._test('started', 'solved', None, False)
def test_bad_simulation_state(self):
self._test('confirmed', 'solved',
'sale_trade_condition_module/slapos_aggregated_trade_condition', False)
def test_bad_causality_state(self):
self._test('started', 'calculating',
'sale_trade_condition_module/slapos_aggregated_trade_condition', False)
@withAbort
def _test_script(self, simulation_state, causality_state, specialise,
destination_state, consistency_failure=False):
module = self.portal.getDefaultModule(portal_type=self.portal_type)
delivery = module.newContent(portal_type=self.portal_type,
specialise=specialise, start_date=DateTime())
_jumpToStateFor = self.portal.portal_workflow._jumpToStateFor
_jumpToStateFor(delivery, simulation_state)
_jumpToStateFor(delivery, causality_state)
def checkConsistency(*args, **kwargs):
if consistency_failure:
return ['bad']
else:
return []
try:
from Products.ERP5Type.Core.Folder import Folder
Folder.original_checkConsistency = Folder.checkConsistency
Folder.checkConsistency = checkConsistency
getattr(delivery, self.script)()
finally:
Folder.checkConsistency = Folder.original_checkConsistency
delattr(Folder, 'original_checkConsistency')
self.assertEqual(destination_state, delivery.getSimulationState())
def test_script_typical(self):
self._test_script('started', 'solved',
'sale_trade_condition_module/slapos_aggregated_trade_condition',
self.destination_state)
def test_script_bad_specialise(self):
self._test_script('started', 'solved', None, 'started')
def test_script_bad_simulation_state(self):
self._test_script('confirmed', 'solved',
'sale_trade_condition_module/slapos_aggregated_trade_condition',
'confirmed')
def test_script_bad_causality_state(self):
self._test_script('started', 'building',
'sale_trade_condition_module/slapos_aggregated_trade_condition',
'started')
def test_script_bad_consistency(self):
self._test_script('started', 'solved',
'sale_trade_condition_module/slapos_aggregated_trade_condition',
'started', True)
class TestSlapOSStopConfirmedAggregatedSaleInvoiceTransactionAlarm( class TestSlapOSStopConfirmedAggregatedSaleInvoiceTransactionAlarm(
testSlapOSMixin, TestSlapOSConfirmedDeliveryMixin): testSlapOSMixin, TestSlapOSConfirmedDeliveryMixin):
......
252 253
\ No newline at end of file \ No newline at end of file
...@@ -21,12 +21,13 @@ open_sale_order_module/slapos_accounting_open_sale_order_line_template/slapos_ac ...@@ -21,12 +21,13 @@ open_sale_order_module/slapos_accounting_open_sale_order_line_template/slapos_ac
open_sale_order_module/slapos_accounting_open_sale_order_template open_sale_order_module/slapos_accounting_open_sale_order_template
organisation_module/slapos organisation_module/slapos
organisation_module/slapos/bank_account organisation_module/slapos/bank_account
portal_alarms/slapos_deliver_confirmed_aggregated_sale_packing_list portal_alarms/slapos_deliver_started_aggregated_sale_packing_list
portal_alarms/slapos_instance_invoicing portal_alarms/slapos_instance_invoicing
portal_alarms/slapos_manage_building_calculating_delivery portal_alarms/slapos_manage_building_calculating_delivery
portal_alarms/slapos_reindex_open_sale_order portal_alarms/slapos_reindex_open_sale_order
portal_alarms/slapos_remove_bogus_delivery_link portal_alarms/slapos_remove_bogus_delivery_link
portal_alarms/slapos_request_update_hosting_subscription_open_sale_order portal_alarms/slapos_request_update_hosting_subscription_open_sale_order
portal_alarms/slapos_start_confirmed_aggregated_sale_packing_list
portal_alarms/slapos_stop_confirmed_aggregated_sale_invoice_transaction portal_alarms/slapos_stop_confirmed_aggregated_sale_invoice_transaction
portal_alarms/slapos_trigger_aggregated_delivery_order_builder portal_alarms/slapos_trigger_aggregated_delivery_order_builder
portal_alarms/slapos_trigger_build portal_alarms/slapos_trigger_build
......
...@@ -697,8 +697,8 @@ class TestSlapOSDefaultScenario(TestSlapOSSecurityMixin): ...@@ -697,8 +697,8 @@ class TestSlapOSDefaultScenario(TestSlapOSSecurityMixin):
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm() self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic() self.tic()
# deliver aggregated deliveries # start aggregated deliveries
self.stepCallSlaposDeliverConfirmedAggregatedSalePackingListAlarm( self.stepCallSlaposStartConfirmedAggregatedSalePackingListAlarm(
accounting_date=DateTime('2222/01/01')) accounting_date=DateTime('2222/01/01'))
self.tic() self.tic()
...@@ -706,6 +706,14 @@ class TestSlapOSDefaultScenario(TestSlapOSSecurityMixin): ...@@ -706,6 +706,14 @@ class TestSlapOSDefaultScenario(TestSlapOSSecurityMixin):
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm() self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic() self.tic()
# deliver aggregated deliveries
self.stepCallSlaposDeliverStartedAggregatedSalePackingListAlarm()
self.tic()
# stabilise aggregated deliveries and expand them
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
# build aggregated invoices # build aggregated invoices
self.stepCallSlaposTriggerBuildAlarm() self.stepCallSlaposTriggerBuildAlarm()
self.tic() self.tic()
...@@ -1044,8 +1052,8 @@ class TestSlapOSDefaultCRMEscalation(TestSlapOSSecurityMixin): ...@@ -1044,8 +1052,8 @@ class TestSlapOSDefaultCRMEscalation(TestSlapOSSecurityMixin):
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm() self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic() self.tic()
# deliver aggregated deliveries # start aggregated deliveries
self.stepCallSlaposDeliverConfirmedAggregatedSalePackingListAlarm( self.stepCallSlaposStartConfirmedAggregatedSalePackingListAlarm(
accounting_date=DateTime('2222/01/01')) accounting_date=DateTime('2222/01/01'))
self.tic() self.tic()
...@@ -1053,6 +1061,14 @@ class TestSlapOSDefaultCRMEscalation(TestSlapOSSecurityMixin): ...@@ -1053,6 +1061,14 @@ class TestSlapOSDefaultCRMEscalation(TestSlapOSSecurityMixin):
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm() self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic() self.tic()
# deliver aggregated deliveries
self.stepCallSlaposDeliverStartedAggregatedSalePackingListAlarm()
self.tic()
# stabilise aggregated deliveries and expand them
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
# build aggregated invoices # build aggregated invoices
self.stepCallSlaposTriggerBuildAlarm() self.stepCallSlaposTriggerBuildAlarm()
self.tic() self.tic()
...@@ -1160,8 +1176,8 @@ class TestSlapOSDefaultCRMEscalation(TestSlapOSSecurityMixin): ...@@ -1160,8 +1176,8 @@ class TestSlapOSDefaultCRMEscalation(TestSlapOSSecurityMixin):
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm() self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic() self.tic()
# deliver aggregated deliveries # start aggregated deliveries
self.stepCallSlaposDeliverConfirmedAggregatedSalePackingListAlarm( self.stepCallSlaposStartConfirmedAggregatedSalePackingListAlarm(
accounting_date=DateTime('2222/01/01')) accounting_date=DateTime('2222/01/01'))
self.tic() self.tic()
...@@ -1169,6 +1185,14 @@ class TestSlapOSDefaultCRMEscalation(TestSlapOSSecurityMixin): ...@@ -1169,6 +1185,14 @@ class TestSlapOSDefaultCRMEscalation(TestSlapOSSecurityMixin):
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm() self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic() self.tic()
# deliver aggregated deliveries
self.stepCallSlaposDeliverStartedAggregatedSalePackingListAlarm()
self.tic()
# stabilise aggregated deliveries and expand them
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
# build aggregated invoices # build aggregated invoices
self.stepCallSlaposTriggerBuildAlarm() self.stepCallSlaposTriggerBuildAlarm()
self.tic() self.tic()
......
166 167
\ No newline at end of file \ 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