Пример #1
0
    def submitTransition(self, action, came_from, items):
        """ Performs the action's transition for the specified items
            Returns (numtransitions, destination), where:
            - numtransitions: the number of objects successfully transitioned.
                If no objects have been successfully transitioned, gets 0 value
            - destination: the destination url to be loaded immediately
        """
        dest = None
        transitioned = []
        workflow = getToolByName(self.context, 'portal_workflow')

        # transition selected items from the bika_listing/Table.
        for item in items:
            # the only actions allowed on inactive/cancelled
            # items are "reinstate" and "activate"
            if not isActive(item) and action not in ('reinstate', 'activate'):
                continue
            if not skip(item, action, peek=True):
                allowed_transitions = [it['id'] for it in \
                                       workflow.getTransitionsFor(item)]
                if action in allowed_transitions:
                    success, message = doActionFor(item, action)
                    if success:
                        transitioned.append(item.id)
                    else:
                        self.context.plone_utils.addPortalMessage(message, 'error')
        # automatic label printing
        if transitioned and action == 'receive' \
            and 'receive' in self.portal.bika_setup.getAutoPrintStickers():
            q = "/sticker?autoprint=1&template=%s&items=" % (self.portal.bika_setup.getAutoStickerTemplate())
            # selected_items is a list of UIDs (stickers for AR_add use IDs)
            q += ",".join(transitioned)
            dest = self.context.absolute_url() + q

        return len(transitioned), dest
Пример #2
0
 def workflow_action_publish(self):
     action, came_from = WorkflowAction._get_form_workflow_action(self)
     if not isActive(self.context):
         message = _('Item is inactive.')
         self.context.plone_utils.addPortalMessage(message, 'info')
         self.request.response.redirect(self.context.absolute_url())
         return
     # AR publish preview
     self.request.response.redirect(self.context.absolute_url() + "/publish")
Пример #3
0
 def workflow_action_publish(self):
     action, came_from = WorkflowAction._get_form_workflow_action(self)
     if not isActive(self.context):
         message = _('Item is inactive.')
         self.context.plone_utils.addPortalMessage(message, 'info')
         self.request.response.redirect(self.context.absolute_url())
         return
     # AR publish preview
     self.request.response.redirect(self.context.absolute_url() +
                                    "/publish")
Пример #4
0
 def getContacts(self, dl=True):
     pairs = []
     objects = []
     for contact in self.aq_parent.objectValues('Contact'):
         if isActive(contact) and contact.UID() != self.UID():
             pairs.append((contact.UID(), contact.Title()))
             if not dl:
                 objects.append(contact)
     pairs.sort(lambda x, y: cmp(x[1].lower(), y[1].lower()))
     return dl and DisplayList(pairs) or objects
Пример #5
0
 def getContacts(self, dl=True):
     pairs = []
     objects = []
     for contact in self.aq_parent.objectValues('Contact'):
         if isActive(contact) and contact.UID() != self.UID():
             pairs.append((contact.UID(), contact.Title()))
             if not dl:
                 objects.append(contact)
     pairs.sort(lambda x, y: cmp(x[1].lower(), y[1].lower()))
     return dl and DisplayList(pairs) or objects
Пример #6
0
 def analysisprofiles(self):
     """ Return applicable client and Lab AnalysisProfile records
     """
     res = []
     profiles = []
     client = self.context.portal_type == 'AnalysisRequest' \
         and self.context.aq_parent or self.context
     for profile in client.objectValues("AnalysisProfile"):
         if isActive(profile):
             profiles.append((profile.Title(), profile))
     profiles.sort(lambda x, y: cmp(x[0], y[0]))
     res += profiles
     profiles = []
     for profile in self.context.bika_setup.bika_analysisprofiles.objectValues("AnalysisProfile"):
         if isActive(profile):
             lab = t(_('Lab'))
             title = to_utf8(profile.Title())
             profiles.append(("%s: %s" % (lab, title), profile))
     profiles.sort(lambda x, y: cmp(x[0], y[0]))
     res += profiles
     return res
Пример #7
0
 def artemplates(self):
     """ Return applicable client and Lab ARTemplate records
     """
     res = []
     templates = []
     client = self.context.portal_type == 'AnalysisRequest' \
         and self.context.aq_parent or self.context
     for template in client.objectValues("ARTemplate"):
         if isActive(template):
             templates.append((template.Title(), template))
     templates.sort(lambda x, y: cmp(x[0], y[0]))
     res += templates
     templates = []
     for template in self.context.bika_setup.bika_artemplates.objectValues("ARTemplate"):
         if isActive(template):
             lab = t(_('Lab'))
             title = to_utf8(template.Title())
             templates.append(("%s: %s" % (lab, title), template))
     templates.sort(lambda x, y: cmp(x[0], y[0]))
     res += templates
     return res
Пример #8
0
 def analysisprofiles(self):
     """ Return applicable client and Lab AnalysisProfile records
     """
     res = []
     profiles = []
     client = self.context.portal_type == 'AnalysisRequest' \
         and self.context.aq_parent or self.context
     for profile in client.objectValues("AnalysisProfile"):
         if isActive(profile):
             profiles.append((profile.Title(), profile))
     profiles.sort(lambda x, y: cmp(x[0], y[0]))
     res += profiles
     profiles = []
     for profile in self.context.bika_setup.bika_analysisprofiles.objectValues(
             "AnalysisProfile"):
         if isActive(profile):
             lab = t(_('Lab'))
             title = to_utf8(profile.Title())
             profiles.append(("%s: %s" % (lab, title), profile))
     profiles.sort(lambda x, y: cmp(x[0], y[0]))
     res += profiles
     return res
Пример #9
0
 def artemplates(self):
     """ Return applicable client and Lab ARTemplate records
     """
     res = []
     templates = []
     client = self.context.portal_type == 'AnalysisRequest' \
         and self.context.aq_parent or self.context
     for template in client.objectValues("ARTemplate"):
         if isActive(template):
             templates.append((template.Title(), template))
     templates.sort(lambda x, y: cmp(x[0], y[0]))
     res += templates
     templates = []
     for template in self.context.bika_setup.bika_artemplates.objectValues(
             "ARTemplate"):
         if isActive(template):
             lab = t(_('Lab'))
             title = to_utf8(template.Title())
             templates.append(("%s: %s" % (lab, title), template))
     templates.sort(lambda x, y: cmp(x[0], y[0]))
     res += templates
     return res
Пример #10
0
 def __call__(self):
     mtool = getToolByName(self.context, 'portal_membership')
     checkPermission = mtool.checkPermission
     if isActive(self.context):
         if checkPermission(AddAnalysisSpec, self.context):
             self.context_actions[_('Add')] = \
                 {'url': 'createObject?type_name=AnalysisSpec',
                  'icon': '++resource++bika.lims.images/add.png'}
         #
         # @lemoene with the changes made in AR-specs, I dont know how much
         # sense this makes anymore.
         # if checkPermission("Modify portal content", self.context):
         #     self.context_actions[_('Set to lab defaults')] = \
         #         {'url': 'set_to_lab_defaults',
         #          'icon': '++resource++bika.lims.images/analysisspec.png'}
     return super(ClientAnalysisSpecsView, self).__call__()
Пример #11
0
    def __call__(self):
        self.context_actions = {}
        wf = getToolByName(self.context, 'portal_workflow')
        mtool = getToolByName(self.context, 'portal_membership')
        addPortalMessage = self.context.plone_utils.addPortalMessage
        translate = self.context.translate
        # client contact required
        active_contacts = [c for c in self.context.objectValues('Contact') if
                           wf.getInfoFor(c, 'inactive_state', '') == 'active']
        if isActive(self.context):
            if self.context.portal_type == "Client" and not active_contacts:
                msg = _("Client contact required before request may be submitted")
                addPortalMessage(msg)
            else:
                if mtool.checkPermission(AddAnalysisRequest, self.context):
                    self.context_actions[t(_('Add'))] = {
                        'url': self.context.absolute_url() + "/portal_factory/"
                        "AnalysisRequest/Request new analyses/ar_add",
                        'icon': '++resource++bika.lims.images/add.png'}

        return super(ClientAnalysisRequestsView, self).__call__()
