Beispiel #1
0
    def getFormattedResult(self, specs=None, decimalmark='.', sciformat=1):
        """Formatted result:
        1. If the result is not floatable, return it without being formatted
        2. If the analysis specs has hidemin or hidemax enabled and the
           result is out of range, render result as '<min' or '>max'
        3. If the result is floatable, render it to the correct precision
        specs param is optional. A dictionary as follows:
            {'min': <min_val>,
             'max': <max_val>,
             'error': <error>,
             'hidemin': <hidemin_val>,
             'hidemax': <hidemax_val>}
        :param sciformat: 1. The sci notation has to be formatted as aE^+b
                          2. The sci notation has to be formatted as a·10^b
                          3. As 2, but with super html entity for exp
                          4. The sci notation has to be formatted as a·10^b
                          5. As 4, but with super html entity for exp
                          By default 1
        """
        result = self.getResult()
        service = self.getService()

        # 1. If the result is not floatable, return it without being formatted
        try:
            result = float(result)
        except:
            return result

        # 2. If the analysis specs has enabled hidemin or hidemax and the
        #    result is out of range, render result as '<min' or '>max'
        belowmin = False
        abovemax = False
        if not specs:
            uid = self.getServiceUID()
            specs = self.aq_parent.getResultsRangeDict().get(uid, {})
        hidemin = specs.get('hidemin', '')
        hidemax = specs.get('hidemax', '')
        try:
            belowmin = hidemin and result < float(hidemin) or False
        except:
            belowmin = False
            pass
        try:
            abovemax = hidemax and result > float(hidemax) or False
        except:
            abovemax = False
            pass

        # 2.1. If result is below min and hidemin enabled, return '<min'
        if belowmin:
            return formatDecimalMark('< %s' % hidemin, decimalmark)

        # 2.2. If result is above max and hidemax enabled, return '>max'
        if abovemax:
            return formatDecimalMark('> %s' % hidemax, decimalmark)

        # 3. If the result is floatable, render it to the correct precision
        return formatDecimalMark(format_numeric_result(self, result, sciformat), decimalmark)
Beispiel #2
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
Beispiel #3
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
Beispiel #4
0
    def __call__(self):

        pc = self.portal_catalog
        self.checkPermission = self.context.portal_membership.checkPermission
        self.now = DateTime()
        self.SamplingWorkflowEnabled = self.context.bika_setup.getSamplingWorkflowEnabled()

        # Client details (if client is associated)
        self.client = None
        client_uid = hasattr(self.context, 'getClientUID') and self.context.getClientUID()
        if client_uid:
            proxies = pc(portal_type='Client', UID=client_uid)
        if proxies:
            self.client = proxies[0].getObject()
            client_address = self.client.getPostalAddress()
            if self.contact and not client_address:
                client_address = self.contact.getBillingAddress()
                if not client_address:
                    client_address = self.contact.getPhysicalAddress()
            if client_address:
                _keys = ['address', 'city', 'state', 'zip', 'country']
                _list = [client_address.get(v) for v in _keys if client_address.get(v)]
                self.client_address = "<br/>".join(_list).replace("\n", "<br/>")
                if self.client_address.endswith("<br/>"):
                    self.client_address = self.client_address[:-5]
            else:
                self.client_address = None

        # Reporter
        self.member = self.context.portal_membership.getAuthenticatedMember()
        self.username = self.member.getUserName()
        self.reporter = self.user_fullname(self.username)
        self.reporter_email = self.user_email(self.username)
        self.reporter_signature = ""
        c = [x for x in self.bika_setup_catalog(portal_type='LabContact')
             if x.getObject().getUsername() == self.username]
        if c:
            sf = c[0].getObject().getSignature()
            if sf:
                self.reporter_signature = sf.absolute_url() + "/Signature"

        # laboratory
        self.laboratory = self.context.bika_setup.laboratory
        self.accredited = self.laboratory.getLaboratoryAccredited()
        lab_address = self.laboratory.getPrintAddress()
        if lab_address:
            _keys = ['address', 'city', 'state', 'zip', 'country']
            _list = [lab_address.get(v) for v in _keys if lab_address.get(v)]
            self.lab_address = "<br/>".join(_list).replace("\n", "<br/>")
            if self.lab_address.endswith("<br/>"):
                self.lab_address = self.lab_address[:-5]
        else:
            self.lab_address = None

        # Analysis Request results
        self.ars = []
        self.ar_headers = [_("Request ID"),
                           _("Date Requested"),
                           _("Sample Type"),
                           _("Sample Point")]
        self.analysis_headers = [_("Analysis Service"),
                                 _("Method"),
                                 _("Result"),
                                 _("Analyst")]
        for ar in self.context.getAnalysisRequests():
            datecreated = ar.created()
            # datereceived = ar.getDateReceived()
            # datepublished = ar.getDatePublished()
            datalines = []
            for analysis in ar.getAnalyses(full_objects=True):
                service = analysis.getService()
                method = service.getMethod()
                sample = ar.getSample()
                result = analysis.getResult()
                formatted_result = format_numeric_result(analysis, result)
                datalines.append({_("Analysis Service"): analysis.getService().Title(),
                                  _("Method"): method and method.Title() or "",
                                  _("Result"): formatted_result,
                                  _("Analyst"): self.user_fullname(analysis.getAnalyst()),
                                  _("Remarks"): analysis.getRemarks()})
            self.ars.append({
                        _("Request ID"): ar.getRequestID(),
                                _("Date Requested"): self.ulocalized_time(datecreated),  # requested->created
                        _("Sample Type"): sample.getSampleType() and sample.getSampleType().Title() or '',
                                _("Sample Point"): sample.getSamplePoint() and sample.getSamplePoint().Title() or '',
                        _("datalines"): datalines,
                        })

        # Create Report
        fn = self.context.Title() + " " + self.ulocalized_time(self.now)
        report_html = self.template()

