def test_FinanceAdviceFoundInUpdateDelayAwareAdvices(self): """Check that every states of finance advice WF will be managed by @@update-delay-aware-advices.""" '''Test the finances advice workflow.''' cfg = self.meetingConfig self.changeUser('siteadmin') self._configureFinancesAdvice(cfg) # put users in finances group self._setupFinancesGroup() update_advices_view = self.portal.restrictedTraverse( '@@update-delay-aware-advices') self.changeUser('pmCreator1') item = self.create('MeetingItem', title='The first item') # ask finances advice item.setOptionalAdvisers( ('{0}__rowid__unique_id_002'.format(finance_group_uid()), )) self.proposeItem(item) self.changeUser('pmReviewer1') self.do(item, 'wait_advices_from_prevalidated') # now act as the finances users self.changeUser('pmFinController') # set completeness item.setCompleteness('completeness_complete') item.at_post_edit_script() # add advice advice = createContentInContainer( item, 'meetingadvicefinances', **{ 'advice_group': finance_group_uid(), 'advice_type': u'negative_finance', 'advice_comment': RichTextValue(u'My comment finances'), 'advice_category': u'acquisitions' }) query = update_advices_view._computeQuery() catalog = api.portal.get_tool('portal_catalog') self.assertTrue( item.UID() in [brain.UID for brain in catalog(**query)]) # send advice to finances editor self.do(advice, 'proposeToFinancialEditor') self.assertTrue( item.UID() in [brain.UID for brain in catalog(**query)]) # send advice to finances reviewer self.changeUser('pmFinEditor') self.do(advice, 'proposeToFinancialReviewer') self.assertTrue( item.UID() in [brain.UID for brain in catalog(**query)]) # send advice to finances Manager self.changeUser('pmFinReviewer') self.do(advice, 'proposeToFinancialManager') self.assertTrue( item.UID() in [brain.UID for brain in catalog(**query)]) # sign the advice self.changeUser('pmFinManager') self.do(advice, 'signFinancialAdvice') # no more found now that advice is signed, at has been moved to 'advice_given' self.assertTrue(advice.queryState() in ADVICE_STATES_ENDED) self.assertFalse( item.UID() in [brain.UID for brain in catalog(**query)])
def _add_finance_advice(item, newItem, last_transition): if item['toDiscuss'] and \ item['category'] in ['affaires-juridiques', 'remboursement'] and \ last_transition == 'prevalidate': finance_group = finance_group_uid() if finance_group: finance_advice_id = '{0}__rowid__unique_id_002'.format( finance_group) optional_advisers = get_vocab( newItem, 'Products.PloneMeeting.vocabularies.itemoptionaladvicesvocabulary' ) if finance_advice_id not in optional_advisers: _memos.clear() finance_group = finance_group_uid() finance_advice_id = '{0}__rowid__unique_id_002'.format( finance_group) newItem.setOptionalAdvisers((finance_advice_id, )) newItem.updateLocalRoles() wfTool.doActionFor(newItem, 'wait_advices_from_prevalidated') newItem.setCompleteness('completeness_complete') newItem.updateLocalRoles() with api.env.adopt_user('dfin'): advice = createContentInContainer( newItem, 'meetingadvicefinances', **{ 'advice_group': finance_group, 'advice_type': u'positive_finance', 'advice_comment': RichTextValue(u'Mon commentaire') }) wfTool.doActionFor(advice, 'proposeToFinancialEditor') wfTool.doActionFor(advice, 'proposeToFinancialReviewer') wfTool.doActionFor(advice, 'proposeToFinancialManager') wfTool.doActionFor(advice, 'signFinancialAdvice') return True return False
def showFinancesAdvice(self, advice_data=None): """Finances advice is only shown : - if given (at worst it will be 'not_given_finance'); - if advice_type is not not_required_finance; - in any case if item is in Council; - if item is 'validated' to everybody; - if it is 'prevalidated_waiting_advices', to finances advisers.""" adviceHolder = self._advice_holder(advice_data) adviceObj = adviceHolder.getAdviceObj(finance_group_uid()) # check advice state if not adviceObj or adviceObj.advice_type == 'not_required_finance': return False # check item state item_state = self.real_context.queryState() if item_state == 'validated' or adviceHolder.hasMeeting(): return True # check user access (administrators and advisers from finance director service) tool = api.portal.get_tool('portal_plonemeeting') if tool.isManager(self.real_context, realManagers=True) \ or (item_state == 'prevalidated_waiting_advices' and tool.get_orgs_for_user(suffixes=['advisers'], using_groups=[finance_group_uid()], the_objects=False)): return True return False
def test_AdviceCategoryCorrectlyIndexed(self): """When an advice_category is defined, it is correctly indexed it's parent after add/modify/delete.""" catalog = self.portal.portal_catalog cfg = self.meetingConfig self.changeUser('siteadmin') self._configureFinancesAdvice(cfg) # put users in finances group self._setupFinancesGroup() self.changeUser('pmCreator1') item = self.create('MeetingItem', title='The first item') # ask finances advice item.setOptionalAdvisers( ('{0}__rowid__unique_id_002'.format(finance_group_uid()), )) self.proposeItem(item) self.changeUser('pmReviewer1') self.do(item, 'wait_advices_from_prevalidated') # now act as the finances users # advice may be added/edit when item is considered 'complete' self.changeUser('pmFinController') changeCompleteness = item.restrictedTraverse( '@@change-item-completeness') self.request.set('new_completeness_value', 'completeness_complete') self.request.form['form.submitted'] = True changeCompleteness() # add advice advice = createContentInContainer( item, 'meetingadvicefinances', **{ 'advice_group': finance_group_uid(), 'advice_type': u'positive_finance', 'advice_comment': RichTextValue(u'My comment finances'), 'advice_category': u'acquisitions' }) # reindexed when advice added itemUID = item.UID() self.assertEqual( catalog(meta_type='MeetingItem', financesAdviceCategory=advice.advice_category)[0].UID, itemUID) # reindexed when advice edited advice.advice_category = u'attributions' # notify modified notify(ObjectModifiedEvent(advice)) self.assertEqual( catalog(meta_type='MeetingItem', financesAdviceCategory=advice.advice_category)[0].UID, itemUID) # reindexed when advice deleted self.portal.restrictedTraverse('@@delete_givenuid')(advice.UID()) self.assertEqual( len( catalog(meta_type='MeetingItem', financesAdviceCategory=advice.advice_category)), 0)
def test_CollegePostPoneNextMeetingWithGivenAdvices(self): '''Check that postpone_next_meeting will work, especially because if finances advice is asked, it must be mandatorily given for the item to be validated.''' cfg = self.meetingConfig self.changeUser('siteadmin') self._configureFinancesAdvice(cfg) self.changeUser('pmCreator1') item = self.create('MeetingItem', title='The first item') # ask finances advice item.setOptionalAdvisers( ('{0}__rowid__unique_id_002'.format(finance_group_uid()), )) item.at_post_edit_script() self.proposeItem(item) # finances advice self.changeUser('pmReviewer1') self.do(item, 'wait_advices_from_prevalidated') self.changeUser('pmFinController') item.setCompleteness('completeness_complete') item._update_after_edit() # give advice positive with remarks # finances advice WF is tested in test_CollegeFinancesAdviceWF advice = createContentInContainer( item, 'meetingadvicefinances', **{ 'advice_group': finance_group_uid(), 'advice_type': u'positive_finance', 'advice_comment': RichTextValue(u'My comment finances'), 'advice_category': u'acquisitions' }) self.do(advice, 'proposeToFinancialEditor') self.changeUser('pmFinEditor') self.do(advice, 'proposeToFinancialReviewer') self.changeUser('pmFinReviewer') self.do(advice, 'proposeToFinancialManager') self.changeUser('pmFinManager') self.do(advice, 'signFinancialAdvice') # item was automatically validated, present it into a meeting self.changeUser('pmManager') meeting = self.create('Meeting', date=DateTime('2016/12/11')) self.presentItem(item) self.decideMeeting(meeting) self.do(item, 'postpone_next_meeting') # item was postponed, cloned item is validated and advices are inherited self.assertEqual(item.queryState(), 'postponed_next_meeting') cloneItem = item.getBRefs('ItemPredecessor')[0] for adviceInfo in cloneItem.adviceIndex.values(): self.assertTrue(adviceInfo['inherited'])
def onAdvicesUpdated(item, event): ''' If item is 'backToProposedToInternalReviewer', we need to reinitialize finances advice delay. ''' for groupId, adviceInfo in item.adviceIndex.items(): # special behaviour for finances advice if groupId != finance_group_uid(): continue # when a finance advice is just timed out, we will send the item back to the refadmin if adviceInfo['delay_infos']['delay_status'] == 'timed_out' and \ 'delay_infos' in event.old_adviceIndex[groupId] and not \ event.old_adviceIndex[groupId]['delay_infos']['delay_status'] == 'timed_out': if item.queryState() == 'prevalidated_waiting_advices': wfTool = api.portal.get_tool('portal_workflow') item.adviceIndex[groupId]['delay_stopped_on'] = datetime.now() item.REQUEST.set( 'maybackTo_proposed_to_refadmin_from_waiting_advices', True) wfTool.doActionFor( item, 'backTo_proposed_to_refadmin_from_waiting_advices', comment='item_wf_changed_finance_advice_timed_out') item.REQUEST.set( 'maybackTo_proposed_to_refadmin_from_waiting_advices', False)
def _configureFinancesAdvice(self, cfg): """ """ # add finances group self._createFinancesGroup() # put users in finances group self._setupFinancesGroup() # configure customAdvisers for 'meeting-config-college' # turn FINANCE_GROUP_ID into relevant org UID customAdvisers = deepcopy(charleroi_import_data.collegeMeeting.customAdvisers) for customAdviser in customAdvisers: customAdviser['org'] = finance_group_uid() cfg.setCustomAdvisers(customAdvisers) cfg.setTransitionsReinitializingDelays( charleroi_import_data.collegeMeeting.transitionsReinitializingDelays) # configure usedAdviceTypes cfg.setUsedAdviceTypes(('asked_again', 'positive', 'positive_with_remarks', 'negative', 'nil', 'positive_finance', 'positive_with_remarks_finance', 'negative_finance', 'not_given_finance')) # finances advice can be given when item in state 'prevalidated_waiting_advices' cfg.setKeepAccessToItemWhenAdviceIsGiven(True)
def financesAdviceCategory(item): """ Indexes the 'advice_category' field defined on the contained 'meetingadvicefinances'. """ # finance group could not be present at portal creation (necessary in tests) advice = item.getAdviceObj(finance_group_uid()) if advice and advice.advice_category: return advice.advice_category return _marker
def _setItemToWaitingAdvices(self, item, transition=None): """We need to ask finances advice to be able to do the transition.""" originalMember = self.member.getId() self.changeUser('siteadmin') self._configureFinancesAdvice(self.meetingConfig) self.changeUser(originalMember) item.setOptionalAdvisers(item.getOptionalAdvisers() + ( '{0}__rowid__unique_id_002'.format(finance_group_uid()), )) item.at_post_edit_script() if transition: self.do(item, transition)
def _setupFinancesGroup(self): '''Configure finances group.''' groupsTool = api.portal.get_tool('portal_groups') # add finances users to relevant groups # _advisers groupsTool.addPrincipalToGroup('pmFinController', '{0}_advisers'.format(finance_group_uid())) groupsTool.addPrincipalToGroup('pmFinEditor', '{0}_advisers'.format(finance_group_uid())) groupsTool.addPrincipalToGroup('pmFinReviewer', '{0}_advisers'.format(finance_group_uid())) groupsTool.addPrincipalToGroup('pmFinManager', '{0}_advisers'.format(finance_group_uid())) groupsTool.addPrincipalToGroup('dfin', '{0}_advisers'.format(finance_group_uid())) # respective _financesXXX groups groupsTool.addPrincipalToGroup('pmFinController', '{0}_financialcontrollers'.format(finance_group_uid())) groupsTool.addPrincipalToGroup('pmFinEditor', '{0}_financialeditors'.format(finance_group_uid())) groupsTool.addPrincipalToGroup('pmFinReviewer', '{0}_financialreviewers'.format(finance_group_uid())) groupsTool.addPrincipalToGroup('pmFinManager', '{0}_financialmanagers'.format(finance_group_uid())) # dfin is member of every finances groups groupsTool.addPrincipalToGroup('dfin', '{0}_financialcontrollers'.format(finance_group_uid())) groupsTool.addPrincipalToGroup('dfin', '{0}_financialeditors'.format(finance_group_uid())) groupsTool.addPrincipalToGroup('dfin', '{0}_financialreviewers'.format(finance_group_uid())) groupsTool.addPrincipalToGroup('dfin', '{0}_financialmanagers'.format(finance_group_uid()))
def _financeAdviceData(self, advice_data=None): """ """ # if item is in Council, get the adviceData from it's predecessor if not advice_data: advice_data = self.real_context.getAdviceDataFor( self.real_context, finance_group_uid()) adviceTypeTranslated = advice_data['type_translated'].replace( 'Avis finances ', '') advice_data['advice_type_translated'] = adviceTypeTranslated return advice_data
def printFinancesAdvice(self): """Print the legal text regarding Finances advice.""" advice_data = self.real_context.getAdviceDataFor( self.real_context, finance_group_uid()) if not self.showFinancesAdvice(advice_data=advice_data): return '' adviceData = self._financeAdviceData(advice_data=advice_data) delayStartedOnLocalized = adviceData['delay_infos'][ 'delay_started_on_localized'] adviceGivenOnLocalized = adviceData['advice_given_on_localized'] adviceTypeTranslated = safe_unicode( adviceData['advice_type_translated']) return FIN_ADVICE_LINE1.format(delayStartedOnLocalized) + \ FIN_ADVICE_LINE2.format(adviceTypeTranslated, adviceGivenOnLocalized)
def test_FinancesAdviserOnlyMayEvaluateCompleteness(self): '''Only finances adviser may evaluate completeness when item is 'waiting_advices'.''' self.changeUser('admin') self._configureFinancesAdvice(self.meetingConfig) # completeness widget is disabled for items of the config cfg = self.meetingConfig self.changeUser('siteadmin') recurringItem = cfg.getRecurringItems()[0] templateItem = cfg.getItemTemplates(as_brains=False)[0] self.assertFalse(recurringItem.adapted().mayEvaluateCompleteness()) self.assertFalse(templateItem.adapted().mayEvaluateCompleteness()) self.assertFalse(recurringItem.adapted().mayAskCompletenessEvalAgain()) self.assertFalse(templateItem.adapted().mayAskCompletenessEvalAgain()) # creator may not evaluate self.changeUser('pmCreator1') item = self.create('MeetingItem') self.assertTrue( item.getCompleteness() == 'completeness_not_yet_evaluated') self.assertTrue(self.hasPermission(ModifyPortalContent, item)) self.assertFalse(item.adapted().mayEvaluateCompleteness()) # reviewer may not evaluate self.proposeItem(item) self.changeUser('pmReviewer1') self.assertTrue(self.hasPermission(ModifyPortalContent, item)) self.assertFalse(item.adapted().mayEvaluateCompleteness()) # not sendable to 'waiting_advices' if finances advice not asked self.assertFalse('wait_advices' in self.transitions(item)) item.setOptionalAdvisers( ('{0}__rowid__unique_id_002'.format(finance_group_uid()), )) item.at_post_edit_script() self.do(item, 'wait_advices_from_prevalidated') self.assertFalse(item.adapted().mayEvaluateCompleteness()) # finances controller is able to evaluate self.changeUser('pmFinController') self.assertTrue(self.hasPermission(View, item)) self.assertTrue(item.adapted().mayEvaluateCompleteness()) itemCompletenessView = item.restrictedTraverse('item-completeness') # and even able to change it self.assertTrue(itemCompletenessView.listSelectableCompleteness())
def test_MayChangeDelayTo(self): """Method MeetingItem.mayChangeDelayTo is made to control the 'available_on' of customAdvisers used for finance advice (5, 10 or 20 days). - 10 days is the only advice selectable thru the item edit form; - the delay '5' must be changed using the change delay widdget; - the delay '20' is reserved to finance advisers.""" cfg = self.meetingConfig self.changeUser('siteadmin') self._configureFinancesAdvice(cfg) # put users in finances group self._setupFinancesGroup() self.changeUser('pmCreator1') item = self.create('MeetingItem') # by default, only the 10 days delay is selectable optional_advisers = get_vocab( item, 'Products.PloneMeeting.vocabularies.itemoptionaladvicesvocabulary') self.assertEqual([term.token for term in optional_advisers], [ 'not_selectable_value_delay_aware_optional_advisers', '{0}__rowid__unique_id_002'.format(finance_group_uid()), 'not_selectable_value_non_delay_aware_optional_advisers', self.developers_uid, self.vendors_uid ]) # select the 10 days delay item.setOptionalAdvisers( ('%s__rowid__unique_id_002' % finance_group_uid(), )) item.at_post_edit_script() self.assertEqual(item.adviceIndex[finance_group_uid()]['delay'], '10') # Managers, are also required to use change delay widget for 5/20 delays self.changeUser('pmManager') self.assertFalse(item.adapted().mayChangeDelayTo(5)) self.assertTrue(item.adapted().mayChangeDelayTo(10)) self.assertFalse(item.adapted().mayChangeDelayTo(20)) # user having 'Modify portal content' may select 10 but not others self.changeUser('pmCreator1') self.assertFalse(item.adapted().mayChangeDelayTo(5)) self.assertTrue(item.adapted().mayChangeDelayTo(10)) self.assertFalse(item.adapted().mayChangeDelayTo(20)) # may select 5 if using change delay widget # aka 'managing_available_delays' is found in the REQUEST self.request.set('managing_available_delays', True) self.assertTrue(item.adapted().mayChangeDelayTo(5)) self.assertTrue(item.adapted().mayChangeDelayTo(10)) self.assertFalse(item.adapted().mayChangeDelayTo(20)) # change to 5 days item.setOptionalAdvisers( ('{0}__rowid__unique_id_003'.format(finance_group_uid()), )) item.at_post_edit_script() self.assertEqual(item.adviceIndex[finance_group_uid()]['delay'], '5') # could back to 10 days self.assertTrue(item.adapted().mayChangeDelayTo(5)) self.assertTrue(item.adapted().mayChangeDelayTo(10)) self.assertFalse(item.adapted().mayChangeDelayTo(20)) # Managers have bypass when using change delay widget self.changeUser('pmManager') self.assertTrue(item.adapted().mayChangeDelayTo(5)) self.assertTrue(item.adapted().mayChangeDelayTo(10)) self.assertTrue(item.adapted().mayChangeDelayTo(20)) # now, when item is 'wait_advices_from_prevalidated', finance advisers # may select the 20 days delay self.changeUser('pmReviewer1') self.proposeItem(item) self.do(item, 'wait_advices_from_prevalidated') self.changeUser('pmFinController') # may change to 20 self.assertFalse(item.adapted().mayChangeDelayTo(5)) self.assertFalse(item.adapted().mayChangeDelayTo(10)) self.assertTrue(item.adapted().mayChangeDelayTo(20)) # change to 20 days item.setOptionalAdvisers( ('{0}__rowid__unique_id_004'.format(finance_group_uid()), )) item.at_post_edit_script() self.assertEqual(item.adviceIndex[finance_group_uid()]['delay'], '20') # once to 20, may back to 10 self.assertFalse(item.adapted().mayChangeDelayTo(5)) self.assertTrue(item.adapted().mayChangeDelayTo(10)) self.assertTrue(item.adapted().mayChangeDelayTo(20)) # if item no more waiting finances advice, finances may not # change delay anymore self.do(item, 'backTo_proposed_to_refadmin_from_waiting_advices') self.assertFalse(item.adapted().mayChangeDelayTo(5)) self.assertFalse(item.adapted().mayChangeDelayTo(10)) self.assertFalse(item.adapted().mayChangeDelayTo(20)) # if advice delay is set to 20, user have edit rights may not change it anymore self.changeUser('pmRefAdmin1') self.assertFalse(item.adapted().mayChangeDelayTo(5)) self.assertFalse(item.adapted().mayChangeDelayTo(10)) self.assertFalse(item.adapted().mayChangeDelayTo(20)) # only a Manager will be able to change that delay now self.changeUser('pmManager') self.assertTrue(item.adapted().mayChangeDelayTo(5)) self.assertTrue(item.adapted().mayChangeDelayTo(10)) self.assertTrue(item.adapted().mayChangeDelayTo(20))
def test_CollegeProcessWithFinancesAdvice(self): '''How does the process behave when the 'finances' advice is asked.''' cfg = self.meetingConfig self.changeUser('siteadmin') self._configureFinancesAdvice(cfg) self.changeUser('pmCreator1') item = self.create('MeetingItem', title='The first item') # ask finances advice item.setOptionalAdvisers( ('{0}__rowid__unique_id_002'.format(finance_group_uid()), )) item.at_post_edit_script() # not askable for now self.assertEqual(self.transitions(item), [ 'propose', ]) # only directors may ask finances advice, item must be 'prevalidated' self.assertEqual(self.transitions(item), ['propose']) self.do(item, 'propose') self.assertFalse(self.transitions(item)) self.changeUser('pmServiceHead1') self.assertEqual(self.transitions(item), ['backToItemCreated', 'proposeToRefAdmin']) self.do(item, 'proposeToRefAdmin') self.assertFalse(self.transitions(item)) self.changeUser('pmRefAdmin1') self.assertEqual(self.transitions(item), ['backToProposed', 'prevalidate']) self.do(item, 'prevalidate') self.assertFalse(self.transitions(item)) self.changeUser('pmReviewer1') # may only validate if no finances advice or finances advice is given self.assertEqual(self.transitions(item), [ 'backToItemCreatedFromPrevalidated', 'backToProposedFromPrevalidated', 'backToProposedToRefAdmin', 'wait_advices_from_prevalidated' ]) # remove fact that finances advice was asked item.setOptionalAdvisers(()) self.assertEqual(self.transitions(item), [ 'backToItemCreatedFromPrevalidated', 'backToProposedFromPrevalidated', 'backToProposedToRefAdmin', 'wait_advices_from_prevalidated' ]) item.setOptionalAdvisers( ('{0}__rowid__unique_id_002'.format(finance_group_uid()), )) # ask finances advice self.do(item, 'wait_advices_from_prevalidated') # when item is sent to finances, even the reviewer may not change it's state self.assertFalse(self.transitions(item)) # item may be returned to RefAdmin if completeness not evaluated/incomplete self.changeUser('pmFinController') self.assertEqual(item.getCompleteness(), 'completeness_not_yet_evaluated') self.assertEqual(self.transitions(item), ['backTo_proposed_to_refadmin_from_waiting_advices']) changeCompleteness = item.restrictedTraverse( '@@change-item-completeness') self.request.set('new_completeness_value', 'completeness_incomplete') self.request.form['form.submitted'] = True changeCompleteness() self.assertEqual(self.transitions(item), ['backTo_proposed_to_refadmin_from_waiting_advices']) # once 'complete' item may not be returned to refAdmin anymore self.request.set('new_completeness_value', 'completeness_complete') changeCompleteness() self.assertEqual(self.transitions(item), []) # give advice positive with remarks # finances advice WF is tested in test_CollegeFinancesAdviceWF self.changeUser('pmFinController') advice = createContentInContainer( item, 'meetingadvicefinances', **{ 'advice_group': finance_group_uid(), 'advice_type': u'negative_finance', 'advice_comment': RichTextValue(u'My comment finances'), 'advice_category': u'acquisitions' }) # MeetingManager may not change item state when sent to finance, but Manager may self.do(advice, 'proposeToFinancialEditor') self.changeUser('pmManager') self.assertFalse(self.transitions(item)) self.changeUser('siteadmin') self.assertEqual(self.transitions(item), ['backTo_prevalidated_from_waiting_advices']) self.changeUser('pmFinEditor') self.do(advice, 'proposeToFinancialReviewer') self.changeUser('pmManager') self.assertFalse(self.transitions(item)) self.changeUser('siteadmin') self.assertEqual(self.transitions(item), ['backTo_prevalidated_from_waiting_advices']) self.changeUser('pmFinReviewer') self.do(advice, 'proposeToFinancialManager') self.changeUser('pmManager') self.assertFalse(self.transitions(item)) self.changeUser('siteadmin') self.assertEqual(self.transitions(item), ['backTo_prevalidated_from_waiting_advices']) self.changeUser('pmFinManager') self.do(advice, 'signFinancialAdvice') # item was sent back to administrative referent self.assertEqual(item.queryState(), 'proposed_to_refadmin') # now if it goes to director, director is able to validate the item self.changeUser('pmRefAdmin1') self.do(item, 'prevalidate') self.changeUser('pmReviewer1') # now item may be validated but finances advice may not be asked # anymore as it was already given self.assertEqual(self.transitions(item), [ 'backToItemCreatedFromPrevalidated', 'backToProposedFromPrevalidated', 'backToProposedToRefAdmin', 'validate' ]) # if finances advice is 'asked_again', it is giveable again changeView = advice.restrictedTraverse('@@change-advice-asked-again') changeView() self.assertEqual(advice.advice_type, 'asked_again') self.assertEqual(self.transitions(item), [ 'backToItemCreatedFromPrevalidated', 'backToProposedFromPrevalidated', 'backToProposedToRefAdmin', 'wait_advices_from_prevalidated' ])
def onAdviceAfterTransition(advice, event): '''Called whenever a transition has been fired on an advice.''' if advice != event.object: return # pass if we are pasting items as advices are not kept if advice.REQUEST.get('currentlyPastingItems', False): return # manage finance workflow, just consider relevant transitions # if it is not a finance wf transition, return if advice.advice_group != finance_group_uid(): return item = advice.getParentNode() itemState = item.queryState() oldStateId = event.old_state.id newStateId = event.new_state.id # initial_state or going back from 'advice_given', we set automatically # advice_hide_during_redaction to True if not event.transition or \ newStateId == 'proposed_to_financial_controller' and oldStateId == 'advice_given': advice.advice_hide_during_redaction = True if newStateId == 'financial_advice_signed': plone_utils = api.portal.get_tool('plone_utils') # final state of the wf, make sure advice is no more hidden during redaction advice.advice_hide_during_redaction = False # if item was still in state 'prevalidated_waiting_advices', # it is automatically validated if advice is 'positive_finance' # otherwise it is sent back to the refadmin if itemState == 'prevalidated_waiting_advices': wfTool = api.portal.get_tool('portal_workflow') if advice.advice_type == 'positive_finance': item.REQUEST.set('mayValidate', True) wfTool.doActionFor( item, 'backTo_validated_from_waiting_advices', comment='item_wf_changed_finance_advice_positive') item.REQUEST.set('mayValidate', False) msg = _AP('backTo_validated_from_waiting_advices_done_descr') else: item.REQUEST.set( 'maybackTo_proposed_to_refadmin_from_waiting_advices', True) wfTool.doActionFor( item, 'backTo_proposed_to_refadmin_from_waiting_advices', comment='item_wf_changed_finance_advice_not_positive') item.REQUEST.set( 'maybackTo_proposed_to_refadmin_from_waiting_advices', False) sendMailIfRelevant( item, 'sentBackToRefAdminWhileSigningNotPositiveFinancesAdvice', 'MeetingReviewer', isRole=True) msg = _AP( 'backTo_proposed_to_refadmin_from_waiting_advices_done_descr' ) plone_utils.addPortalMessage(msg) # in some corner case, we could be here and we are actually already updating advices, # this is the case if we validate an item and it triggers the fact that advice delay is exceeded # this should never be the case as advice delay should have been updated during nightly cron... # but if we are in a '_updateAdvices', do not _updateAdvices again... # also bypass if we are creating the advice as onAdviceAdded is called after onAdviceTransition if event.transition and not item.REQUEST.get('currentlyUpdatingAdvice', False): item.updateLocalRoles() return
def test_CollegeFinancesAdviceWF(self): '''Test the finances advice workflow.''' cfg = self.meetingConfig self.changeUser('siteadmin') self._configureFinancesAdvice(cfg) # put users in finances group self._setupFinancesGroup() self.changeUser('pmCreator1') item = self.create('MeetingItem', title='The first item') # ask finances advice item.setOptionalAdvisers( ('{0}__rowid__unique_id_002'.format(finance_group_uid()), )) self.proposeItem(item) self.changeUser('pmReviewer1') self.do(item, 'wait_advices_from_prevalidated') # now act as the finances users # advice may be added/edit when item is considered 'complete' self.changeUser('pmFinController') self.assertEqual(self.transitions(item), ['backTo_proposed_to_refadmin_from_waiting_advices']) toAdd, toEdit = item.getAdvicesGroupsInfosForUser() self.assertFalse(toAdd or toEdit) # set item as "incomplete" using itemcompleteness view # this way, it checks that current user may actually evaluate completeness # and item is updated (at_post_edit_script is called) self.assertEqual(item.getCompleteness(), 'completeness_not_yet_evaluated') changeCompleteness = item.restrictedTraverse( '@@change-item-completeness') self.request.set('new_completeness_value', 'completeness_incomplete') self.request.form['form.submitted'] = True changeCompleteness() self.assertEqual(item.getCompleteness(), 'completeness_incomplete') # can be sent back even if considered incomplete self.assertEqual(self.transitions(item), ['backTo_proposed_to_refadmin_from_waiting_advices']) toAdd, toEdit = item.getAdvicesGroupsInfosForUser() self.assertFalse(toAdd or toEdit) # back to refadmin self.do(item, 'backTo_proposed_to_refadmin_from_waiting_advices') # now do item complete self.changeUser('pmRefAdmin1') self.do(item, 'prevalidate') self.changeUser('pmReviewer1') self.do(item, 'wait_advices_from_prevalidated') self.changeUser('pmFinController') # delay is not started self.assertIsNone( item.adviceIndex[finance_group_uid()]['delay_started_on']) self.assertEqual(item.getCompleteness(), 'completeness_evaluation_asked_again') self.request.set('new_completeness_value', 'completeness_complete') changeCompleteness() self.assertEqual(item.getCompleteness(), 'completeness_complete') self.assertTrue( item.adviceIndex[finance_group_uid()]['delay_started_on']) # advice may be added toAdd, toEdit = item.getAdvicesGroupsInfosForUser() self.assertEqual(toAdd, [(finance_group_uid(), u'Directeur Financier')]) self.assertFalse(toEdit) # add advice advice = createContentInContainer( item, 'meetingadvicefinances', **{ 'advice_group': finance_group_uid(), 'advice_type': u'negative_finance', 'advice_comment': RichTextValue(u'My comment finances'), 'advice_category': u'acquisitions' }) # a meetingadvicefinances will be automatically hidden during redaction self.assertFalse(cfg.getDefaultAdviceHiddenDuringRedaction()) self.assertTrue(advice.advice_hide_during_redaction) # editable self.assertTrue(item.adapted()._adviceIsEditableByCurrentUser( finance_group_uid())) # send advice to finances editor self.assertEqual(self.transitions(advice), ['proposeToFinancialEditor']) self.do(advice, 'proposeToFinancialEditor') # send advice to finances reviewer self.changeUser('pmFinEditor') # editable self.assertTrue(item.adapted()._adviceIsEditableByCurrentUser( finance_group_uid())) self.assertEqual(self.transitions(advice), [ 'backToProposedToFinancialController', 'proposeToFinancialReviewer' ]) self.do(advice, 'proposeToFinancialReviewer') # send advice to finances Manager self.changeUser('pmFinReviewer') # editable self.assertTrue(item.adapted()._adviceIsEditableByCurrentUser( finance_group_uid())) self.assertEqual(self.transitions(advice), [ 'backToProposedToFinancialController', 'backToProposedToFinancialEditor', 'proposeToFinancialManager' ]) self.do(advice, 'proposeToFinancialManager') # sign the advice self.changeUser('pmFinManager') # editable self.assertTrue(item.adapted()._adviceIsEditableByCurrentUser( finance_group_uid())) self.assertEqual(self.transitions(advice), [ 'backToProposedToFinancialController', 'backToProposedToFinancialReviewer', 'signFinancialAdvice' ]) # sign the advice, as it is 'negative_finance', aka not 'positive_finances' # it will be automatically sent back to the refadmin self.do(advice, 'signFinancialAdvice') self.assertEqual(item.queryState(), 'proposed_to_refadmin') # advice was automatically shown self.assertFalse(advice.advice_hide_during_redaction) # sign the advice as 'positive_finance' it will be automatically 'validated' self.changeUser('pmReviewer1') self.do(item, 'prevalidate') # as advice was already given, user needs to ask advice again self.assertFalse( 'wait_advices_from_prevalidated' in self.transitions(item)) changeView = advice.restrictedTraverse('@@change-advice-asked-again') changeView() self.assertEqual(advice.advice_type, 'asked_again') self.do(item, 'wait_advices_from_prevalidated') self.changeUser('pmFinController') # advice was automatically hidden self.assertTrue(advice.advice_hide_during_redaction) # delay is not started self.assertIsNone( item.adviceIndex[finance_group_uid()]['delay_started_on']) self.assertEqual(item.getCompleteness(), 'completeness_evaluation_asked_again') self.request.set('new_completeness_value', 'completeness_complete') changeCompleteness() self.assertTrue( item.adviceIndex[finance_group_uid()]['delay_started_on']) self.do(advice, 'proposeToFinancialEditor') self.changeUser('pmFinEditor') self.do(advice, 'proposeToFinancialReviewer') self.changeUser('pmFinReviewer') # advice may not be sent to financial manager if it is still asked_again # change advice_type to 'positive_finance' self.assertFalse( 'proposeToFinancialManager' in self.transitions(advice)) advice.advice_type = u'positive_finance' self.assertTrue( 'proposeToFinancialManager' in self.transitions(advice)) self.do(advice, 'proposeToFinancialManager') self.changeUser('pmFinManager') self.do(advice, 'signFinancialAdvice') self.assertEqual(item.queryState(), 'validated') # advice was automatically shown self.assertFalse(advice.advice_hide_during_redaction) # advice is no more editable/deletable by finances self.assertFalse(self.hasPermission(ModifyPortalContent, advice)) self.assertFalse(self.hasPermission(DeleteObjects, advice))
def test_ItemWithTimedOutAdviceIsAutomaticallySentBackToRefAdmin(self): '''When an item is 'prevalidated_waiting_advices', if delay is exceeded the 'backTo_proposed_to_refadmin_from_waiting_advices' is automatically triggered. If advice was already given but still not signed, 'advice_hide_during_redaction' is set to True so advice is considered 'not_given' as if it was never added.''' cfg = self.meetingConfig self.changeUser('siteadmin') self._configureFinancesAdvice(cfg) # first case, delay exceeded and advice was never given, the item is sent back to refadmin self.changeUser('pmCreator1') item = self.create('MeetingItem', title='The first item') # ask finances advice item.setOptionalAdvisers( ('{0}__rowid__unique_id_002'.format(finance_group_uid()), )) item.at_post_edit_script() self.proposeItem(item) # finances advice self.changeUser('pmReviewer1') self.do(item, 'wait_advices_from_prevalidated') self.changeUser('pmFinController') item.setCompleteness('completeness_complete') item.updateLocalRoles() # now does advice timed out item.adviceIndex[ finance_group_uid()]['delay_started_on'] = datetime.datetime( 2014, 1, 1) item.updateLocalRoles() # advice is timed out self.assertEqual( item.adviceIndex[finance_group_uid()]['delay_infos'] ['delay_status'], 'no_more_giveable') # item has been automatically sent back to refadmin self.assertEqual(item.queryState(), 'proposed_to_refadmin') # advice delay is kept self.assertEqual( item.adviceIndex[finance_group_uid()]['delay_started_on'], datetime.datetime(2014, 1, 1)) # second case, delay exceeded and advice exists but not signed # 'advice_hide_during_redaction' is set to True on the advice # and item is sent back to refadmin self.changeUser('pmCreator1') item = self.create('MeetingItem', title='The second item') # ask finances advice item.setOptionalAdvisers( ('{0}__rowid__unique_id_002'.format(finance_group_uid()), )) item.at_post_edit_script() self.proposeItem(item) # finances advice self.changeUser('pmReviewer1') self.do(item, 'wait_advices_from_prevalidated') self.changeUser('pmFinController') item.setCompleteness('completeness_complete') item._update_after_edit() # give advice positive with remarks advice = createContentInContainer( item, 'meetingadvicefinances', **{ 'advice_group': finance_group_uid(), 'advice_type': u'positive_finance', 'advice_comment': RichTextValue(u'My comment finances'), 'advice_category': u'acquisitions' }) self.do(advice, 'proposeToFinancialEditor') self.assertTrue( item.adviceIndex[finance_group_uid()]['hidden_during_redaction']) # now does advice timed out item.adviceIndex[ finance_group_uid()]['delay_started_on'] = datetime.datetime( 2014, 1, 1) item.updateLocalRoles() # advice is timed out self.assertEqual( item.adviceIndex[finance_group_uid()]['delay_infos'] ['delay_status'], 'no_more_giveable') # item has been automatically sent back to refadmin self.assertTrue(item.queryState() == 'proposed_to_refadmin') # advice is still 'hidden_during_redaction' self.assertTrue( item.adviceIndex[finance_group_uid()]['hidden_during_redaction'])
def _advice_holder(self, advice_data=None): if not advice_data: advice_data = self.real_context.getAdviceDataFor( self.real_context, finance_group_uid()) return advice_data.get('adviceHolder', self.real_context)