Пример #12
0
    def folderitems(self):
        rc = getToolByName(self.context, REFERENCE_CATALOG)
        bsc = getToolByName(self.context, 'bika_setup_catalog')
        workflow = getToolByName(self.context, 'portal_workflow')
        mtool = getToolByName(self.context, 'portal_membership')
        checkPermission = mtool.checkPermission
        if not self.allow_edit:
            can_edit_analyses = False
        else:
            if self.contentFilter.get('getPointOfCapture', '') == 'field':
                can_edit_analyses = checkPermission(EditFieldResults,
                                                    self.context)
            else:
                can_edit_analyses = checkPermission(EditResults, self.context)
            self.allow_edit = can_edit_analyses
        self.show_select_column = self.allow_edit
        context_active = isActive(self.context)

        self.categories = []
        items = super(AnalysesView, self).folderitems(full_objects=True)

        # manually skim retracted analyses from the list
        new_items = []
        for i, item in enumerate(items):
            # self.contentsMethod may return brains or objects.
            if not ('obj' in items[i]):
                continue
            obj = hasattr(items[i]['obj'], 'getObject') and \
                items[i]['obj'].getObject() or \
                items[i]['obj']
            if workflow.getInfoFor(obj, 'review_state') == 'retracted' \
                and not checkPermission(ViewRetractedAnalyses, self.context):
                continue
            new_items.append(item)
        items = new_items

        methods = self.get_methods_vocabulary()

        self.interim_fields = {}
        self.interim_columns = {}
        self.specs = {}
        show_methodinstr_columns = False
        for i, item in enumerate(items):
            # self.contentsMethod may return brains or objects.
            obj = hasattr(items[i]['obj'], 'getObject') and \
                items[i]['obj'].getObject() or \
                items[i]['obj']
            if workflow.getInfoFor(obj, 'review_state') == 'retracted' \
                and not checkPermission(ViewRetractedAnalyses, self.context):
                continue

            result = obj.getResult()
            service = obj.getService()
            calculation = service.getCalculation()
            unit = service.getUnit()
            keyword = service.getKeyword()

            if self.show_categories:
                cat = obj.getService().getCategoryTitle()
                items[i]['category'] = cat
                if cat not in self.categories:
                    self.categories.append(cat)

            # Check for InterimFields attribute on our object,
            interim_fields = hasattr(obj, 'getInterimFields') \
                and obj.getInterimFields() or []
            # kick some pretty display values in.
            for x in range(len(interim_fields)):
                interim_fields[x]['formatted_value'] = \
                    format_numeric_result(obj, interim_fields[x]['value'])
            self.interim_fields[obj.UID()] = interim_fields
            items[i]['service_uid'] = service.UID()
            items[i]['Service'] = service.Title()
            items[i]['Keyword'] = keyword
            items[i]['Unit'] = format_supsub(unit) if unit else ''
            items[i]['Result'] = ''
            items[i]['formatted_result'] = ''
            items[i]['interim_fields'] = interim_fields
            items[i]['Remarks'] = obj.getRemarks()
            items[i]['Uncertainty'] = ''
            items[i]['DetectionLimit'] = ''
            items[i]['retested'] = obj.getRetested()
            items[i]['class']['retested'] = 'center'
            items[i]['result_captured'] = self.ulocalized_time(
                obj.getResultCaptureDate(), long_format=0)
            items[i]['calculation'] = calculation and True or False
            try:
                items[i]['Partition'] = obj.getSamplePartition().getId()
            except AttributeError:
                items[i]['Partition'] = ''
            if obj.portal_type == "ReferenceAnalysis":
                items[i]['DueDate'] = self.ulocalized_time(
                    obj.aq_parent.getExpiryDate(), long_format=0)
            else:
                items[i]['DueDate'] = self.ulocalized_time(obj.getDueDate(),
                                                           long_format=1)
            cd = obj.getResultCaptureDate()
            items[i]['CaptureDate'] = cd and self.ulocalized_time(
                cd, long_format=1) or ''
            items[i]['Attachments'] = ''

            item['allow_edit'] = []
            client_or_lab = ""

            tblrowclass = items[i].get('table_row_class')
            if obj.portal_type == 'ReferenceAnalysis':
                items[i]['st_uid'] = obj.aq_parent.UID()
                items[i]['table_row_class'] = ' '.join(
                    [tblrowclass, 'qc-analysis'])
            elif obj.portal_type == 'DuplicateAnalysis' and \
                obj.getAnalysis().portal_type == 'ReferenceAnalysis':
                items[i]['st_uid'] = obj.aq_parent.UID()
                items[i]['table_row_class'] = ' '.join(
                    [tblrowclass, 'qc-analysis'])
            else:
                sample = None
                if self.context.portal_type == 'AnalysisRequest':
                    sample = self.context.getSample()
                elif self.context.portal_type == 'Worksheet':
                    if obj.portal_type in ('DuplicateAnalysis',
                                           'RejectAnalysis'):
                        sample = obj.getAnalysis().getSample()
                    else:
                        sample = obj.aq_parent.getSample()
                elif self.context.portal_type == 'Sample':
                    sample = self.context
                st_uid = sample.getSampleType().UID() if sample else ''
                items[i]['st_uid'] = st_uid

            if checkPermission(ManageBika, self.context):
                service_uid = service.UID()
                latest = rc.lookupObject(service_uid).version_id
                items[i]['Service'] = service.Title()
                items[i]['class']['Service'] = "service_title"

            # Show version number of out-of-date objects
            # No: This should be done in another column, if at all.
            # The (vX) value confuses some more fragile forms.
            #     if hasattr(obj, 'reference_versions') and \
            #        service_uid in obj.reference_versions and \
            #        latest != obj.reference_versions[service_uid]:
            #         items[i]['after']['Service'] = "(v%s)" % \
            #              (obj.reference_versions[service_uid])

            # choices defined on Service apply to result fields.
            choices = service.getResultOptions()
            if choices:
                item['choices']['Result'] = choices

            # permission to view this item's results
            can_view_result = \
                getSecurityManager().checkPermission(ViewResults, obj)

            # permission to edit this item's results
            # Editing Field Results is possible while in Sample Due.
            poc = self.contentFilter.get("getPointOfCapture", 'lab')
            can_edit_analysis = self.allow_edit and context_active and \
                ( (poc == 'field' and getSecurityManager().checkPermission(EditFieldResults, obj))
                  or
                  (poc != 'field' and getSecurityManager().checkPermission(EditResults, obj)) )

            allowed_method_states = [
                'to_be_sampled', 'to_be_preserved', 'sample_received',
                'sample_registered', 'sampled', 'assigned'
            ]

            # Prevent from being edited if the instrument assigned
            # is not valid (out-of-date or uncalibrated), except if
            # the analysis is a QC with assigned status
            can_edit_analysis = can_edit_analysis \
                and (obj.isInstrumentValid() \
                    or (obj.portal_type == 'ReferenceAnalysis' \
                        and item['review_state'] in allowed_method_states))

            if can_edit_analysis:
                items[i]['allow_edit'].extend(['Analyst', 'Result', 'Remarks'])
                # if the Result field is editable, our interim fields are too
                for f in self.interim_fields[obj.UID()]:
                    items[i]['allow_edit'].append(f['keyword'])

                # if there isn't a calculation then result must be re-testable,
                # and if there are interim fields, they too must be re-testable.
                if not items[i]['calculation'] or \
                   (items[i]['calculation'] and self.interim_fields[obj.UID()]):
                    items[i]['allow_edit'].append('retested')

            # TODO: Only the labmanager must be able to change the method
            # can_set_method = getSecurityManager().checkPermission(SetAnalysisMethod, obj)
            can_set_method = can_edit_analysis \
                and item['review_state'] in allowed_method_states
            method = obj.getMethod() \
                        if hasattr(obj, 'getMethod') and obj.getMethod() \
                        else service.getMethod()

            # Display the methods selector if the AS has at least one
            # method assigned
            item['Method'] = ''
            item['replace']['Method'] = ''
            if can_set_method:
                voc = self.get_methods_vocabulary(obj)
                if voc:
                    # The service has at least one method available
                    item['Method'] = method.UID() if method else ''
                    item['choices']['Method'] = voc
                    item['allow_edit'].append('Method')
                    show_methodinstr_columns = True

                elif method:
                    # This should never happen
                    # The analysis has set a method, but its parent
                    # service hasn't any method available O_o
                    item['Method'] = method.Title()
                    item['replace']['Method'] = "<a href='%s'>%s</a>" % \
                        (method.absolute_url(), method.Title())
                    show_methodinstr_columns = True

            elif method:
                # Edition not allowed, but method set
                item['Method'] = method.Title()
                item['replace']['Method'] = "<a href='%s'>%s</a>" % \
                    (method.absolute_url(), method.Title())
                show_methodinstr_columns = True

            # TODO: Instrument selector dynamic behavior in worksheet Results
            # Only the labmanager must be able to change the instrument to be used. Also,
            # the instrument selection should be done in accordance with the method selected
            # can_set_instrument = service.getInstrumentEntryOfResults() and getSecurityManager().checkPermission(SetAnalysisInstrument, obj)
            can_set_instrument = service.getInstrumentEntryOfResults() \
                and can_edit_analysis \
                and item['review_state'] in allowed_method_states

            item['Instrument'] = ''
            item['replace']['Instrument'] = ''
            if service.getInstrumentEntryOfResults():
                instrument = None

                # If the analysis has an instrument already assigned, use it
                if service.getInstrumentEntryOfResults() \
                    and hasattr(obj, 'getInstrument') \
                    and obj.getInstrument():
                    instrument = obj.getInstrument()

                # Otherwise, use the Service's default instrument
                elif service.getInstrumentEntryOfResults():
                    instrument = service.getInstrument()

                if can_set_instrument:
                    # Edition allowed
                    voc = self.get_instruments_vocabulary(obj)
                    if voc:
                        # The service has at least one instrument available
                        item['Instrument'] = instrument.UID(
                        ) if instrument else ''
                        item['choices']['Instrument'] = voc
                        item['allow_edit'].append('Instrument')
                        show_methodinstr_columns = True

                    elif instrument:
                        # This should never happen
                        # The analysis has an instrument set, but the
                        # service hasn't any available instrument
                        item['Instrument'] = instrument.Title()
                        item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \
                            (instrument.absolute_url(), instrument.Title())
                        show_methodinstr_columns = True

                elif instrument:
                    # Edition not allowed, but instrument set
                    item['Instrument'] = instrument.Title()
                    item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \
                        (instrument.absolute_url(), instrument.Title())
                    show_methodinstr_columns = True

            else:
                # Manual entry of results, instrument not allowed
                item['Instrument'] = _('Manual')
                msgtitle = t(
                    _(
                        "Instrument entry of results not allowed for ${service}",
                        mapping={"service": safe_unicode(service.Title())},
                    ))
                item['replace']['Instrument'] = \
                    '<a href="#" title="%s">%s</a>' % (msgtitle, t(_('Manual')))

            # Sets the analyst assigned to this analysis
            if can_edit_analysis:
                analyst = obj.getAnalyst()
                # widget default: current user
                if not analyst:
                    analyst = mtool.getAuthenticatedMember().getUserName()
                items[i]['Analyst'] = analyst
                item['choices']['Analyst'] = self.getAnalysts()
            else:
                items[i]['Analyst'] = obj.getAnalystName()

            # If the user can attach files to analyses, show the attachment col
            can_add_attachment = \
                getSecurityManager().checkPermission(AddAttachment, obj)
            if can_add_attachment or can_view_result:
                attachments = ""
                if hasattr(obj, 'getAttachment'):
                    for attachment in obj.getAttachment():
                        af = attachment.getAttachmentFile()
                        icon = af.getBestIcon()
                        attachments += "<span class='attachment' attachment_uid='%s'>" % (
                            attachment.UID())
                        if icon:
                            attachments += "<img src='%s/%s'/>" % (
                                self.portal_url, icon)
                        attachments += '<a href="%s/at_download/AttachmentFile"/>%s</a>' % (
                            attachment.absolute_url(), af.filename)
                        if can_edit_analysis:
                            attachments += "<img class='deleteAttachmentButton' attachment_uid='%s' src='%s'/>" % (
                                attachment.UID(),
                                "++resource++bika.lims.images/delete.png")
                        attachments += "</br></span>"
                items[i]['replace'][
                    'Attachments'] = attachments[:-12] + "</span>"

            # Only display data bearing fields if we have ViewResults
            # permission, otherwise just put an icon in Result column.
            if can_view_result:
                items[i]['Result'] = result
                scinot = self.context.bika_setup.getScientificNotationResults()
                dmk = self.context.bika_setup.getResultsDecimalMark()
                items[i]['formatted_result'] = obj.getFormattedResult(
                    sciformat=int(scinot), decimalmark=dmk)

                # LIMS-1379 Allow manual uncertainty value input
                # https://jira.bikalabs.com/browse/LIMS-1379
                fu = format_uncertainty(obj,
                                        result,
                                        decimalmark=dmk,
                                        sciformat=int(scinot))
                fu = fu if fu else ''
                if can_edit_analysis and service.getAllowManualUncertainty(
                ) == True:
                    unc = obj.getUncertainty(result)
                    item['allow_edit'].append('Uncertainty')
                    items[i]['Uncertainty'] = unc if unc else ''
                    items[i]['before']['Uncertainty'] = '&plusmn;&nbsp;'
                    items[i]['after'][
                        'Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % items[
                            i]['Unit']
                elif fu:
                    items[i]['Uncertainty'] = fu
                    items[i]['before']['Uncertainty'] = '&plusmn;&nbsp;'
                    items[i]['after'][
                        'Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % items[
                            i]['Unit']

                # LIMS-1700. Allow manual input of Detection Limits
                # LIMS-1775. Allow to select LDL or UDL defaults in results with readonly mode
                # https://jira.bikalabs.com/browse/LIMS-1700
                # https://jira.bikalabs.com/browse/LIMS-1775
                if can_edit_analysis and \
                    hasattr(obj, 'getDetectionLimitOperand') and \
                    hasattr(service, 'getDetectionLimitSelector') and \
                    service.getDetectionLimitSelector() == True:
                    isldl = obj.isBelowLowerDetectionLimit()
                    isudl = obj.isAboveUpperDetectionLimit()
                    dlval = ''
                    if isldl or isudl:
                        dlval = '<' if isldl else '>'
                    item['allow_edit'].append('DetectionLimit')
                    item['DetectionLimit'] = dlval
                    choices = [{
                        'ResultValue': '<',
                        'ResultText': '<'
                    }, {
                        'ResultValue': '>',
                        'ResultText': '>'
                    }]
                    item['choices']['DetectionLimit'] = choices
                    self.columns['DetectionLimit']['toggle'] = True
                    srv = obj.getService()
                    defdls = {
                        'min': srv.getLowerDetectionLimit(),
                        'max': srv.getUpperDetectionLimit(),
                        'manual': srv.getAllowManualDetectionLimit()
                    }
                    defin = '<input type="hidden" id="DefaultDLS.%s" value=\'%s\'/>'
                    defin = defin % (obj.UID(), json.dumps(defdls))
                    item['after']['DetectionLimit'] = defin

                # LIMS-1769. Allow to use LDL and UDL in calculations.
                # https://jira.bikalabs.com/browse/LIMS-1769
                # Since LDL, UDL, etc. are wildcards that can be used
                # in calculations, these fields must be loaded always
                # for 'live' calculations.
                if can_edit_analysis:
                    dls = {
                        'default_ldl': 'none',
                        'default_udl': 'none',
                        'below_ldl': False,
                        'above_udl': False,
                        'is_ldl': False,
                        'is_udl': False,
                        'manual_allowed': False,
                        'dlselect_allowed': False
                    }
                    if hasattr(obj, 'getDetectionLimits'):
                        dls['below_ldl'] = obj.isBelowLowerDetectionLimit()
                        dls['above_udl'] = obj.isBelowLowerDetectionLimit()
                        dls['is_ldl'] = obj.isLowerDetectionLimit()
                        dls['is_udl'] = obj.isUpperDetectionLimit()
                        dls['default_ldl'] = service.getLowerDetectionLimit()
                        dls['default_udl'] = service.getUpperDetectionLimit()
                        dls['manual_allowed'] = service.getAllowManualDetectionLimit(
                        )
                        dls['dlselect_allowed'] = service.getDetectionLimitSelector(
                        )
                    dlsin = '<input type="hidden" id="AnalysisDLS.%s" value=\'%s\'/>'
                    dlsin = dlsin % (obj.UID(), json.dumps(dls))
                    item['after']['Result'] = dlsin

            else:
                items[i]['Specification'] = ""
                if 'Result' in items[i]['allow_edit']:
                    items[i]['allow_edit'].remove('Result')
                items[i]['before']['Result'] = \
                    '<img width="16" height="16" ' + \
                    'src="%s/++resource++bika.lims.images/to_follow.png"/>' % \
                    (self.portal_url)
            # Everyone can see valid-ranges
            spec = self.get_analysis_spec(obj)
            if spec:
                min_val = spec.get('min', '')
                min_str = ">{0}".format(min_val) if min_val else ''
                max_val = spec.get('max', '')
                max_str = "<{0}".format(max_val) if max_val else ''
                error_val = spec.get('error', '')
                error_str = "{0}%".format(error_val) if error_val else ''
                rngstr = ",".join(
                    [x for x in [min_str, max_str, error_str] if x])
            else:
                rngstr = ""
            items[i]['Specification'] = rngstr
            # Add this analysis' interim fields to the interim_columns list
            for f in self.interim_fields[obj.UID()]:
                if f['keyword'] not in self.interim_columns and not f.get(
                        'hidden', False):
                    self.interim_columns[f['keyword']] = f['title']
                # and to the item itself
                items[i][f['keyword']] = f
                items[i]['class'][f['keyword']] = 'interim'

            # check if this analysis is late/overdue

            resultdate = obj.aq_parent.getDateSampled() \
                if obj.portal_type == 'ReferenceAnalysis' \
                else obj.getResultCaptureDate()

            duedate = obj.aq_parent.getExpiryDate() \
                if obj.portal_type == 'ReferenceAnalysis' \
                else obj.getDueDate()

            items[i]['replace']['DueDate'] = \
                self.ulocalized_time(duedate, long_format=1)

            if items[i]['review_state'] not in [
                    'to_be_sampled', 'to_be_preserved', 'sample_due',
                    'published'
            ]:

                if (resultdate and resultdate > duedate) \
                    or (not resultdate and DateTime() > duedate):

                    items[i]['replace']['DueDate'] = '%s <img width="16" height="16" src="%s/++resource++bika.lims.images/late.png" title="%s"/>' % \
                        (self.ulocalized_time(duedate, long_format=1),
                         self.portal_url,
                         t(_("Late Analysis")))

            # Submitting user may not verify results (admin can though)
            if items[i]['review_state'] == 'to_be_verified' and \
               not checkPermission(VerifyOwnResults, obj):
                user_id = getSecurityManager().getUser().getId()
                self_submitted = False
                try:
                    review_history = list(
                        workflow.getInfoFor(obj, 'review_history'))
                    review_history.reverse()
                    for event in review_history:
                        if event.get('action') == 'submit':
                            if event.get('actor') == user_id:
                                self_submitted = True
                            break
                    if self_submitted:
                        items[i]['after']['state_title'] = \
                             "<img src='++resource++bika.lims.images/submitted-by-current-user.png' title='%s'/>" % \
                             (t(_("Cannot verify: Submitted by current user")))
                except WorkflowException:
                    pass

            # add icon for assigned analyses in AR views
            if self.context.portal_type == 'AnalysisRequest':
                obj = items[i]['obj']
                if obj.portal_type in ['ReferenceAnalysis',
                                       'DuplicateAnalysis'] or \
                   workflow.getInfoFor(obj, 'worksheetanalysis_review_state') == 'assigned':
                    br = obj.getBackReferences('WorksheetAnalysis')
                    if len(br) > 0:
                        ws = br[0]
                        items[i]['after']['state_title'] = \
                             "<a href='%s'><img src='++resource++bika.lims.images/worksheet.png' title='%s'/></a>" % \
                             (ws.absolute_url(),
                              t(_("Assigned to: ${worksheet_id}",
                                  mapping={'worksheet_id': safe_unicode(ws.id)})))

        # the TAL requires values for all interim fields on all
        # items, so we set blank values in unused cells
        for item in items:
            for field in self.interim_columns:
                if field not in item:
                    item[field] = ''

        # XXX order the list of interim columns
        interim_keys = self.interim_columns.keys()
        interim_keys.reverse()

        # add InterimFields keys to columns
        for col_id in interim_keys:
            if col_id not in self.columns:
                self.columns[col_id] = {
                    'title': self.interim_columns[col_id],
                    'input_width': '6',
                    'input_class': 'ajax_calculate numeric',
                    'sortable': False
                }

        if can_edit_analyses:
            new_states = []
            for state in self.review_states:
                # InterimFields are displayed in review_state
                # They are anyway available through View.columns though.
                # In case of hidden fields, the calcs.py should check calcs/services
                # for additional InterimFields!!
                pos = 'Result' in state['columns'] and \
                    state['columns'].index('Result') or len(state['columns'])
                for col_id in interim_keys:
                    if col_id not in state['columns']:
                        state['columns'].insert(pos, col_id)
                # retested column is added after Result.
                pos = 'Result' in state['columns'] and \
                    state['columns'].index('Uncertainty') + 1 or len(state['columns'])
                state['columns'].insert(pos, 'retested')
                new_states.append(state)
            self.review_states = new_states
            # Allow selecting individual analyses
            self.show_select_column = True

        # Dry Matter.
        # The Dry Matter column is never enabled for reference sample contexts
        # and refers to getReportDryMatter in ARs.
        if items and \
            (hasattr(self.context, 'getReportDryMatter') and \
             self.context.getReportDryMatter()):

            # look through all items
            # if the item's Service supports ReportDryMatter, add getResultDM().
            for item in items:
                if item['obj'].getService().getReportDryMatter():
                    item['ResultDM'] = item['obj'].getResultDM()
                else:
                    item['ResultDM'] = ''
                if item['ResultDM']:
                    item['after']['ResultDM'] = "<em class='discreet'>%</em>"

            # modify the review_states list to include the ResultDM column
            new_states = []
            for state in self.review_states:
                pos = 'Result' in state['columns'] and \
                    state['columns'].index('Uncertainty') + 1 or len(state['columns'])
                state['columns'].insert(pos, 'ResultDM')
                new_states.append(state)
            self.review_states = new_states

        self.categories.sort()

        # self.json_specs = json.dumps(self.specs)
        self.json_interim_fields = json.dumps(self.interim_fields)
        self.items = items

        # Method and Instrument columns must be shown or hidden at the
        # same time, because the value assigned to one causes
        # a value reassignment to the other (one method can be performed
        # by different instruments)
        self.columns['Method']['toggle'] = show_methodinstr_columns
        self.columns['Instrument']['toggle'] = show_methodinstr_columns

        return items