#         debug_mode = App.config.getConfiguration().debug_mode "Commented by Yasir"
        debug_mode = True                                      #"Added by Yasir"
        if debug_mode:
            tmp_fd, tmp_fn = tempfile.mkstemp(suffix=".html")
            logger.debug("Writing HTML for %s to %s" % (self.context, tmp_fn))
            tmp_fd.write(report_html)
            tmp_fd.close()

        pdf_fd, pdf_fn = tempfile.mkstemp(suffix="pdf")
        pdf_fd.close()
        pdf = createPdf(report_html, outfile=pdf_fn)
        if debug_mode:
            logger.debug("Wrote PDF for %s to %s" % (self.context, pdf_fn))
        else:
            os.remove(pdf_fn)

        # XXX Email published batches to who?

        # Send PDF to browser
        if not pdf.err:
            setheader = self.request.RESPONSE.setHeader
            setheader('Content-Type', 'application/pdf')
            setheader("Content-Disposition", "attachment;filename=\"%s\"" % fn)
            self.request.RESPONSE.write(pdf)
Beispiel #5
0
    def __call__(self):

        pc = self.portal_catalog
        self.checkPermission = self.context.portal_membership.checkPermission
        self.now = DateTime()
        self.SamplingWorkflowEnabled = self.context.bika_setup.getSamplingWorkflowEnabled(
        )

        # Client details (if client is associated)
        self.client = None
        client_uid = hasattr(self.context,
                             'getClientUID') and self.context.getClientUID()
        if client_uid:
            proxies = pc(portal_type='Client', UID=client_uid)
        if proxies:
            self.client = proxies[0].getObject()
            client_address = self.client.getPostalAddress()
            if self.contact and not client_address:
                client_address = self.contact.getBillingAddress()
                if not client_address:
                    client_address = self.contact.getPhysicalAddress()
            if client_address:
                _keys = ['address', 'city', 'state', 'zip', 'country']
                _list = [
                    client_address.get(v) for v in _keys
                    if client_address.get(v)
                ]
                self.client_address = "<br/>".join(_list).replace(
                    "\n", "<br/>")
                if self.client_address.endswith("<br/>"):
                    self.client_address = self.client_address[:-5]
            else:
                self.client_address = None

        # Reporter
        self.member = self.context.portal_membership.getAuthenticatedMember()
        self.username = self.member.getUserName()
        self.reporter = self.user_fullname(self.username)
        self.reporter_email = self.user_email(self.username)
        self.reporter_signature = ""
        c = [
            x for x in self.bika_setup_catalog(portal_type='LabContact')
            if x.getObject().getUsername() == self.username
        ]
        if c:
            sf = c[0].getObject().getSignature()
            if sf:
                self.reporter_signature = sf.absolute_url() + "/Signature"

        # laboratory
        self.laboratory = self.context.bika_setup.laboratory
        self.accredited = self.laboratory.getLaboratoryAccredited()
        lab_address = self.laboratory.getPrintAddress()
        if lab_address:
            _keys = ['address', 'city', 'state', 'zip', 'country']
            _list = [lab_address.get(v) for v in _keys if lab_address.get(v)]
            self.lab_address = "<br/>".join(_list).replace("\n", "<br/>")
            if self.lab_address.endswith("<br/>"):
                self.lab_address = self.lab_address[:-5]
        else:
            self.lab_address = None

        # Analysis Request results
        self.ars = []
        self.ar_headers = [
            _("Request ID"),
            _("Date Requested"),
            _("Sample Type"),
            _("Sample Point")
        ]
        self.analysis_headers = [
            _("Analysis Service"),
            _("Method"),
            _("Result"),
            _("Analyst")
        ]
        for ar in self.context.getAnalysisRequests():
            datecreated = ar.created()
            # datereceived = ar.getDateReceived()
            # datepublished = ar.getDatePublished()
            datalines = []
            for analysis in ar.getAnalyses(full_objects=True):
                service = analysis.getService()
                method = service.getMethod()
                sample = ar.getSample()
                result = analysis.getResult()
                formatted_result = format_numeric_result(analysis, result)
                datalines.append({
                    _("Analysis Service"):
                    analysis.getService().Title(),
                    _("Method"):
                    method and method.Title() or "",
                    _("Result"):
                    formatted_result,
                    _("Analyst"):
                    self.user_fullname(analysis.getAnalyst()),
                    _("Remarks"):
                    analysis.getRemarks()
                })
            self.ars.append({
                _("Request ID"):
                ar.getRequestID(),
                _("Date Requested"):
                self.ulocalized_time(datecreated),  # requested->created
                _("Sample Type"):
                sample.getSampleType() and sample.getSampleType().Title()
                or '',
                _("Sample Point"):
                sample.getSamplePoint() and sample.getSamplePoint().Title()
                or '',
                _("datalines"):
                datalines,
            })

        # Create Report
        fn = self.context.Title() + " " + self.ulocalized_time(self.now)
        report_html = self.template()

        #         debug_mode = App.config.getConfiguration().debug_mode "Commented by Yasir"
        debug_mode = True  #"Added by Yasir"
        if debug_mode:
            tmp_fd, tmp_fn = tempfile.mkstemp(suffix=".html")
            logger.debug("Writing HTML for %s to %s" % (self.context, tmp_fn))
            tmp_fd.write(report_html)
            tmp_fd.close()

        pdf_fd, pdf_fn = tempfile.mkstemp(suffix="pdf")
        pdf_fd.close()
        pdf = createPdf(report_html, outfile=pdf_fn)
        if debug_mode:
            logger.debug("Wrote PDF for %s to %s" % (self.context, pdf_fn))
        else:
            os.remove(pdf_fn)

        # XXX Email published batches to who?

        # Send PDF to browser
        if not pdf.err:
            setheader = self.request.RESPONSE.setHeader
            setheader('Content-Type', 'application/pdf')
            setheader("Content-Disposition", "attachment;filename=\"%s\"" % fn)
            self.request.RESPONSE.write(pdf)
