Beispiel #1
0
 def isOpen(self):
     """ Returns true if the Batch is in 'open' state
     """
     revstatus = getCurrentState(self, StateFlow.review)
     canstatus = getCurrentState(self, StateFlow.cancellation)
     return revstatus == BatchState.open \
         and canstatus == CancellationState.active
Beispiel #2
0
 def isOpen(self):
     """ Returns true if the Batch is in 'open' state
     """
     revstatus = getCurrentState(self, StateFlow.review)
     canstatus = getCurrentState(self, StateFlow.cancellation)
     return revstatus == BatchState.open \
         and canstatus == CancellationState.active
Beispiel #3
0
 def workflow_guard_close(self):
     """ Permitted if current review_state is 'open'.
         The close transition is already controlled by 'Bika: Close Batch'
         permission, but left here for security reasons and also for the
         capability of being expanded/overrided by child products or
         instance-specific needs.
     """
     revstatus = getCurrentState(self, StateFlow.review)
     canstatus = getCurrentState(self, StateFlow.cancellation)
     return revstatus == BatchState.open and canstatus == CancellationState.active
Beispiel #4
0
 def workflow_guard_close(self):
     """ Permitted if current review_state is 'open'.
         The close transition is already controlled by 'Bika: Close Batch'
         permission, but left here for security reasons and also for the
         capability of being expanded/overrided by child products or
         instance-specific needs.
     """
     revstatus = getCurrentState(self, StateFlow.review)
     canstatus = getCurrentState(self, StateFlow.cancellation)
     return revstatus == BatchState.open \
         and canstatus == CancellationState.active
Beispiel #5
0
def after_reject(obj):
    """Method triggered after a 'reject' transition for the Analysis Request
    passed in is performed. Transitions and sets the rejection reasons to the
    parent Sample. Also transitions the analyses assigned to the AR
    bika.lims.workflow.AfterTransitionEventHandler
    :param obj: Analysis Request affected by the transition
    :type obj: AnalysisRequest
    """
    sample = obj.getSample()
    if not sample:
        return

    if getCurrentState(sample) != 'rejected':
        doActionFor(sample, 'reject')
        reasons = obj.getRejectionReasons()
        sample.setRejectionReasons(reasons)

    # Deactivate all analyses from this Analysis Request
    ans = obj.getAnalyses(full_objects=True)
    for analysis in ans:
        doActionFor(analysis, 'reject')

    if obj.bika_setup.getNotifyOnRejection():
        # Import here to brake circular importing somewhere
        from bika.lims.utils.analysisrequest import notify_rejection
        # Notify the Client about the Rejection.
        notify_rejection(obj)
Beispiel #6
0
def prepublish(obj):
    """Returns True if 'prepublish' transition can be applied to the Analysis
    Request passed in.
    Returns true if the Analysis Request is active (not in a cancelled/inactive
    state), the 'publish' transition cannot be performed yet, and at least one
    of its analysis is under to_be_verified state or has been already verified.
    As per default DC workflow definition in bika_ar_workflow, note that
    prepublish does not transitions the Analysis Request to any other state
    different from the actual one, neither its children. This 'fake' transition
    is only used for the prepublish action to be displayed when the Analysis
    Request' status is other than verified, so the labman can generate a
    provisional report, also if results are not yet definitive.
    :returns: true or false
    """
    if not isBasicTransitionAllowed(obj):
        return False

    if isTransitionAllowed(obj, 'publish'):
        return False

    analyses = obj.getAnalyses(full_objects=True)
    for an in analyses:
        # If the analysis is not active, omit
        if not isActive(an):
            continue

        # Check if the current state is 'verified'
        status = getCurrentState(an)
        if status in ['verified', 'to_be_verified']:
            return True

    # This analysis request has no single result ready to be verified or
    # verified yet. In this situation, it doesn't make sense to publish a
    # provisional results reports without a single result to display
    return False
Beispiel #7
0
def prepublish(obj):
    """Returns True if 'prepublish' transition can be applied to the Analysis
    Request passed in.
    Returns true if the Analysis Request is active (not in a cancelled/inactive
    state), the 'publish' transition cannot be performed yet, and at least one
    of its analysis is under to_be_verified state or has been already verified.
    As per default DC workflow definition in bika_ar_workflow, note that
    prepublish does not transitions the Analysis Request to any other state
    different from the actual one, neither its children. This 'fake' transition
    is only used for the prepublish action to be displayed when the Analysis
    Request' status is other than verified, so the labman can generate a
    provisional report, also if results are not yet definitive.
    :returns: true or false
    """
    if not isBasicTransitionAllowed(obj):
        return False

    if isTransitionAllowed(obj, 'publish'):
        return False

    analyses = obj.getAnalyses(full_objects=True)
    for an in analyses:
        # If the analysis is not active, omit
        if not isActive(an):
            continue

        # Check if the current state is 'verified'
        status = getCurrentState(an)
        if status in ['verified', 'to_be_verified']:
            return True

    # This analysis request has no single result ready to be verified or
    # verified yet. In this situation, it doesn't make sense to publish a
    # provisional results reports without a single result to display
    return False