Пример #13
0
    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):
            message = _('Item is inactive.')
            self.context.plone_utils.addPortalMessage(message, 'info')
            self.request.response.redirect(self.context.absolute_url())
            return
        # calcs.js has kept item_data and form input interim values synced,
        # so the json strings from item_data will be the same as the form values
        item_data = {}
        if 'item_data' in form:
            if isinstance(form['item_data'], list):
                for i_d in form['item_data']:
                    for i, d in json.loads(i_d).items():
                        item_data[i] = d
            else:
                item_data = json.loads(form['item_data'])
        selected_analyses = WorkflowAction._get_selected_items(self)
        results = {}
        hasInterims = {}
        # check that the form values match the database
        # save them if not.
        for uid, result in self.request.form.get('Result', [{}])[0].items():
            # if the AR has ReportDryMatter set, get dry_result from form.
            dry_result = ''
            if hasattr(self.context, 'getReportDryMatter') \
               and self.context.getReportDryMatter():
                for k, v in self.request.form['ResultDM'][0].items():
                    if uid == k:
                        dry_result = v
                        break
            if uid in selected_analyses:
                analysis = selected_analyses[uid]
            else:
                analysis = rc.lookupObject(uid)
            if not analysis:
                # ignore result if analysis object no longer exists
                continue
            results[uid] = result
            interimFields = item_data[uid]
            if len(interimFields) > 0:
                hasInterims[uid] = True
            else:
                hasInterims[uid] = False
            retested = 'retested' in form and uid in form['retested']
            remarks = form.get('Remarks', [
                {},
            ])[0].get(uid, '')
            # Don't save uneccessary things
            # https://github.com/bikalabs/Bika-LIMS/issues/766:
            #    Somehow, using analysis.edit() fails silently when
            #    logged in as Analyst.
            if analysis.getInterimFields() != interimFields or \
               analysis.getRetested() != retested or \
               analysis.getRemarks() != remarks:
                analysis.setInterimFields(interimFields)
                analysis.setRetested(retested)
                analysis.setRemarks(remarks)
            # save results separately, otherwise capture date is rewritten
            if analysis.getResult() != result or \
               analysis.getResultDM() != dry_result:
                analysis.setResultDM(dry_result)
                analysis.setResult(result)
        methods = self.request.form.get('Method', [{}])[0]
        instruments = self.request.form.get('Instrument', [{}])[0]
        analysts = self.request.form.get('Analyst', [{}])[0]
        uncertainties = self.request.form.get('Uncertainty', [{}])[0]
        dlimits = self.request.form.get('DetectionLimit', [{}])[0]
        # discover which items may be submitted
        submissable = []
        for uid, analysis in selected_analyses.items():
            analysis_active = isActive(analysis)

            # Need to save the instrument?
            if uid in instruments and analysis_active:
                # TODO: Add SetAnalysisInstrument permission
                # allow_setinstrument = sm.checkPermission(SetAnalysisInstrument)
                allow_setinstrument = True
                # ---8<-----
                if allow_setinstrument == True:
                    # The current analysis allows the instrument regards
                    # to its analysis service and method?
                    if (instruments[uid] == ''):
                        previnstr = analysis.getInstrument()
                        if previnstr:
                            previnstr.removeAnalysis(analysis)
                        analysis.setInstrument(None)
                    elif analysis.isInstrumentAllowed(instruments[uid]):
                        previnstr = analysis.getInstrument()
                        if previnstr:
                            previnstr.removeAnalysis(analysis)
                        analysis.setInstrument(instruments[uid])
                        instrument = analysis.getInstrument()
                        instrument.addAnalysis(analysis)

            # Need to save the method?
            if uid in methods and analysis_active:
                # TODO: Add SetAnalysisMethod permission
                # allow_setmethod = sm.checkPermission(SetAnalysisMethod)
                allow_setmethod = True
                # ---8<-----
                if allow_setmethod == True and analysis.isMethodAllowed(
                        methods[uid]):
                    analysis.setMethod(methods[uid])

            # Need to save the analyst?
            if uid in analysts and analysis_active:
                analysis.setAnalyst(analysts[uid])

            # Need to save the uncertainty?
            if uid in uncertainties and analysis_active:
                analysis.setUncertainty(uncertainties[uid])

            # Need to save the detection limit?
            if analysis_active:
                analysis.setDetectionLimitOperand(dlimits.get(uid, None))

            if uid not in results or not results[uid]:
                continue
            can_submit = True
            # guard_submit does a lot of the same stuff, too.
            # the code there has also been commented.
            # we must find a better way to allow dependencies to control
            # this process.
            # for dependency in analysis.getDependencies():
            #     dep_state = workflow.getInfoFor(dependency, 'review_state')
            #     if hasInterims[uid]:
            #         if dep_state in ('to_be_sampled', 'to_be_preserved',
            #                          'sample_due', 'sample_received',
            #                          'attachment_due', 'to_be_verified',):
            #             can_submit = False
            #             break
            #     else:
            #         if dep_state in ('to_be_sampled', 'to_be_preserved',
            #                          'sample_due', 'sample_received',):
            #             can_submit = False
            #             break
            if can_submit and analysis not in submissable:
                submissable.append(analysis)
        # and then submit them.
        for analysis in submissable:
            doActionFor(analysis, 'submit')
        message = PMF("Changes saved.")
        self.context.plone_utils.addPortalMessage(message, 'info')
        if checkPermission(EditResults, self.context):
            self.destination_url = self.context.absolute_url(
            ) + "/manage_results"
        else:
            self.destination_url = self.context.absolute_url()
        self.request.response.redirect(self.destination_url)