Beispiel #6
0
    def calculate(self, uid=None):
        analysis = self.analyses[uid]
        form_result = self.current_results[uid]['result']
        service = analysis.getService()
        calculation = service.getCalculation()
        if analysis.portal_type == 'ReferenceAnalysis':
            deps = {}
        else:
            deps = {}
            for dep in analysis.getDependencies():
                deps[dep.UID()] = dep
        path = '++resource++bika.lims.images'
        mapping = {}

        # values to be returned to form for this UID
        Result = {'uid': uid, 'result': form_result}
        try:
            Result['result'] = float(form_result)
        except:
            if form_result == "0/0":
                Result['result'] = ""

        if calculation:

            '''
             We need first to create the map of available parameters
             acording to the interims, analyses and wildcards:
             params = {
                    <as-1-keyword>              : <analysis_result>,
                    <as-1-keyword>.<wildcard-1> : <wildcard_1_value>,
                    <as-1-keyword>.<wildcard-2> : <wildcard_2_value>,
                    <interim-1>                 : <interim_result>,
                    ...
                    }
            '''

            # Get dependent analyses results and wildcard values to the
            # mapping. If dependent analysis without result found,
            # break and abort calculation
            unsatisfied = False
            for dependency_uid, dependency in deps.items():
                if dependency_uid in self.ignore_uids:
                    unsatisfied = True
                    break

                # LIMS-1769. Allow to use LDL and UDL in calculations.
                # https://jira.bikalabs.com/browse/LIMS-1769
                analysisvalues = {}
                if dependency_uid in self.current_results:
                    analysisvalues = self.current_results[dependency_uid]
                else:
                    # Retrieve the result and DLs from the analysis
                    analysisvalues = {
                        'keyword':  dependency.getKeyword(),
                        'result':   dependency.getResult(),
                        'ldl':      dependency.getLowerDetectionLimit(),
                        'udl':      dependency.getUpperDetectionLimit(),
                        'belowldl': dependency.isBelowLowerDetectionLimit(),
                        'aboveudl': dependency.isAboveUpperDetectionLimit(),
                    }
                if analysisvalues['result']=='':
                    unsatisfied = True
                    break;
                key = analysisvalues.get('keyword',dependency.getService().getKeyword())

                # Analysis result
                # All result mappings must be float, or they are ignored.
                try:
                    mapping[key] = float(analysisvalues.get('result'))
                    mapping['%s.%s' % (key, 'RESULT')] = float(analysisvalues.get('result'))
                    mapping['%s.%s' % (key, 'LDL')] = float(analysisvalues.get('ldl'))
                    mapping['%s.%s' % (key, 'UDL')] = float(analysisvalues.get('udl'))
                    mapping['%s.%s' % (key, 'BELOWLDL')] = int(analysisvalues.get('belowldl'))
                    mapping['%s.%s' % (key, 'ABOVEUDL')] = int(analysisvalues.get('aboveudl'))
                except:
                    # If not floatable, then abort!
                    unsatisfied = True
                    break

            if unsatisfied:
                # unsatisfied means that one or more result on which we depend
                # is blank or unavailable, so we set blank result and abort.
                self.results.append({'uid': uid,
                                     'result': '',
                                     'formatted_result': ''})
                return None

            # Add all interims to mapping
            for i_uid, i_data in self.item_data.items():
                for i in i_data:
                    # if this interim belongs to current analysis and is blank,
                    # return an empty result for this analysis.
                    if i_uid == uid and i['value'] == '':
                        self.results.append({'uid': uid,
                                             'result': '',
                                             'formatted_result': ''})
                        return None
                    # All interims must be float, or they are ignored.
                    try:
                        i['value'] = float(i['value'])
                    except:
                        pass

                    # all interims are ServiceKeyword.InterimKeyword
                    if i_uid in deps:
                        key = "%s.%s" % (deps[i_uid].getService().getKeyword(),
                                         i['keyword'])
                        mapping[key] = i['value']
                    # this analysis' interims get extra reference
                    # without service keyword prefix
                    if uid == i_uid:
                        mapping[i['keyword']] = i['value']

            # Grab values for hidden InterimFields for only for current calculation
            # we can't allow non-floats through here till we change the eval's
            # interpolation
            hidden_fields = []
            c_fields = calculation.getInterimFields()
            s_fields = service.getInterimFields()
            for field in c_fields:
                if field.get('hidden', False):
                    hidden_fields.append(field['keyword'])
                    try:
                        mapping[field['keyword']] = float(field['value'])
                    except ValueError:
                        pass
            # also grab stickier defaults from AnalysisService
            for field in s_fields:
                if field['keyword'] in hidden_fields:
                    try:
                        mapping[field['keyword']] = float(field['value'])
                    except ValueError:
                        pass

            # convert formula to a valid python string, ready for interpolation
            formula = calculation.getMinifiedFormula()
            formula = formula.replace('[', '%(').replace(']', ')f')
            try:
                formula = eval("'%s'%%mapping" % formula,
                               {"__builtins__": None,
                                'math': math,
                                'context': self.context},
                               {'mapping': mapping})
                # calculate
                result = eval(formula)
                Result['result'] = result
                self.current_results[uid]['result'] = result
            except TypeError as e:
                # non-numeric arguments in interim mapping?
                alert = {'field': 'Result',
                         'icon': path + '/exclamation.png',
                         'msg': "{0}: {1} ({2}) ".format(
                             t(_("Type Error")),
                             html_quote(str(e.args[0])),
                             formula)}
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [alert, ]
            except ZeroDivisionError as e:
                Result['result'] = '0/0'
                Result['formatted_result'] = '0/0'
                self.current_results[uid]['result'] = '0/0'
                self.results.append(Result)
                alert = {'field': 'Result',
                         'icon': path + '/exclamation.png',
                         'msg': "{0}: {1} ({2}) ".format(
                             t(_("Division by zero")),
                             html_quote(str(e.args[0])),
                             formula)}
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [alert, ]
                return None
            except KeyError as e:
                alert = {'field': 'Result',
                         'icon': path + '/exclamation.png',
                         'msg': "{0}: {1} ({2}) ".format(
                             t(_("Key Error")),
                             html_quote(str(e.args[0])),
                             formula)}
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [alert, ]

        # format result
        belowmin = False
        abovemax = False
        # Some analyses will not have AnalysisSpecs, eg, ReferenceAnalysis
        if hasattr(analysis, 'getAnalysisSpecs'):
            specs = analysis.getAnalysisSpecs()
            specs = specs.getResultsRangeDict() if specs is not None else {}
            specs = specs.get(analysis.getKeyword(), {})
            hidemin = specs.get('hidemin', '')
            hidemax = specs.get('hidemax', '')
            if Result.get('result', ''):
                fresult = Result['result']
                try:
                    belowmin = hidemin and fresult < float(hidemin) or False
                except ValueError:
                    belowmin = False
                    pass
                try:
                    abovemax = hidemax and fresult > float(hidemax) or False
                except ValueError:
                    abovemax = False
                    pass

        if belowmin is True:
            Result['formatted_result'] = '< %s' % hidemin
        elif abovemax is True:
            Result['formatted_result'] = '> %s' % hidemax
        else:
            try:
                Result['formatted_result'] = format_numeric_result(analysis,
                                                                   Result['result'])
            except ValueError:
                # non-float
                Result['formatted_result'] = Result['result']

        # calculate Dry Matter result
        # if parent is not an AR, it's never going to be calculable
        dm = hasattr(analysis.aq_parent, 'getReportDryMatter') and \
            analysis.aq_parent.getReportDryMatter() and \
            analysis.getService().getReportDryMatter()
        if dm:
            dry_service = self.context.bika_setup.getDryMatterService()
            # get the UID of the DryMatter Analysis from our parent AR
            dry_analysis = [a for a in
                            analysis.aq_parent.getAnalyses(full_objects=True)
                            if a.getService().UID() == dry_service.UID()]
            if dry_analysis:
                dry_analysis = dry_analysis[0]
                dry_uid = dry_analysis.UID()
                # get the current DryMatter analysis result from the form
                if dry_uid in self.current_results:
                    try:
                        dry_result = float(self.current_results[dry_uid])
                    except:
                        dm = False
                else:
                    try:
                        dry_result = float(dry_analysis.getResult())
                    except:
                        dm = False
            else:
                dm = False
        Result['dry_result'] = dm and dry_result and \
            '%.2f' % ((Result['result'] / dry_result) * 100) or ''

        self.results.append(Result)

        # if App.config.getConfiguration().debug_mode:
        #     logger.info("calc.py: %s->%s %s" % (analysis.aq_parent.id,
        #                                         analysis.id,
        #                                         Result))

        # LIMS-1808 Uncertainty calculation on DL
        # https://jira.bikalabs.com/browse/LIMS-1808
        flres = Result.get('result', None)
        if flres and isnumber(flres):
            flres = float(flres)
            anvals = self.current_results[uid]
            isldl = anvals.get('isldl', False)
            isudl = anvals.get('isudl', False)
            ldl = anvals.get('ldl',0)
            udl = anvals.get('udl',0)
            ldl = float(ldl) if isnumber(ldl) else 0
            udl = float(udl) if isnumber(udl) else 10000000
            belowldl = (isldl or flres < ldl)
            aboveudl = (isudl or flres > udl)
            unc = '' if (belowldl or aboveudl) else analysis.getUncertainty(Result.get('result'))
            if not (belowldl or aboveudl):
                self.uncertainties.append({'uid': uid, 'uncertainty': unc})

        # maybe a service who depends on us must be recalculated.
        if analysis.portal_type == 'ReferenceAnalysis':
            dependents = []
        else:
            dependents = analysis.getDependents()
        if dependents:
            for dependent in dependents:
                dependent_uid = dependent.UID()
                # ignore analyses that no longer exist.
                if dependent_uid in self.ignore_uids or \
                   dependent_uid not in self.analyses:
                    continue
                self.calculate(dependent_uid)

        # These self.alerts are just for the json return.
        # we're placing the entire form's results in kwargs.
        adapters = getAdapters((analysis, ), IFieldIcons)
        for name, adapter in adapters:
            alerts = adapter(result=Result['result'], form_results=self.current_results)
            if alerts:
                if analysis.UID() in self.alerts:
                    self.alerts[analysis.UID()].extend(alerts[analysis.UID()])
                else:
                    self.alerts[analysis.UID()] = alerts[analysis.UID()]
