Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue-2204: Conditional Email Notification on AR retract #2205

Merged
merged 6 commits into from
Sep 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 102 additions & 97 deletions bika/lims/browser/analysisrequest/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,41 @@
# Copyright 2011-2017 by it's authors.
# Some rights reserved. See LICENSE.txt, AUTHORS.txt.

from bika.lims import bikaMessageFactory as _
from bika.lims.utils import t
from bika.lims import PMF
from bika.lims.browser.bika_listing import WorkflowAction
from bika.lims.idserver import renameAfterCreation
from bika.lims.permissions import *
from bika.lims.utils import changeWorkflowState
from bika.lims.utils import encode_header
from bika.lims.utils import isActive
from bika.lims.utils import tmpID
from bika.lims.utils import to_utf8
from bika.lims.workflow import doActionFor
from DateTime import DateTime
import json
from string import Template

from email.Utils import formataddr
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.Utils import formataddr
from Products.Archetypes.config import REFERENCE_CATALOG

from DateTime import DateTime

from Products.Archetypes.event import ObjectInitializedEvent
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.utils import safe_unicode, _createObjectByType
from bika.lims import interfaces
from Products.CMFPlone.utils import _createObjectByType
from Products.CMFPlone.utils import safe_unicode

import json
import plone
import zope.event

from bika.lims import api
from bika.lims import PMF
from bika.lims import bikaMessageFactory as _
from bika.lims import interfaces
from bika.lims.browser.bika_listing import WorkflowAction
from bika.lims.idserver import renameAfterCreation
from bika.lims.permissions import EditFieldResults
from bika.lims.permissions import EditResults
from bika.lims.permissions import PreserveSample
from bika.lims.utils import changeWorkflowState
from bika.lims.utils import encode_header
from bika.lims.utils import isActive
from bika.lims.utils import t
from bika.lims.utils import tmpID
from bika.lims.workflow import doActionFor


class AnalysisRequestWorkflowAction(WorkflowAction):