Пример #14
0
    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):
            message = _('Item is inactive.')
            self.context.plone_utils.addPortalMessage(message, 'info')
            self.request.response.redirect(self.context.absolute_url())
            return

        # 1. Copies the AR linking the original one and viceversa
        ar = self.context
        newar = self.cloneAR(ar)

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

        # 3. The new AR copy opens in status 'to be verified'
        changeWorkflowState(newar, 'bika_ar_workflow', 'to_be_verified')

        # 4. The system immediately alerts the client contacts who ordered
        # the results, per email and SMS, that a possible mistake has been
        # 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')

        message = _('${items} invalidated.',
                    mapping={'items': ar.getRequestID()})
        self.context.plone_utils.addPortalMessage(message, 'warning')
        self.request.response.redirect(newar.absolute_url())
Пример #15
0
    def folderitems(self):
        rc = getToolByName(self.context, REFERENCE_CATALOG)
        bsc = getToolByName(self.context, 'bika_setup_catalog')
        workflow = getToolByName(self.context, 'portal_workflow')
        mtool = getToolByName(self.context, 'portal_membership')
        checkPermission = mtool.checkPermission
        if not self.allow_edit:
            can_edit_analyses = False
        else:
            if self.contentFilter.get('getPointOfCapture', '') == 'field':
                can_edit_analyses = checkPermission(EditFieldResults, self.context)
            else:
                can_edit_analyses = checkPermission(EditResults, self.context)
            self.allow_edit = can_edit_analyses
        self.show_select_column = self.allow_edit
        context_active = isActive(self.context)

        self.categories = []
        items = super(AnalysesView, self).folderitems(full_objects = True)

        # manually skim retracted analyses from the list
        new_items = []
        for i,item in enumerate(items):
            # self.contentsMethod may return brains or objects.
            if not ('obj' in items[i]):
                continue
            obj = hasattr(items[i]['obj'], 'getObject') and \
                items[i]['obj'].getObject() or \
                items[i]['obj']
            if workflow.getInfoFor(obj, 'review_state') == 'retracted' \
                and not checkPermission(ViewRetractedAnalyses, self.context):
                continue
            new_items.append(item)
        items = new_items

        methods = self.get_methods_vocabulary()

        self.interim_fields = {}
        self.interim_columns = {}
        self.specs = {}
        show_methodinstr_columns = False
        for i, item in enumerate(items):
            # self.contentsMethod may return brains or objects.
            obj = hasattr(items[i]['obj'], 'getObject') and \
                items[i]['obj'].getObject() or \
                items[i]['obj']
            if workflow.getInfoFor(obj, 'review_state') == 'retracted' \
                and not checkPermission(ViewRetractedAnalyses, self.context):
                continue

            result = obj.getResult()
            service = obj.getService()
            calculation = service.getCalculation()
            unit = service.getUnit()
            keyword = service.getKeyword()

            if self.show_categories:
                cat = obj.getService().getCategoryTitle()
                items[i]['category'] = cat
                if cat not in self.categories:
                    self.categories.append(cat)

            # Check for InterimFields attribute on our object,
            interim_fields = hasattr(obj, 'getInterimFields') \
                and obj.getInterimFields() or []
            # kick some pretty display values in.
            for x in range(len(interim_fields)):
                interim_fields[x]['formatted_value'] = \
                    format_numeric_result(obj, interim_fields[x]['value'])
            self.interim_fields[obj.UID()] = interim_fields
            items[i]['service_uid'] = service.UID()
            items[i]['Service'] = service.Title()
            items[i]['Keyword'] = keyword
            items[i]['Unit'] = format_supsub(unit) if unit else ''
            items[i]['Result'] = ''
            items[i]['formatted_result'] = ''
            items[i]['interim_fields'] = interim_fields
            items[i]['Remarks'] = obj.getRemarks()
            items[i]['Uncertainty'] = ''
            items[i]['DetectionLimit'] = ''
            items[i]['retested'] = obj.getRetested()
            items[i]['class']['retested'] = 'center'
            items[i]['result_captured'] = self.ulocalized_time(
                obj.getResultCaptureDate(), long_format=0)
            items[i]['calculation'] = calculation and True or False
            try:
                items[i]['Partition'] = obj.getSamplePartition().getId()
            except AttributeError:
                items[i]['Partition'] = ''
            if obj.portal_type == "ReferenceAnalysis":
                items[i]['DueDate'] = self.ulocalized_time(obj.aq_parent.getExpiryDate(), long_format=0)
            else:
                items[i]['DueDate'] = self.ulocalized_time(obj.getDueDate(), long_format=1)
            cd = obj.getResultCaptureDate()
            items[i]['CaptureDate'] = cd and self.ulocalized_time(cd, long_format=1) or ''
            items[i]['Attachments'] = ''

            item['allow_edit'] = []
            client_or_lab = ""

            tblrowclass = items[i].get('table_row_class');
            if obj.portal_type == 'ReferenceAnalysis':
                items[i]['st_uid'] = obj.aq_parent.UID()
                items[i]['table_row_class'] = ' '.join([tblrowclass, 'qc-analysis']);
            elif obj.portal_type == 'DuplicateAnalysis' and \
                obj.getAnalysis().portal_type == 'ReferenceAnalysis':
                items[i]['st_uid'] = obj.aq_parent.UID()
                items[i]['table_row_class'] = ' '.join([tblrowclass, 'qc-analysis']);
            else:
                sample = None
                if self.context.portal_type == 'AnalysisRequest':
                    sample = self.context.getSample()
                elif self.context.portal_type == 'Worksheet':
                    if obj.portal_type in ('DuplicateAnalysis', 'RejectAnalysis'):
                        sample = obj.getAnalysis().getSample()
                    else:
                        sample = obj.aq_parent.getSample()
                elif self.context.portal_type == 'Sample':
                    sample = self.context
                st_uid = sample.getSampleType().UID() if sample else ''
                items[i]['st_uid'] = st_uid

            if checkPermission(ManageBika, self.context):
                service_uid = service.UID()
                latest = rc.lookupObject(service_uid).version_id
                items[i]['Service'] = service.Title()
                items[i]['class']['Service'] = "service_title"

            # Show version number of out-of-date objects
            # No: This should be done in another column, if at all.
            # The (vX) value confuses some more fragile forms.
            #     if hasattr(obj, 'reference_versions') and \
            #        service_uid in obj.reference_versions and \
            #        latest != obj.reference_versions[service_uid]:
            #         items[i]['after']['Service'] = "(v%s)" % \
            #              (obj.reference_versions[service_uid])

            # choices defined on Service apply to result fields.
            choices = service.getResultOptions()
            if choices:
                item['choices']['Result'] = choices

            # permission to view this item's results
            can_view_result = \
                getSecurityManager().checkPermission(ViewResults, obj)

            # permission to edit this item's results
            # Editing Field Results is possible while in Sample Due.
            poc = self.contentFilter.get("getPointOfCapture", 'lab')
            can_edit_analysis = self.allow_edit and context_active and \
                ( (poc == 'field' and getSecurityManager().checkPermission(EditFieldResults, obj))
                  or
                  (poc != 'field' and getSecurityManager().checkPermission(EditResults, obj)) )


            allowed_method_states = ['to_be_sampled',
                                     'to_be_preserved',
                                     'sample_received',
                                     'sample_registered',
                                     'sampled',
                                     'assigned']

            # Prevent from being edited if the instrument assigned
            # is not valid (out-of-date or uncalibrated), except if
            # the analysis is a QC with assigned status
            can_edit_analysis = can_edit_analysis \
                and (obj.isInstrumentValid() \
                    or (obj.portal_type == 'ReferenceAnalysis' \
                        and item['review_state'] in allowed_method_states))

            if can_edit_analysis:
                items[i]['allow_edit'].extend(['Analyst',
                                               'Result',
                                               'Remarks'])
                # if the Result field is editable, our interim fields are too
                for f in self.interim_fields[obj.UID()]:
                    items[i]['allow_edit'].append(f['keyword'])

                # if there isn't a calculation then result must be re-testable,
                # and if there are interim fields, they too must be re-testable.
                if not items[i]['calculation'] or \
                   (items[i]['calculation'] and self.interim_fields[obj.UID()]):
                    items[i]['allow_edit'].append('retested')


            # TODO: Only the labmanager must be able to change the method
            # can_set_method = getSecurityManager().checkPermission(SetAnalysisMethod, obj)
            can_set_method = can_edit_analysis \
                and item['review_state'] in allowed_method_states
            method = obj.getMethod() \
                        if hasattr(obj, 'getMethod') and obj.getMethod() \
                        else service.getMethod()

            # Display the methods selector if the AS has at least one
            # method assigned
            item['Method'] = ''
            item['replace']['Method'] = ''
            if can_set_method:
                voc = self.get_methods_vocabulary(obj)
                if voc:
                    # The service has at least one method available
                    item['Method'] = method.UID() if method else ''
                    item['choices']['Method'] = voc
                    item['allow_edit'].append('Method')
                    show_methodinstr_columns = True

                elif method:
                    # This should never happen
                    # The analysis has set a method, but its parent
                    # service hasn't any method available O_o
                    item['Method'] = method.Title()
                    item['replace']['Method'] = "<a href='%s'>%s</a>" % \
                        (method.absolute_url(), method.Title())
                    show_methodinstr_columns = True

            elif method:
                # Edition not allowed, but method set
                item['Method'] = method.Title()
                item['replace']['Method'] = "<a href='%s'>%s</a>" % \
                    (method.absolute_url(), method.Title())
                show_methodinstr_columns = True

            # TODO: Instrument selector dynamic behavior in worksheet Results
            # Only the labmanager must be able to change the instrument to be used. Also,
            # the instrument selection should be done in accordance with the method selected
            # can_set_instrument = service.getInstrumentEntryOfResults() and getSecurityManager().checkPermission(SetAnalysisInstrument, obj)
            can_set_instrument = service.getInstrumentEntryOfResults() \
                and can_edit_analysis \
                and item['review_state'] in allowed_method_states

            item['Instrument'] = ''
            item['replace']['Instrument'] = ''
            if service.getInstrumentEntryOfResults():
                instrument = None

                # If the analysis has an instrument already assigned, use it
                if service.getInstrumentEntryOfResults() \
                    and hasattr(obj, 'getInstrument') \
                    and obj.getInstrument():
                        instrument = obj.getInstrument()

                # Otherwise, use the Service's default instrument
                elif service.getInstrumentEntryOfResults():
                        instrument = service.getInstrument()

                if can_set_instrument:
                    # Edition allowed
                    voc = self.get_instruments_vocabulary(obj)
                    if voc:
                        # The service has at least one instrument available
                        item['Instrument'] = instrument.UID() if instrument else ''
                        item['choices']['Instrument'] = voc
                        item['allow_edit'].append('Instrument')
                        show_methodinstr_columns = True

                    elif instrument:
                        # This should never happen
                        # The analysis has an instrument set, but the
                        # service hasn't any available instrument
                        item['Instrument'] = instrument.Title()
                        item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \
                            (instrument.absolute_url(), instrument.Title())
                        show_methodinstr_columns = True

                elif instrument:
                    # Edition not allowed, but instrument set
                    item['Instrument'] = instrument.Title()
                    item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \
                        (instrument.absolute_url(), instrument.Title())
                    show_methodinstr_columns = True

            else:
                # Manual entry of results, instrument not allowed
                item['Instrument'] = _('Manual')
                msgtitle = t(_(
                    "Instrument entry of results not allowed for ${service}",
                    mapping={"service": safe_unicode(service.Title())},
                ))
                item['replace']['Instrument'] = \
                    '<a href="#" title="%s">%s</a>' % (msgtitle, t(_('Manual')))

            # Sets the analyst assigned to this analysis
            if can_edit_analysis:
                analyst = obj.getAnalyst()
                # widget default: current user
                if not analyst:
                    analyst = mtool.getAuthenticatedMember().getUserName()
                items[i]['Analyst'] = analyst
                item['choices']['Analyst'] = self.getAnalysts()
            else:
                items[i]['Analyst'] = obj.getAnalystName()

            # If the user can attach files to analyses, show the attachment col
            can_add_attachment = \
                getSecurityManager().checkPermission(AddAttachment, obj)
            if can_add_attachment or can_view_result:
                attachments = ""
                if hasattr(obj, 'getAttachment'):
                    for attachment in obj.getAttachment():
                        af = attachment.getAttachmentFile()
                        icon = af.getBestIcon()
                        attachments += "<span class='attachment' attachment_uid='%s'>" % (attachment.UID())
                        if icon: attachments += "<img src='%s/%s'/>" % (self.portal_url, icon)
                        attachments += '<a href="%s/at_download/AttachmentFile"/>%s</a>' % (attachment.absolute_url(), af.filename)
                        if can_edit_analysis:
                            attachments += "<img class='deleteAttachmentButton' attachment_uid='%s' src='%s'/>" % (attachment.UID(), "++resource++bika.lims.images/delete.png")
                        attachments += "</br></span>"
                items[i]['replace']['Attachments'] = attachments[:-12] + "</span>"

            # Only display data bearing fields if we have ViewResults
            # permission, otherwise just put an icon in Result column.
            if can_view_result:
                items[i]['Result'] = result
                scinot = self.context.bika_setup.getScientificNotationResults()
                dmk = self.context.bika_setup.getResultsDecimalMark()
                items[i]['formatted_result'] = obj.getFormattedResult(sciformat=int(scinot),decimalmark=dmk)

                # LIMS-1379 Allow manual uncertainty value input
                # https://jira.bikalabs.com/browse/LIMS-1379
                fu = format_uncertainty(obj, result, decimalmark=dmk, sciformat=int(scinot))
                fu = fu if fu else ''
                if can_edit_analysis and service.getAllowManualUncertainty() == True:
                    unc = obj.getUncertainty(result)
                    item['allow_edit'].append('Uncertainty')
                    items[i]['Uncertainty'] = unc if unc else ''
                    items[i]['before']['Uncertainty'] = '&plusmn;&nbsp;';
                    items[i]['after']['Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % items[i]['Unit'];
                elif fu:
                    items[i]['Uncertainty'] = fu
                    items[i]['before']['Uncertainty'] = '&plusmn;&nbsp;';
                    items[i]['after']['Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % items[i]['Unit'];

                # LIMS-1700. Allow manual input of Detection Limits
                # LIMS-1775. Allow to select LDL or UDL defaults in results with readonly mode
                # https://jira.bikalabs.com/browse/LIMS-1700
                # https://jira.bikalabs.com/browse/LIMS-1775
                if can_edit_analysis and \
                    hasattr(obj, 'getDetectionLimitOperand') and \
                    hasattr(service, 'getDetectionLimitSelector') and \
                    service.getDetectionLimitSelector() == True:
                    isldl = obj.isBelowLowerDetectionLimit()
                    isudl = obj.isAboveUpperDetectionLimit()
                    dlval=''
                    if isldl or isudl:
                        dlval = '<' if isldl else '>'
                    item['allow_edit'].append('DetectionLimit')
                    item['DetectionLimit'] = dlval
                    choices=[{'ResultValue': '<', 'ResultText': '<'},
                             {'ResultValue': '>', 'ResultText': '>'}]
                    item['choices']['DetectionLimit'] = choices
                    self.columns['DetectionLimit']['toggle'] = True
                    srv = obj.getService()
                    defdls = {'min':srv.getLowerDetectionLimit(),
                              'max':srv.getUpperDetectionLimit(),
                              'manual':srv.getAllowManualDetectionLimit()}
                    defin = '<input type="hidden" id="DefaultDLS.%s" value=\'%s\'/>'
                    defin = defin % (obj.UID(), json.dumps(defdls))
                    item['after']['DetectionLimit'] = defin

                # LIMS-1769. Allow to use LDL and UDL in calculations.
                # https://jira.bikalabs.com/browse/LIMS-1769
                # Since LDL, UDL, etc. are wildcards that can be used
                # in calculations, these fields must be loaded always
                # for 'live' calculations.
                if can_edit_analysis:
                    dls = {'default_ldl': 'none',
                           'default_udl': 'none',
                           'below_ldl': False,
                           'above_udl': False,
                           'is_ldl': False,
                           'is_udl': False,
                           'manual_allowed': False,
                           'dlselect_allowed': False}
                    if hasattr(obj, 'getDetectionLimits'):
                        dls['below_ldl'] = obj.isBelowLowerDetectionLimit()
                        dls['above_udl'] = obj.isBelowLowerDetectionLimit()
                        dls['is_ldl'] = obj.isLowerDetectionLimit()
                        dls['is_udl'] = obj.isUpperDetectionLimit()
                        dls['default_ldl'] = service.getLowerDetectionLimit()
                        dls['default_udl'] = service.getUpperDetectionLimit()
                        dls['manual_allowed'] = service.getAllowManualDetectionLimit()
                        dls['dlselect_allowed'] = service.getDetectionLimitSelector()
                    dlsin = '<input type="hidden" id="AnalysisDLS.%s" value=\'%s\'/>'
                    dlsin = dlsin % (obj.UID(), json.dumps(dls))
                    item['after']['Result'] = dlsin

            else:
                items[i]['Specification'] = ""
                if 'Result' in items[i]['allow_edit']:
                    items[i]['allow_edit'].remove('Result')
                items[i]['before']['Result'] = \
                    '<img width="16" height="16" ' + \
                    'src="%s/++resource++bika.lims.images/to_follow.png"/>' % \
                    (self.portal_url)
            # Everyone can see valid-ranges
            spec = self.get_analysis_spec(obj)
            if spec:
                min_val = spec.get('min', '')
                min_str = ">{0}".format(min_val) if min_val else ''
                max_val = spec.get('max', '')
                max_str = "<{0}".format(max_val) if max_val else ''
                error_val = spec.get('error', '')
                error_str = "{0}%".format(error_val) if error_val else ''
                rngstr = ",".join([x for x in [min_str, max_str, error_str] if x])
            else:
                rngstr = ""
            items[i]['Specification'] = rngstr
            # Add this analysis' interim fields to the interim_columns list
            for f in self.interim_fields[obj.UID()]:
                if f['keyword'] not in self.interim_columns and not f.get('hidden', False):
                    self.interim_columns[f['keyword']] = f['title']
                # and to the item itself
                items[i][f['keyword']] = f
                items[i]['class'][f['keyword']] = 'interim'

            # check if this analysis is late/overdue

            resultdate = obj.aq_parent.getDateSampled() \
                if obj.portal_type == 'ReferenceAnalysis' \
                else obj.getResultCaptureDate()

            duedate = obj.aq_parent.getExpiryDate() \
                if obj.portal_type == 'ReferenceAnalysis' \
                else obj.getDueDate()

            items[i]['replace']['DueDate'] = \
                self.ulocalized_time(duedate, long_format=1)

            if items[i]['review_state'] not in ['to_be_sampled',
                                                'to_be_preserved',
                                                'sample_due',
                                                'published']:

                if (resultdate and resultdate > duedate) \
                    or (not resultdate and DateTime() > duedate):

                    items[i]['replace']['DueDate'] = '%s <img width="16" height="16" src="%s/++resource++bika.lims.images/late.png" title="%s"/>' % \
                        (self.ulocalized_time(duedate, long_format=1),
                         self.portal_url,
                         t(_("Late Analysis")))

            # Submitting user may not verify results (admin can though)
            if items[i]['review_state'] == 'to_be_verified' and \
               not checkPermission(VerifyOwnResults, obj):
                user_id = getSecurityManager().getUser().getId()
                self_submitted = False
                try:
                    review_history = list(workflow.getInfoFor(obj, 'review_history'))
                    review_history.reverse()
                    for event in review_history:
                        if event.get('action') == 'submit':
                            if event.get('actor') == user_id:
                                self_submitted = True
                            break
                    if self_submitted:
                        items[i]['after']['state_title'] = \
                             "<img src='++resource++bika.lims.images/submitted-by-current-user.png' title='%s'/>" % \
                             (t(_("Cannot verify: Submitted by current user")))
                except WorkflowException:
                    pass

            # add icon for assigned analyses in AR views
            if self.context.portal_type == 'AnalysisRequest':
                obj = items[i]['obj']
                if obj.portal_type in ['ReferenceAnalysis',
                                       'DuplicateAnalysis'] or \
                   workflow.getInfoFor(obj, 'worksheetanalysis_review_state') == 'assigned':
                    br = obj.getBackReferences('WorksheetAnalysis')
                    if len(br) > 0:
                        ws = br[0]
                        items[i]['after']['state_title'] = \
                             "<a href='%s'><img src='++resource++bika.lims.images/worksheet.png' title='%s'/></a>" % \
                             (ws.absolute_url(),
                              t(_("Assigned to: ${worksheet_id}",
                                  mapping={'worksheet_id': safe_unicode(ws.id)})))

        # the TAL requires values for all interim fields on all
        # items, so we set blank values in unused cells
        for item in items:
            for field in self.interim_columns:
                if field not in item:
                    item[field] = ''

        # XXX order the list of interim columns
        interim_keys = self.interim_columns.keys()
        interim_keys.reverse()

        # add InterimFields keys to columns
        for col_id in interim_keys:
            if col_id not in self.columns:
                self.columns[col_id] = {'title': self.interim_columns[col_id],
                                        'input_width': '6',
                                        'input_class': 'ajax_calculate numeric',
                                        'sortable': False}

        if can_edit_analyses:
            new_states = []
            for state in self.review_states:
                # InterimFields are displayed in review_state
                # They are anyway available through View.columns though.
                # In case of hidden fields, the calcs.py should check calcs/services
                # for additional InterimFields!!
                pos = 'Result' in state['columns'] and \
                    state['columns'].index('Result') or len(state['columns'])
                for col_id in interim_keys:
                    if col_id not in state['columns']:
                        state['columns'].insert(pos, col_id)
                # retested column is added after Result.
                pos = 'Result' in state['columns'] and \
                    state['columns'].index('Uncertainty') + 1 or len(state['columns'])
                state['columns'].insert(pos, 'retested')
                new_states.append(state)
            self.review_states = new_states
            # Allow selecting individual analyses
            self.show_select_column = True

        # Dry Matter.
        # The Dry Matter column is never enabled for reference sample contexts
        # and refers to getReportDryMatter in ARs.
        if items and \
            (hasattr(self.context, 'getReportDryMatter') and \
             self.context.getReportDryMatter()):

            # look through all items
            # if the item's Service supports ReportDryMatter, add getResultDM().
            for item in items:
                if item['obj'].getService().getReportDryMatter():
                    item['ResultDM'] = item['obj'].getResultDM()
                else:
                    item['ResultDM'] = ''
                if item['ResultDM']:
                    item['after']['ResultDM'] = "<em class='discreet'>%</em>"

            # modify the review_states list to include the ResultDM column
            new_states = []
            for state in self.review_states:
                pos = 'Result' in state['columns'] and \
                    state['columns'].index('Uncertainty') + 1 or len(state['columns'])
                state['columns'].insert(pos, 'ResultDM')
                new_states.append(state)
            self.review_states = new_states

        self.categories.sort()

        # self.json_specs = json.dumps(self.specs)
        self.json_interim_fields = json.dumps(self.interim_fields)
        self.items = items

        # Method and Instrument columns must be shown or hidden at the
        # same time, because the value assigned to one causes
        # a value reassignment to the other (one method can be performed
        # by different instruments)
        self.columns['Method']['toggle'] = show_methodinstr_columns
        self.columns['Instrument']['toggle'] = show_methodinstr_columns

        return items
Пример #16
0
    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):
            message = _('Item is inactive.')
            self.context.plone_utils.addPortalMessage(message, 'info')
            self.request.response.redirect(self.context.absolute_url())
            return

        # 1. Copies the AR linking the original one and viceversa
        ar = self.context
        newar = self.cloneAR(ar)

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

        # 3. The new AR copy opens in status 'to be verified'
        changeWorkflowState(newar, 'bika_ar_workflow', 'to_be_verified')

        # 4. The system immediately alerts the client contacts who ordered
        # the results, per email and SMS, that a possible mistake has been
        # 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')

        message = _('${items} invalidated.',
                    mapping={'items': ar.getRequestID()})
        self.context.plone_utils.addPortalMessage(message, 'warning')
        self.request.response.redirect(newar.absolute_url())
Пример #17
0
    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):
            message = _('Item is inactive.')
            self.context.plone_utils.addPortalMessage(message, 'info')
            self.request.response.redirect(self.context.absolute_url())
            return
        # calcs.js has kept item_data and form input interim values synced,
        # so the json strings from item_data will be the same as the form values
        item_data = {}
        if 'item_data' in form:
            if isinstance(form['item_data'], list):
                for i_d in form['item_data']:
                    for i, d in json.loads(i_d).items():
                        item_data[i] = d
            else:
                item_data = json.loads(form['item_data'])
        selected_analyses = WorkflowAction._get_selected_items(self)
        results = {}
        hasInterims = {}
        # check that the form values match the database
        # save them if not.
        for uid, result in self.request.form.get('Result', [{}])[0].items():
            # if the AR has ReportDryMatter set, get dry_result from form.
            dry_result = ''
            if hasattr(self.context, 'getReportDryMatter') \
               and self.context.getReportDryMatter():
                for k, v in self.request.form['ResultDM'][0].items():
                    if uid == k:
                        dry_result = v
                        break
            if uid in selected_analyses:
                analysis = selected_analyses[uid]
            else:
                analysis = rc.lookupObject(uid)
            if not analysis:
                # ignore result if analysis object no longer exists
                continue
            results[uid] = result
            interimFields = item_data[uid]
            if len(interimFields) > 0:
                hasInterims[uid] = True
            else:
                hasInterims[uid] = False
            retested = 'retested' in form and uid in form['retested']
            remarks = form.get('Remarks', [{}, ])[0].get(uid, '')
            # Don't save uneccessary things
            # https://github.com/bikalabs/Bika-LIMS/issues/766:
            #    Somehow, using analysis.edit() fails silently when
            #    logged in as Analyst.
            if analysis.getInterimFields() != interimFields or \
               analysis.getRetested() != retested or \
               analysis.getRemarks() != remarks:
                analysis.setInterimFields(interimFields)
                analysis.setRetested(retested)
                analysis.setRemarks(remarks)
            # save results separately, otherwise capture date is rewritten
            if analysis.getResult() != result or \
               analysis.getResultDM() != dry_result:
                analysis.setResultDM(dry_result)
                analysis.setResult(result)
        methods = self.request.form.get('Method', [{}])[0]
        instruments = self.request.form.get('Instrument', [{}])[0]
        analysts = self.request.form.get('Analyst', [{}])[0]
        uncertainties = self.request.form.get('Uncertainty', [{}])[0]
        dlimits = self.request.form.get('DetectionLimit', [{}])[0]
        # discover which items may be submitted
        submissable = []
        for uid, analysis in selected_analyses.items():
            analysis_active = isActive(analysis)

            # Need to save the instrument?
            if uid in instruments and analysis_active:
                # TODO: Add SetAnalysisInstrument permission
                # allow_setinstrument = sm.checkPermission(SetAnalysisInstrument)
                allow_setinstrument = True
                # ---8<-----
                if allow_setinstrument == True:
                    # The current analysis allows the instrument regards
                    # to its analysis service and method?
                    if (instruments[uid]==''):
                        previnstr = analysis.getInstrument()
                        if previnstr:
                            previnstr.removeAnalysis(analysis)
                        analysis.setInstrument(None);
                    elif analysis.isInstrumentAllowed(instruments[uid]):
                        previnstr = analysis.getInstrument()
                        if previnstr:
                            previnstr.removeAnalysis(analysis)
                        analysis.setInstrument(instruments[uid])
                        instrument = analysis.getInstrument()
                        instrument.addAnalysis(analysis)

            # Need to save the method?
            if uid in methods and analysis_active:
                # TODO: Add SetAnalysisMethod permission
                # allow_setmethod = sm.checkPermission(SetAnalysisMethod)
                allow_setmethod = True
                # ---8<-----
                if allow_setmethod == True and analysis.isMethodAllowed(methods[uid]):
                    analysis.setMethod(methods[uid])

            # Need to save the analyst?
            if uid in analysts and analysis_active:
                analysis.setAnalyst(analysts[uid])

            # Need to save the uncertainty?
            if uid in uncertainties and analysis_active:
                analysis.setUncertainty(uncertainties[uid])

            # Need to save the detection limit?
            if analysis_active:
                analysis.setDetectionLimitOperand(dlimits.get(uid, None))

            if uid not in results or not results[uid]:
                continue
            can_submit = True
            # guard_submit does a lot of the same stuff, too.
            # the code there has also been commented.
            # we must find a better way to allow dependencies to control
            # this process.
            # for dependency in analysis.getDependencies():
            #     dep_state = workflow.getInfoFor(dependency, 'review_state')
            #     if hasInterims[uid]:
            #         if dep_state in ('to_be_sampled', 'to_be_preserved',
            #                          'sample_due', 'sample_received',
            #                          'attachment_due', 'to_be_verified',):
            #             can_submit = False
            #             break
            #     else:
            #         if dep_state in ('to_be_sampled', 'to_be_preserved',
            #                          'sample_due', 'sample_received',):
            #             can_submit = False
            #             break
            if can_submit and analysis not in submissable:
                submissable.append(analysis)
        # and then submit them.
        for analysis in submissable:
            doActionFor(analysis, 'submit')
        message = PMF("Changes saved.")
        self.context.plone_utils.addPortalMessage(message, 'info')
        if checkPermission(EditResults, self.context):
            self.destination_url = self.context.absolute_url() + "/manage_results"
        else:
            self.destination_url = self.context.absolute_url()
        self.request.response.redirect(self.destination_url)