Beispiel #8
0
def after_reject(obj):
    """Method triggered after a 'reject' transition for the Analysis Request
    passed in is performed. Transitions and sets the rejection reasons to the
    parent Sample. Also transitions the analyses assigned to the AR
    bika.lims.workflow.AfterTransitionEventHandler
    :param obj: Analysis Request affected by the transition
    :type obj: AnalysisRequest
    """
    sample = obj.getSample()
    if not sample:
        return

    if getCurrentState(sample) != 'rejected':
        doActionFor(sample, 'reject')
        reasons = obj.getRejectionReasons()
        sample.setRejectionReasons(reasons)

    # Deactivate all analyses from this Analysis Request
    ans = obj.getAnalyses(full_objects=True)
    for analysis in ans:
        doActionFor(analysis, 'reject')

    if obj.bika_setup.getNotifyOnRejection():
        # Import here to brake circular importing somewhere
        from bika.lims.utils.analysisrequest import notify_rejection
        # Notify the Client about the Rejection.
        notify_rejection(obj)
Beispiel #9
0
def after_reject(obj):
    """Method triggered after a 'reject' transition for the Sample passed in is
    performed. Transitions children (Analysis Requests and Sample Partitions)
    bika.lims.workflow.AfterTransitionEventHandler
    :param obj: Sample affected by the transition
    :type obj: Sample
    """
    ars = obj.getAnalysisRequests()
    for ar in ars:
        if getCurrentState(ar) != 'rejected':
            doActionFor(ar, 'reject')
            reasons = obj.getRejectionReasons()
            ar.setRejectionReasons(reasons)

    parts = obj.objectValues('SamplePartition')
    for part in parts:
        if getCurrentState(part) != 'rejected':
            doActionFor(part, "reject")
Beispiel #10
0
def after_reject(obj):
    """Method triggered after a 'reject' transition for the Sample passed in is
    performed. Transitions children (Analysis Requests and Sample Partitions)
    bika.lims.workflow.AfterTransitionEventHandler
    :param obj: Sample affected by the transition
    :type obj: Sample
    """
    ars = obj.getAnalysisRequests()
    for ar in ars:
        if getCurrentState(ar) != 'rejected':
            doActionFor(ar, 'reject')
            reasons = obj.getRejectionReasons()
            ar.setRejectionReasons(reasons)

    parts = obj.objectValues('SamplePartition')
    for part in parts:
        if getCurrentState(part) != 'rejected':
            doActionFor(part, "reject")
Beispiel #11
0
 def getContacts(self, only_active=True):
     """ Return an array containing the contacts from this Client
     """
     contacts = []
     if only_active:
         contacts = [c for c in self.objectValues('Contact') if
                     getCurrentState(c, StateFlow.inactive) == InactiveState.active]
     else:
         contacts = self.objectValues('Contact')
     return contacts;
Beispiel #12
0
 def getContacts(self, only_active=True):
     """ Return an array containing the contacts from this Client
     """
     contacts = []
     if only_active:
         contacts = [c for c in self.objectValues('Contact') if
                     getCurrentState(c, StateFlow.inactive) == InactiveState.active]
     else:
         contacts = self.objectValues('Contact')
     return contacts;