"""Workflow actions taken in AnalysisRequest context.

Sample context workflow actions also redirect here
Expand All @@ -60,6 +65,68 @@ def __call__(self):
else:
WorkflowAction.__call__(self)

def notify_ar_retract(self, ar, newar):
bika_setup = api.get_bika_setup()
laboratory = bika_setup.laboratory
lab_address = "<br/>".join(laboratory.getPrintAddress())
mime_msg = MIMEMultipart('related')
mime_msg['Subject'] = t(_("Erroneus result publication from ${request_id}",
mapping={"request_id": ar.getRequestID()}))
mime_msg['From'] = formataddr(
(encode_header(laboratory.getName()),
laboratory.getEmailAddress()))
to = []
contact = ar.getContact()
if contact:
to.append(formataddr((encode_header(contact.Title()),
contact.getEmailAddress())))
for cc in ar.getCCContact():
formatted = formataddr((encode_header(cc.Title()),
cc.getEmailAddress()))
if formatted not in to:
to.append(formatted)

managers = self.context.portal_groups.getGroupMembers('LabManagers')
for bcc in managers:
user = self.portal.acl_users.getUser(bcc)
if user:
uemail = user.getProperty('email')
ufull = user.getProperty('fullname')
formatted = formataddr((encode_header(ufull), uemail))
if formatted not in to:
to.append(formatted)
mime_msg['To'] = ','.join(to)
aranchor = "<a href='%s'>%s</a>" % (ar.absolute_url(),
ar.getRequestID())
naranchor = "<a href='%s'>%s</a>" % (newar.absolute_url(),
newar.getRequestID())
addremarks = ('addremarks' in self.request and ar.getRemarks()) and ("<br/><br/>" + _("Additional remarks:") +
"<br/>" + ar.getRemarks().split("===")[1].strip() +
"<br/><br/>") or ''
sub_d = dict(request_link=aranchor,
new_request_link=naranchor,
remarks=addremarks,
lab_address=lab_address)
body = Template("Some errors have been detected in the results report "
"published from the Analysis Request $request_link. The Analysis "
"Request $new_request_link has been created automatically and the "
"previous has been invalidated.<br/>The possible mistake "
"has been picked up and is under investigation.<br/><br/>"
"$remarks $lab_address").safe_substitute(sub_d)
msg_txt = MIMEText(safe_unicode(body).encode('utf-8'),
_subtype='html')
mime_msg.preamble = 'This is a multi-part MIME message.'
mime_msg.attach(msg_txt)
try:
host = getToolByName(self.context, 'MailHost')
host.send(mime_msg.as_string(), immediate=True)
except Exception as msg:
message = _('Unable to send an email to alert lab '
'client contacts that the Analysis Request has been '
'retracted: ${error}',
mapping={'error': safe_unicode(msg)})
self.context.plone_utils.addPortalMessage(message, 'warning')

def workflow_action_save_partitions_button(self):
form = self.request.form
# Sample Partitions or AR Manage Analyses: save Partition Table
Expand Down Expand Up @@ -138,7 +205,7 @@ def workflow_action_save_analyses_button(self):
for uid in Analyses:
hidden = hiddenans.get(uid, '')
hidden = True if hidden == 'on' else False
outs.append({'uid':uid, 'hidden':hidden})
outs.append({'uid': uid, 'hidden': hidden})
ar.setAnalysisServicesSettings(outs)

specs = {}
Expand Down Expand Up @@ -248,13 +315,13 @@ def workflow_action_preserve(self):
self.context.plone_utils.addPortalMessage(message, 'error')

self.destination_url = self.request.get_header("referer",
self.context.absolute_url())
self.context.absolute_url())
self.request.response.redirect(self.destination_url)

def workflow_action_receive(self):
action, came_from = WorkflowAction._get_form_workflow_action(self)
items = [self.context,] if came_from == 'workflow_action' \
else self._get_selected_items().values()
items = [self.context, ] if came_from == 'workflow_action' \
else self._get_selected_items().values()
trans, dest = self.submitTransition(action, came_from, items)
if trans and 'receive' in self.context.bika_setup.getAutoPrintStickers():
transitioned = [item.id for item in items]
Expand All @@ -270,7 +337,6 @@ def workflow_action_receive(self):

def workflow_action_submit(self):
form = self.request.form
rc = getToolByName(self.context, REFERENCE_CATALOG)
action, came_from = WorkflowAction._get_form_workflow_action(self)
checkPermission = self.context.portal_membership.checkPermission
if not isActive(self.context):
Expand Down Expand Up @@ -361,14 +427,14 @@ def workflow_action_submit(self):
# allow_setinstrument = sm.checkPermission(SetAnalysisInstrument)
allow_setinstrument = True
# ---8<-----
if allow_setinstrument == True:
if allow_setinstrument is True:
# The current analysis allows the instrument regards
# to its analysis service and method?
if (instruments[uid]==''):
if (instruments[uid] == ''):
previnstr = analysis.getInstrument()
if previnstr:
previnstr.removeAnalysis(analysis)
analysis.setInstrument(None);
analysis.setInstrument(None)
elif analysis.isInstrumentAllowed(instruments[uid]):
previnstr = analysis.getInstrument()
if previnstr:
Expand All @@ -383,7 +449,7 @@ def workflow_action_submit(self):
# allow_setmethod = sm.checkPermission(SetAnalysisMethod)
allow_setmethod = True
# ---8<-----
if allow_setmethod == True and analysis.isMethodAllowed(methods[uid]):
if allow_setmethod is True and analysis.isMethodAllowed(methods[uid]):
analysis.setMethod(methods[uid])

# Need to save the analyst?
Expand Down Expand Up @@ -471,10 +537,10 @@ def workflow_action_verify(self):
return self.workflow_action_default(action='verify', came_from=came_from)

def workflow_action_retract_ar(self):
workflow = getToolByName(self.context, 'portal_workflow')

# AR should be retracted
# Can't transition inactive ARs
if not isActive(self.context):
if not api.is_active(self.context):
message = _('Item is inactive.')
self.context.plone_utils.addPortalMessage(message, 'info')
self.request.response.redirect(self.context.absolute_url())
Expand All @@ -485,7 +551,7 @@ def workflow_action_retract_ar(self):
newar = self.cloneAR(ar)

# 2. The old AR gets a status of 'invalid'
workflow.doActionFor(ar, 'retract_ar')
api.do_transition_for(ar, 'retract_ar')

# 3. The new AR copy opens in status 'to be verified'
changeWorkflowState(newar, 'bika_ar_workflow', 'to_be_verified')
Expand All @@ -495,70 +561,9 @@ def workflow_action_retract_ar(self):
# picked up and is under investigation.
# A much possible information is provided in the email, linking
# to the AR online.
laboratory = self.context.bika_setup.laboratory
lab_address = "<br/>".join(laboratory.getPrintAddress())
mime_msg = MIMEMultipart('related')
mime_msg['Subject'] = t(_("Erroneus result publication from ${request_id}",
mapping={"request_id": ar.getRequestID()}))
mime_msg['From'] = formataddr(
(encode_header(laboratory.getName()),
laboratory.getEmailAddress()))
to = []
contact = ar.getContact()
if contact:
to.append(formataddr((encode_header(contact.Title()),
contact.getEmailAddress())))
for cc in ar.getCCContact():
formatted = formataddr((encode_header(cc.Title()),
cc.getEmailAddress()))
if formatted not in to:
to.append(formatted)

managers = self.context.portal_groups.getGroupMembers('LabManagers')
for bcc in managers:
user = self.portal.acl_users.getUser(bcc)
if user:
uemail = user.getProperty('email')
ufull = user.getProperty('fullname')
formatted = formataddr((encode_header(ufull), uemail))
if formatted not in to:
to.append(formatted)
mime_msg['To'] = ','.join(to)
aranchor = "<a href='%s'>%s</a>" % (ar.absolute_url(),
ar.getRequestID())
naranchor = "<a href='%s'>%s</a>" % (newar.absolute_url(),
newar.getRequestID())
addremarks = ('addremarks' in self.request
and ar.getRemarks()) \
and ("<br/><br/>"
+ _("Additional remarks:")
+ "<br/>"
+ ar.getRemarks().split("===")[1].strip()
+ "<br/><br/>") \
or ''
sub_d = dict(request_link=aranchor,
new_request_link=naranchor,
remarks=addremarks,
lab_address=lab_address)
body = Template("Some errors have been detected in the results report "
"published from the Analysis Request $request_link. The Analysis "
"Request $new_request_link has been created automatically and the "
"previous has been invalidated.<br/>The possible mistake "
"has been picked up and is under investigation.<br/><br/>"
"$remarks $lab_address").safe_substitute(sub_d)
msg_txt = MIMEText(safe_unicode(body).encode('utf-8'),
_subtype='html')
mime_msg.preamble = 'This is a multi-part MIME message.'
mime_msg.attach(msg_txt)
try:
host = getToolByName(self.context, 'MailHost')
host.send(mime_msg.as_string(), immediate=True)
except Exception as msg:
message = _('Unable to send an email to alert lab '
'client contacts that the Analysis Request has been '
'retracted: ${error}',
mapping={'error': safe_unicode(msg)})
self.context.plone_utils.addPortalMessage(message, 'warning')
bika_setup = api.get_bika_setup()
if bika_setup.getNotifyOnARRetract():
self.notify_ar_retract(ar, newar)

message = _('${items} invalidated.',
mapping={'items': ar.getRequestID()})
Expand Down Expand Up @@ -616,13 +621,13 @@ def cloneAR(self, ar):
# retracted analyses won't be created/shown in the new AR
workflow = getToolByName(self, "portal_workflow")
analyses = [x for x in ans
if workflow.getInfoFor(x, "review_state") not in ("retracted")]
if workflow.getInfoFor(x, "review_state") not in ("retracted")]
for an in analyses:
try:
nan = _createObjectByType("Analysis", newar, an.getKeyword())
except Exception as e:
from bika.lims import logger
logger.warn('Cannot create analysis %s inside %s (%s)'%
logger.warn('Cannot create analysis %s inside %s (%s)' %
an.getService().Title(), newar, e)
continue
nan.setService(an.getService())
Expand Down
27 changes: 20 additions & 7 deletions bika/lims/content/bikasetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,13 +425,14 @@ class PrefixesField(RecordsField):
"any Analysis in Analysis Service edit view. By default, 1"),
),
),
StringField('TypeOfmultiVerification',
schemata = "Analyses",
default = 'self_multi_enabled',
vocabulary = MULTI_VERIFICATION_TYPE,
widget = SelectionWidget(
StringField(
'TypeOfmultiVerification',
schemata="Analyses",
default='self_multi_enabled',
vocabulary=MULTI_VERIFICATION_TYPE,
widget=SelectionWidget(
label=_("Multi Verification type"),
description = _(
description=_(
"Choose type of multiple verification for the same user."
"This setting can enable/disable verifying/consecutively verifying"
"more than once for the same user."),
Expand Down Expand Up @@ -723,7 +724,7 @@ class PrefixesField(RecordsField):
),
BooleanField(
'NotifyOnRejection',
schemata="Analyses",
schemata="Notifications",
default=False,
widget=BooleanWidget(
label=_("Email notification on rejection"),
Expand All @@ -732,6 +733,17 @@ class PrefixesField(RecordsField):
"Request is rejected.")
),
),
BooleanField(
'NotifyOnARRetract',
schemata="Notifications",
default=True,
widget=BooleanWidget(
label=_("Email notification on AR retract"),
description=_("Select this to activate automatic notifications "
"via email to the Client and Lab Managers when an Analysis "
"Request is retracted.")
),
),
BooleanField(
'AllowDepartmentFiltering',
schemata="Security",
Expand Down Expand Up @@ -839,4 +851,5 @@ def _getNumberOfRequiredVerificationsVocabulary(self):
items = [(1, '1'), (2, '2'), (3, '3'), (4, '4')]
return IntDisplayList(list(items))


registerType(BikaSetup, PROJECTNAME)
Loading