Пример #18
0
    def __call__(self):
        form = self.request.form
        CheckAuthenticator(form)
        self.context = aq_inner(self.context)
        workflow = getToolByName(self.context, 'portal_workflow')
        bc = getToolByName(self.context, 'bika_catalog')
        rc = getToolByName(self.context, REFERENCE_CATALOG)
        translate = self.context.translate
        checkPermission = self.context.portal_membership.checkPermission

        # use came_from to decide which UI action was clicked.
        # "workflow_action" is the action name specified in the
        # portal_workflow transition url.
        came_from = "workflow_action"
        action = form.get(came_from, '')
        if not action:
            # workflow_action_button is the action name specified in
            # the bika_listing_view table buttons.
            came_from = "workflow_action_button"
            action = form.get('workflow_action_id', '')
            if not action:
                if self.destination_url == "":
                    self.destination_url = self.request.get_header("referer",
                                           self.context.absolute_url())
                self.request.response.redirect(self.destination_url)
                return

        if action == "sample":
            objects = AnalysisRequestWorkflowAction._get_selected_items(self)
            transitioned = {'to_be_preserved':[], 'sample_due':[]}
            for obj_uid, obj in objects.items():
                if obj.portal_type == "AnalysisRequest":
                    ar = obj
                    sample = obj.getSample()
                else:
                    sample = obj
                    ar = sample.aq_parent
                # can't transition inactive items
                if workflow.getInfoFor(sample, 'inactive_state', '') == 'inactive':
                    continue

                # grab this object's Sampler and DateSampled from the form
                # (if the columns are available and edit controls exist)
                if 'getSampler' in form and 'getDateSampled' in form:
                    try:
                        Sampler = form['getSampler'][0][obj_uid].strip()
                        DateSampled = form['getDateSampled'][0][obj_uid].strip()
                    except KeyError:
                        continue
                    Sampler = Sampler and Sampler or ''
                    DateSampled = DateSampled and DateTime(DateSampled) or ''
                else:
                    continue

                # write them to the sample
                sample.setSampler(Sampler)
                sample.setDateSampled(DateSampled)
                sample.reindexObject()
                ars = sample.getAnalysisRequests()
                # Analyses and AnalysisRequets have calculated fields
                # that are indexed; re-index all these objects.
                for ar in ars:
                    ar.reindexObject()
                    analyses = sample.getAnalyses({'review_state':'to_be_sampled'})
                    for a in analyses:
                        a.getObject().reindexObject()

                # transition the object if both values are present
                if Sampler and DateSampled:
                    workflow.doActionFor(sample, action)
                    new_state = workflow.getInfoFor(sample, 'review_state')
                    doActionFor(ar, action)
                    transitioned[new_state].append(sample.Title())

            message = None
            for state in transitioned:
                tlist = transitioned[state]
                if len(tlist) > 1:
                    if state == 'to_be_preserved':
                        message = _('${items} are waiting for preservation.',
                                    mapping = {'items': ', '.join(tlist)})
                    else:
                        message = _('${items} are waiting to be received.',
                                    mapping = {'items': ', '.join(tlist)})
                    self.context.plone_utils.addPortalMessage(message, 'info')
                elif len(tlist) == 1:
                    if state == 'to_be_preserved':
                        message = _('${item} is waiting for preservation.',
                                    mapping = {'item': ', '.join(tlist)})
                    else:
                        message = _('${item} is waiting to be received.',
                                    mapping = {'item': ', '.join(tlist)})
                    self.context.plone_utils.addPortalMessage(message, 'info')
            if not message:
                message = _('No changes made.')
                self.context.plone_utils.addPortalMessage(message, 'info')
            self.destination_url = self.request.get_header("referer",
                                   self.context.absolute_url())
            self.request.response.redirect(self.destination_url)

        elif action == "preserve":
            objects = AnalysisRequestWorkflowAction._get_selected_items(self)
            transitioned = {}
            not_transitioned = []
            Preserver = str()
            DatePreserved = str()
            for obj_uid, obj in objects.items():
                if obj.portal_type == "AnalysisRequest":
                    ar = obj
                    sample = obj.getSample()
                else:
                    sample = obj
                    ar = sample.aq_parent
                # can't transition inactive items
                if workflow.getInfoFor(sample, 'inactive_state', '') == 'inactive':
                    continue
                if not checkPermission(PreserveSample, sample):
                    continue

                # grab this object's Preserver and DatePreserved from the form
                # (if the columns are available and edit controls exist)
                if 'getPreserver' in form and 'getDatePreserved' in form:
                    try:
                        Preserver = form['getPreserver'][0][obj_uid].strip()
                        DatePreserved = form['getDatePreserved'][0][obj_uid].strip()
                    except KeyError:
                        continue
                    Preserver = Preserver and Preserver or ''
                    DatePreserved = DatePreserved and DateTime(DatePreserved) or ''
                else:
                    continue

                for sp in sample.objectValues("SamplePartition"):
                    if workflow.getInfoFor(sp, 'review_state') == 'to_be_preserved':
                        sp.setDatePreserved(DatePreserved)
                        sp.setPreserver(Preserver)
                for sp in sample.objectValues("SamplePartition"):
                    if workflow.getInfoFor(sp, 'review_state') == 'to_be_preserved':
                        if Preserver and DatePreserved:
                            doActionFor(sp, action)
                            transitioned[sp.aq_parent.Title()] = sp.Title()
                        else:
                            not_transitioned.append(sp)

            if len(transitioned.keys()) > 1:
                message = _('${items}: partitions are waiting to be received.',
                        mapping = {'items': ', '.join(transitioned.keys())})
            else:
                message = _('${item}: ${part} is waiting to be received.',
                        mapping = {'item': ', '.join(transitioned.keys()),
                                   'part': ', '.join(transitioned.values()),})
            self.context.plone_utils.addPortalMessage(message, 'info')

            # And then the sample itself
            if Preserver and DatePreserved and not not_transitioned:
                doActionFor(sample, action)
                #message = _('${item} is waiting to be received.',
                #            mapping = {'item': sample.Title()})
                #message = t(message)
                #self.context.plone_utils.addPortalMessage(message, 'info')

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

        elif action in ('prepublish', 'publish', 'republish'):
            # We pass a list of AR objects to Publish.
            # it returns a list of AR IDs which were actually published.
            objects = AnalysisRequestWorkflowAction._get_selected_items(self)
            its = []
            for uid, obj in objects.items():
                if isActive(obj):
                    its.append(uid);
            its = ",".join(its)
            q = "/publish?items=" + its
            dest = self.portal_url+"/analysisrequests" + q
            self.request.response.redirect(dest)

        else:
            AnalysisRequestWorkflowAction.__call__(self)