diff --git a/bika/lims/browser/analysisrequest/workflow.py b/bika/lims/browser/analysisrequest/workflow.py
index 9b444c1036..c09255b3dc 100644
--- a/bika/lims/browser/analysisrequest/workflow.py
+++ b/bika/lims/browser/analysisrequest/workflow.py
@@ -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
@@ -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 = "
".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 = "%s" % (ar.absolute_url(),
+ ar.getRequestID())
+ naranchor = "%s" % (newar.absolute_url(),
+ newar.getRequestID())
+ addremarks = ('addremarks' in self.request and ar.getRemarks()) and ("
" + _("Additional remarks:") +
+ "
" + ar.getRemarks().split("===")[1].strip() +
+ "
") 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.
The possible mistake "
+ "has been picked up and is under investigation.
"
+ "$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
@@ -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 = {}
@@ -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]
@@ -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):
@@ -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:
@@ -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?
@@ -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())
@@ -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')
@@ -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 = "
".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 = "%s" % (ar.absolute_url(),
- ar.getRequestID())
- naranchor = "%s" % (newar.absolute_url(),
- newar.getRequestID())
- addremarks = ('addremarks' in self.request
- and ar.getRemarks()) \
- and ("
"
- + _("Additional remarks:")
- + "
"
- + ar.getRemarks().split("===")[1].strip()
- + "
") \
- 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.
The possible mistake "
- "has been picked up and is under investigation.
"
- "$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()})
@@ -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())
diff --git a/bika/lims/content/bikasetup.py b/bika/lims/content/bikasetup.py
index fff5a03fbb..e4496d2644 100644
--- a/bika/lims/content/bikasetup.py
+++ b/bika/lims/content/bikasetup.py
@@ -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."),
@@ -723,7 +724,7 @@ class PrefixesField(RecordsField):
),
BooleanField(
'NotifyOnRejection',
- schemata="Analyses",
+ schemata="Notifications",
default=False,
widget=BooleanWidget(
label=_("Email notification on rejection"),
@@ -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",
@@ -839,4 +851,5 @@ def _getNumberOfRequiredVerificationsVocabulary(self):
items = [(1, '1'), (2, '2'), (3, '3'), (4, '4')]
return IntDisplayList(list(items))
+
registerType(BikaSetup, PROJECTNAME)
diff --git a/bika/lims/skins/bika/form_tabbing.js b/bika/lims/skins/bika/form_tabbing.js
new file mode 100644
index 0000000000..9153e99697
--- /dev/null
+++ b/bika/lims/skins/bika/form_tabbing.js
@@ -0,0 +1,185 @@
+/*
+ * This is the code for the tabbed forms. It assumes the following markup:
+ *
+ *