Beispiel #7
0
    def calculate(self, uid=None):
        analysis = self.analyses[uid]
        form_result = self.current_results[uid]['result']
        service = analysis.getService()
        calculation = service.getCalculation()
        if analysis.portal_type == 'ReferenceAnalysis':
            deps = {}
        else:
            deps = {}
            for dep in analysis.getDependencies():
                deps[dep.UID()] = dep
        path = '++resource++bika.lims.images'
        mapping = {}

        # values to be returned to form for this UID
        Result = {'uid': uid, 'result': form_result}
        try:
            Result['result'] = float(form_result)
        except:
            if form_result == "0/0":
                Result['result'] = ""

        if calculation:
            '''
             We need first to create the map of available parameters
             acording to the interims, analyses and wildcards:
             params = {
                    <as-1-keyword>              : <analysis_result>,
                    <as-1-keyword>.<wildcard-1> : <wildcard_1_value>,
                    <as-1-keyword>.<wildcard-2> : <wildcard_2_value>,
                    <interim-1>                 : <interim_result>,
                    ...
                    }
            '''

            # Get dependent analyses results and wildcard values to the
            # mapping. If dependent analysis without result found,
            # break and abort calculation
            unsatisfied = False
            for dependency_uid, dependency in deps.items():
                if dependency_uid in self.ignore_uids:
                    unsatisfied = True
                    break

                # LIMS-1769. Allow to use LDL and UDL in calculations.
                # https://jira.bikalabs.com/browse/LIMS-1769
                analysisvalues = {}
                if dependency_uid in self.current_results:
                    analysisvalues = self.current_results[dependency_uid]
                else:
                    # Retrieve the result and DLs from the analysis
                    analysisvalues = {
                        'keyword': dependency.getKeyword(),
                        'result': dependency.getResult(),
                        'ldl': dependency.getLowerDetectionLimit(),
                        'udl': dependency.getUpperDetectionLimit(),
                        'belowldl': dependency.isBelowLowerDetectionLimit(),
                        'aboveudl': dependency.isAboveUpperDetectionLimit(),
                    }
                if analysisvalues['result'] == '':
                    unsatisfied = True
                    break
                key = analysisvalues.get('keyword',
                                         dependency.getService().getKeyword())

                # Analysis result
                # All result mappings must be float, or they are ignored.
                try:
                    mapping[key] = float(analysisvalues.get('result'))
                    mapping['%s.%s' % (key, 'RESULT')] = float(
                        analysisvalues.get('result'))
                    mapping['%s.%s' % (key, 'LDL')] = float(
                        analysisvalues.get('ldl'))
                    mapping['%s.%s' % (key, 'UDL')] = float(
                        analysisvalues.get('udl'))
                    mapping['%s.%s' % (key, 'BELOWLDL')] = int(
                        analysisvalues.get('belowldl'))
                    mapping['%s.%s' % (key, 'ABOVEUDL')] = int(
                        analysisvalues.get('aboveudl'))
                except:
                    # If not floatable, then abort!
                    unsatisfied = True
                    break

            if unsatisfied:
                # unsatisfied means that one or more result on which we depend
                # is blank or unavailable, so we set blank result and abort.
                self.results.append({
                    'uid': uid,
                    'result': '',
                    'formatted_result': ''
                })
                return None

            # Add all interims to mapping
            for i_uid, i_data in self.item_data.items():
                for i in i_data:
                    # if this interim belongs to current analysis and is blank,
                    # return an empty result for this analysis.
                    if i_uid == uid and i['value'] == '':
                        self.results.append({
                            'uid': uid,
                            'result': '',
                            'formatted_result': ''
                        })
                        return None
                    # All interims must be float, or they are ignored.
                    try:
                        i['value'] = float(i['value'])
                    except:
                        pass

                    # all interims are ServiceKeyword.InterimKeyword
                    if i_uid in deps:
                        key = "%s.%s" % (deps[i_uid].getService().getKeyword(),
                                         i['keyword'])
                        mapping[key] = i['value']
                    # this analysis' interims get extra reference
                    # without service keyword prefix
                    if uid == i_uid:
                        mapping[i['keyword']] = i['value']

            # Grab values for hidden InterimFields for only for current calculation
            # we can't allow non-floats through here till we change the eval's
            # interpolation
            hidden_fields = []
            c_fields = calculation.getInterimFields()
            s_fields = service.getInterimFields()
            for field in c_fields:
                if field.get('hidden', False):
                    hidden_fields.append(field['keyword'])
                    try:
                        mapping[field['keyword']] = float(field['value'])
                    except ValueError:
                        pass
            # also grab stickier defaults from AnalysisService
            for field in s_fields:
                if field['keyword'] in hidden_fields:
                    try:
                        mapping[field['keyword']] = float(field['value'])
                    except ValueError:
                        pass

            # convert formula to a valid python string, ready for interpolation
            formula = calculation.getMinifiedFormula()
            formula = formula.replace('[', '%(').replace(']', ')f')
            try:
                formula = eval("'%s'%%mapping" % formula, {
                    "__builtins__": None,
                    'math': math,
                    'context': self.context
                }, {'mapping': mapping})
                # calculate
                result = eval(formula)
                Result['result'] = result
                self.current_results[uid]['result'] = result
            except TypeError as e:
                # non-numeric arguments in interim mapping?
                alert = {
                    'field':
                    'Result',
                    'icon':
                    path + '/exclamation.png',
                    'msg':
                    "{0}: {1} ({2}) ".format(t(_("Type Error")),
                                             html_quote(str(e.args[0])),
                                             formula)
                }
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [
                        alert,
                    ]
            except ZeroDivisionError as e:
                Result['result'] = '0/0'
                Result['formatted_result'] = '0/0'
                self.current_results[uid]['result'] = '0/0'
                self.results.append(Result)
                alert = {
                    'field':
                    'Result',
                    'icon':
                    path + '/exclamation.png',
                    'msg':
                    "{0}: {1} ({2}) ".format(t(_("Division by zero")),
                                             html_quote(str(e.args[0])),
                                             formula)
                }
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [
                        alert,
                    ]
                return None
            except KeyError as e:
                alert = {
                    'field':
                    'Result',
                    'icon':
                    path + '/exclamation.png',
                    'msg':
                    "{0}: {1} ({2}) ".format(t(_("Key Error")),
                                             html_quote(str(e.args[0])),
                                             formula)
                }
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [
                        alert,
                    ]

        # format result
        belowmin = False
        abovemax = False
        # Some analyses will not have AnalysisSpecs, eg, ReferenceAnalysis
        if hasattr(analysis, 'getAnalysisSpecs'):
            specs = analysis.getAnalysisSpecs()
            specs = specs.getResultsRangeDict() if specs is not None else {}
            specs = specs.get(analysis.getKeyword(), {})
            hidemin = specs.get('hidemin', '')
            hidemax = specs.get('hidemax', '')
            if Result.get('result', ''):
                fresult = Result['result']
                try:
                    belowmin = hidemin and fresult < float(hidemin) or False
                except ValueError:
                    belowmin = False
                    pass
                try:
                    abovemax = hidemax and fresult > float(hidemax) or False
                except ValueError:
                    abovemax = False
                    pass

        if belowmin is True:
            Result['formatted_result'] = '< %s' % hidemin
        elif abovemax is True:
            Result['formatted_result'] = '> %s' % hidemax
        else:
            try:
                Result['formatted_result'] = format_numeric_result(
                    analysis, Result['result'])
            except ValueError:
                # non-float
                Result['formatted_result'] = Result['result']

        # calculate Dry Matter result
        # if parent is not an AR, it's never going to be calculable
        dm = hasattr(analysis.aq_parent, 'getReportDryMatter') and \
            analysis.aq_parent.getReportDryMatter() and \
            analysis.getService().getReportDryMatter()
        if dm:
            dry_service = self.context.bika_setup.getDryMatterService()
            # get the UID of the DryMatter Analysis from our parent AR
            dry_analysis = [
                a for a in analysis.aq_parent.getAnalyses(full_objects=True)
                if a.getService().UID() == dry_service.UID()
            ]
            if dry_analysis:
                dry_analysis = dry_analysis[0]
                dry_uid = dry_analysis.UID()
                # get the current DryMatter analysis result from the form
                if dry_uid in self.current_results:
                    try:
                        dry_result = float(self.current_results[dry_uid])
                    except:
                        dm = False
                else:
                    try:
                        dry_result = float(dry_analysis.getResult())
                    except:
                        dm = False
            else:
                dm = False
        Result['dry_result'] = dm and dry_result and \
            '%.2f' % ((Result['result'] / dry_result) * 100) or ''

        self.results.append(Result)

        # if App.config.getConfiguration().debug_mode:
        #     logger.info("calc.py: %s->%s %s" % (analysis.aq_parent.id,
        #                                         analysis.id,
        #                                         Result))

        # LIMS-1808 Uncertainty calculation on DL
        # https://jira.bikalabs.com/browse/LIMS-1808
        flres = Result.get('result', None)
        if flres and isnumber(flres):
            flres = float(flres)
            anvals = self.current_results[uid]
            isldl = anvals.get('isldl', False)
            isudl = anvals.get('isudl', False)
            ldl = anvals.get('ldl', 0)
            udl = anvals.get('udl', 0)
            ldl = float(ldl) if isnumber(ldl) else 0
            udl = float(udl) if isnumber(udl) else 10000000
            belowldl = (isldl or flres < ldl)
            aboveudl = (isudl or flres > udl)
            unc = '' if (belowldl or aboveudl) else analysis.getUncertainty(
                Result.get('result'))
            if not (belowldl or aboveudl):
                self.uncertainties.append({'uid': uid, 'uncertainty': unc})

        # maybe a service who depends on us must be recalculated.
        if analysis.portal_type == 'ReferenceAnalysis':
            dependents = []
        else:
            dependents = analysis.getDependents()
        if dependents:
            for dependent in dependents:
                dependent_uid = dependent.UID()
                # ignore analyses that no longer exist.
                if dependent_uid in self.ignore_uids or \
                   dependent_uid not in self.analyses:
                    continue
                self.calculate(dependent_uid)

        # These self.alerts are just for the json return.
        # we're placing the entire form's results in kwargs.
        adapters = getAdapters((analysis, ), IFieldIcons)
        for name, adapter in adapters:
            alerts = adapter(result=Result['result'],
                             form_results=self.current_results)
            if alerts:
                if analysis.UID() in self.alerts:
                    self.alerts[analysis.UID()].extend(alerts[analysis.UID()])
                else:
                    self.alerts[analysis.UID()] = alerts[analysis.UID()]