Beispiel #13
0
    def __call__(self):
        plone.protect.CheckAuthenticator(self.request)
        searchTerm = 'searchTerm' in self.request and self.request['searchTerm'].lower() or ''
        page = self.request['page']
        nr_rows = self.request['rows']
        sord = self.request['sord']
        sidx = self.request['sidx']
        attachable_states = ('assigned', 'unassigned', 'to_be_verified')
        analysis_to_slot = {}
        for s in self.context.getLayout():
            analysis_to_slot[s['analysis_uid']] = int(s['position'])
        analyses = list(self.context.getAnalyses(full_objects=True))
        # Duplicates belong to the worksheet, so we must add them individually
        for i in self.context.objectValues():
            if i.portal_type == 'DuplicateAnalysis':
                analyses.append(i)
        rows = []
        for analysis in analyses:
            review_state = getCurrentState(analysis)
            if review_state not in attachable_states:
                continue
            parent = analysis.getParentTitle()
            rows.append({'analysis_uid': analysis.UID(),
                         'slot': analysis_to_slot[analysis.UID()],
                         'service': analysis.Title(),
                         'parent': parent,
                         'type': analysis.portal_type})

        # if there's a searchTerm supplied, restrict rows to those
        # who contain at least one field that starts with the chars from
        # searchTerm.
        if searchTerm:
            orig_rows = rows
            rows = []
            for row in orig_rows:
                matches = [v for v in row.values()
                           if str(v).lower().startswith(searchTerm)]
                if matches:
                    rows.append(row)

        rows = sorted(rows, cmp=lambda x, y: cmp(x, y), key=itemgetter(sidx and sidx or 'slot'))
        if sord == 'desc':
            rows.reverse()
        pages = len(rows) / int(nr_rows)
        pages += divmod(len(rows), int(nr_rows))[1] and 1 or 0
        start = (int(page)-1) * int(nr_rows)
        end = int(page) * int(nr_rows)
        ret = {'page': page,
               'total': pages,
               'records': len(rows),
               'rows': rows[start:end]}

        return json.dumps(ret)
Beispiel #14
0
def guard_rollback_to_receive(analysis_request):
    """Return whether 'rollback_to_receive' transition can be performed or not
    """
    # Can rollback to receive if at least one analysis hasn't been submitted yet
    # or if all analyses have been rejected or retracted
    analyses = analysis_request.getAnalyses()
    skipped = 0
    for analysis in analyses:
        analysis_object = api.get_object(analysis)
        state = getCurrentState(analysis_object)
        if state in ["unassigned", "assigned"]:
            return True
        if state in ["retracted", "rejected"]:
            skipped += 1
    return len(analyses) == skipped
Beispiel #15
0
    def test_LIMS_2080_correctly_interpret_false_and_blank_values(self):
        client = self.portal.clients.objectValues()[0]
        arimport = self.addthing(client, 'ARImport')
        arimport.unmarkCreationFlag()
        arimport.setFilename("test1.csv")
        arimport.setOriginalFile("""
Header,      File name,  Client name,  Client ID, Contact,     CC Names - Report, CC Emails - Report, CC Names - Invoice, CC Emails - Invoice, No of Samples, Client Order Number, Client Reference,,
Header Data, test1.csv,  Happy Hills,  HH,        Rita Mohale,                  ,                   ,                    ,                    , 10,            HHPO-001,                            ,,
Samples,    ClientSampleID,    SamplingDate,DateSampled,SamplePoint,SampleMatrix,SampleType,ContainerType,ReportDryMatter,Priority,Total number of Analyses or Profiles,Price excl Tax,ECO,SAL,COL,TAS,MicroBio,Properties
Analysis price,,,,,,,,,,,,,,
"Total Analyses or Profiles",,,,,,,,,,,,,9,,,
Total price excl Tax,,,,,,,,,,,,,,
"Sample 1", HHS14001,          3/9/2014,    3/9/2014,   ,     ,     Water,     Cup,          0,              Normal,  1,                                   0,             0,0,0,0,0,1
"Sample 2", HHS14002,          3/9/2014,    3/9/2014,   ,     ,     Water,     Cup,          0,              Normal,  2,                                   0,             0,0,0,0,1,1
"Sample 3", HHS14002,          3/9/2014,    3/9/2014,   Toilet,     Liquids,     Water,     Cup,          1,              Normal,  4,                                   0,             1,1,1,1,0,0
"Sample 4", HHS14002,          3/9/2014,    3/9/2014,   Toilet,     Liquids,     Water,     Cup,          1,              Normal,  2,                                   0,             1,0,0,0,1,0
        """)

        # check that values are saved without errors
        arimport.setErrors([])
        arimport.save_header_data()
        arimport.save_sample_data()
        errors = arimport.getErrors()
        if errors:
            self.fail("Unexpected errors while saving data: " + str(errors))
        transaction.commit()
        browser = self.getBrowser(
            username=TEST_USER_NAME,
            password=TEST_USER_PASSWORD,
            loggedIn=True)

        doActionFor(arimport, 'validate')
        c_state = getCurrentState(arimport)
        self.assertTrue(
            c_state == 'valid',
            "ARrimport in 'invalid' state after it has been transitioned to "
            "'valid'.")
        browser.open(arimport.absolute_url() + "/edit")
        content = browser.contents
        re.match(
            '<option selected=\"selected\" value=\"\d+\">Toilet</option>',
            content)
        if len(re.findall('<.*selected.*Toilet', content)) != 2:
            self.fail("Should be two empty SamplePoints, and two with values")
        if len(re.findall('<.*selected.*Liquids', content)) != 2:
            self.fail("Should be two empty Matrix fields, and two with values")
        if len(re.findall('<.*checked.*ReportDry', content)) != 2:
            self.fail("Should be two False DryMatters, and two True")
Beispiel #16
0
def guard_submit(context):
    if not IAnalysisRequest.providedBy(context):
        # Note that this guard is only used for bika_ar_workflow!
        return True

    logger.info("*** Custom Guard: submit **")
    if not isBasicTransitionAllowed(context):
        return False

    invalid = 0
    analyses = context.getAnalyses()
    for an in analyses:
        if an.review_state == 'to_be_verified':
            continue

        # The analysis has already been verified?
        an = api.get_object(an)
        if wasTransitionPerformed(an, 'submit'):
            continue

        # Maybe the analysis is in an 'inactive' state?
        if not isActive(an):
            invalid += 1
            continue

        # Maybe the analysis has been rejected or retracted?
        dettached = ['rejected', 'retracted', 'attachments_due']
        status = getCurrentState(an)
        if status in dettached:
            invalid += 1
            continue

        # At this point we can assume this analysis is an a valid state and
        # the AR could potentially be submitted, but the Analysis Request can
        # only be submitted if all the analyses have been submitted already
        return False

    # Be sure that at least there is one analysis in an active state, it
    # doesn't make sense to submit an Analysis Request if all the analyses that
    # contains are rejected or cancelled!
    return len(analyses) - invalid > 0
Beispiel #17
0
def verify(obj):
    """Returns True if 'verify' transition can be applied to the Analysis
    Request passed in. This is, returns true if all the analyses that contains
    have already been verified. Those analyses that are in an inactive state
    (cancelled, inactive) are dismissed, but at least one analysis must be in
    an active state (and verified), otherwise always return False. If the
    Analysis Request is in inactive state (cancelled/inactive), returns False
    Note this guard depends entirely on the current status of the children
    :returns: true or false
    """
    if not isBasicTransitionAllowed(obj):
        return False

    analyses = obj.getAnalyses(full_objects=True)
    invalid = 0
    for an in analyses:
        # The analysis has already been verified?
        if wasTransitionPerformed(an, 'verify'):
            continue

        # Maybe the analysis is in an 'inactive' state?
        if not isActive(an):
            invalid += 1
            continue

        # Maybe the analysis has been rejected or retracted?
        dettached = ['rejected', 'retracted', 'attachments_due']
        status = getCurrentState(an)
        if status in dettached:
            invalid += 1
            continue

        # At this point we can assume this analysis is an a valid state and
        # could potentially be verified, but the Analysis Request can only be
        # verified if all the analyses have been transitioned to verified
        return False

    # Be sure that at least there is one analysis in an active state, it
    # doesn't make sense to verify an Analysis Request if all the analyses that
    # contains are rejected or cancelled!
    return len(analyses) - invalid > 0
Beispiel #18
0
def verify(obj):
    """Returns True if 'verify' transition can be applied to the Analysis
    Request passed in. This is, returns true if all the analyses that contains
    have already been verified. Those analyses that are in an inactive state
    (cancelled, inactive) are dismissed, but at least one analysis must be in
    an active state (and verified), otherwise always return False. If the
    Analysis Request is in inactive state (cancelled/inactive), returns False
    Note this guard depends entirely on the current status of the children
    :returns: true or false
    """
    if not isBasicTransitionAllowed(obj):
        return False

    analyses = obj.getAnalyses(full_objects=True)
    invalid = 0
    for an in analyses:
        # The analysis has already been verified?
        if wasTransitionPerformed(an, 'verify'):
            continue

        # Maybe the analysis is in an 'inactive' state?
        if not isActive(an):
            invalid += 1
            continue

        # Maybe the analysis has been rejected or retracted?
        dettached = ['rejected', 'retracted', 'attachments_due']
        status = getCurrentState(an)
        if status in dettached:
            invalid += 1
            continue

        # At this point we can assume this analysis is an a valid state and
        # could potentially be verified, but the Analysis Request can only be
        # verified if all the analyses have been transitioned to verified
        return False

    # Be sure that at least there is one analysis in an active state, it
    # doesn't make sense to verify an Analysis Request if all the analyses that
    # contains are rejected or cancelled!
    return len(analyses) - invalid > 0
Beispiel #19
0
def _children_are_ready(obj, transition_id, dettached_states=None):
    """Returns true if the children of the object passed in (worksheet) have
    been all transitioned in accordance with the 'transition_id' passed in. If
    dettached_states is provided, children with those states are dismissed, so
    they will not be taken into account in the evaluation. Nevertheless, at
    least one child with for which the transition_id performed is required for
    this function to return true (if all children are in dettached states, it
    always return False).
    """
    analyses = obj.getAnalyses()
    invalid = 0
    for an in analyses:
        # The analysis has already been transitioned?
        if wasTransitionPerformed(an, transition_id):
            continue

        # Maybe the analysis is in an 'inactive' state?
        if not isActive(an):
            invalid += 1
            continue

        # Maybe the analysis is in a dettached state?
        if dettached_states:
            status = getCurrentState(an)
            if status in dettached_states:
                invalid += 1
                continue

        # At this point we can assume this analysis is an a valid state and
        # could potentially be transitioned, but the Worksheet can only be
        # transitioned if all the analyses have been transitioned previously
        return False

    # Be sure that at least there is one analysis in an active state, it
    # doesn't make sense to transition a Worksheet if all the analyses that
    # contains are not valid
    return len(analyses) - invalid > 0
Beispiel #20
0
    def workflow_action_save_analyses_button(self):
        form = self.request.form
        workflow = getToolByName(self.context, 'portal_workflow')
        bsc = self.context.bika_setup_catalog
        action, came_from = WorkflowAction._get_form_workflow_action(self)
        # AR Manage Analyses: save Analyses
        ar = self.context
        sample = ar.getSample()
        objects = WorkflowAction._get_selected_items(self)
        if not objects:
            message = _("No analyses have been selected")
            self.context.plone_utils.addPortalMessage(message, 'info')
            self.destination_url = self.context.absolute_url() + "/analyses"
            self.request.response.redirect(self.destination_url)
            return
        Analyses = objects.keys()
        prices = form.get("Price", [None])[0]

        # Hidden analyses?
        # https://jira.bikalabs.com/browse/LIMS-1324
        outs = []
        hiddenans = form.get('Hidden', {})
        for uid in Analyses:
            hidden = hiddenans.get(uid, '')
            hidden = True if hidden == 'on' else False
            outs.append({'uid': uid, 'hidden': hidden})
        ar.setAnalysisServicesSettings(outs)

        specs = {}
        if form.get("min", None):
            for service_uid in Analyses:
                service = objects[service_uid]
                keyword = service.getKeyword()
                specs[service_uid] = {
                    "min": form["min"][0][service_uid],
                    "max": form["max"][0][service_uid],
                    "warn_min": form["warn_min"][0][service_uid],
                    "warn_max": form["warn_max"][0][service_uid],
                    "keyword": keyword,
                    "uid": service_uid,
                }
        else:
            for service_uid in Analyses:
                service = objects[service_uid]
                keyword = service.getKeyword()
                specs[service_uid] = ResultsRangeDict(keyword=keyword,
                                                      uid=service_uid)
        new = ar.setAnalyses(Analyses, prices=prices, specs=specs.values())
        # link analyses and partitions
        # If Bika Setup > Analyses > 'Display individual sample
        # partitions' is checked, no Partitions available.
        # https://github.com/bikalabs/Bika-LIMS/issues/1030
        if 'Partition' in form:
            for service_uid, service in objects.items():
                part_id = form['Partition'][0][service_uid]
                part = sample[part_id]
                analysis = ar[service.getKeyword()]
                analysis.setSamplePartition(part)
                analysis.reindexObject()
                partans = part.getAnalyses()
                partans.append(analysis)
                part.setAnalyses(partans)
                part.reindexObject()

        if new:
            ar_state = getCurrentState(ar)
            if wasTransitionPerformed(ar, 'to_be_verified'):
                # Apply to AR only; we don't want this transition to cascade.
                ar.REQUEST['workflow_skiplist'].append("retract all analyses")
                workflow.doActionFor(ar, 'retract')
                ar.REQUEST['workflow_skiplist'].remove("retract all analyses")
                ar_state = getCurrentState(ar)
            for analysis in new:
                changeWorkflowState(analysis, 'bika_analysis_workflow',
                                    ar_state)

        message = PMF("Changes saved.")
        self.context.plone_utils.addPortalMessage(message, 'info')
        self.destination_url = self.context.absolute_url()
        self.request.response.redirect(self.destination_url)