diff --git a/CHANGES.rst b/CHANGES.rst index 91c0bf3479..997c12d9d5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -17,6 +17,7 @@ Changelog **Fixed** +- #439 Cannot verify calculated analyses when retracted dependencies - #432 Wrong indentation of services in Worksheet - #436 Auto Import View has an Add Button displayed, but shouldn't - #436 Clicking on the Add Button of Instrument Certifications opens an arbitrary Add form diff --git a/bika/lims/content/abstractanalysis.py b/bika/lims/content/abstractanalysis.py index 875f05c347..1b0c6aceca 100644 --- a/bika/lims/content/abstractanalysis.py +++ b/bika/lims/content/abstractanalysis.py @@ -411,8 +411,12 @@ def getDependents(self): raise NotImplementedError("getDependents is not implemented.") @security.public - def getDependencies(self): - """Return a list of analyses who we depend on to calculate our result. + def getDependencies(self, retracted=False): + """Return a list of siblings who we depend on to calculate our result. + :param retracted: If false retracted/rejected analyses are dismissed + :type retracted: bool + :return: Analyses the current analysis depends on + :rtype: list of IAnalysis """ raise NotImplementedError("getDependencies is not implemented.") diff --git a/bika/lims/content/abstractroutineanalysis.py b/bika/lims/content/abstractroutineanalysis.py index d87728fc9c..123733e1f0 100644 --- a/bika/lims/content/abstractroutineanalysis.py +++ b/bika/lims/content/abstractroutineanalysis.py @@ -22,10 +22,11 @@ from bika.lims.interfaces import IAnalysis, IRoutineAnalysis, \ ISamplePrepWorkflow from bika.lims.interfaces.analysis import IRequestAnalysis -from bika.lims.workflow import doActionFor +from bika.lims.workflow import doActionFor, getCurrentState from bika.lims.workflow import getTransitionDate from bika.lims.workflow import skip from bika.lims.workflow import wasTransitionPerformed +from bika.lims.workflow.analysis import STATE_RETRACTED, STATE_REJECTED from zope.interface import implements # The physical sample partition linked to the Analysis. @@ -416,17 +417,28 @@ def getResultsRange(self, specification=None): return rr @security.public - def getSiblings(self): - """Return the siblings analyses, using the parent to which the current - analysis belongs to as the source""" + def getSiblings(self, retracted=False): + """ + Return the siblings analyses, using the parent to which the current + analysis belongs to as the source + :param retracted: If false, retracted/rejected siblings are dismissed + :type retracted: bool + :return: list of siblings for this analysis + :rtype: list of IAnalysis + """ raise NotImplementedError("getSiblings is not implemented.") @security.public - def getDependents(self): - """Return of siblings who depend on us to calculate their result + def getDependents(self, retracted=False): + """ + Returns a list of siblings who depend on us to calculate their result. + :param retracted: If false, retracted/rejected dependents are dismissed + :type retracted: bool + :return: Analyses the current analysis depends on + :rtype: list of IAnalysis """ dependents = [] - for sibling in self.getSiblings(): + for sibling in self.getSiblings(retracted=retracted): calculation = sibling.getCalculation() if not calculation: continue @@ -437,16 +449,24 @@ def getDependents(self): return dependents @security.public - def getDependencies(self): - """Return a list of siblings who we depend on to calculate our result. + def getDependencies(self, retracted=False): + """ + Return a list of siblings who we depend on to calculate our result. + :param retracted: If false retracted/rejected dependencies are dismissed + :type retracted: bool + :return: Analyses the current analysis depends on + :rtype: list of IAnalysis """ calc = self.getCalculation() if not calc: return [] dependencies = [] - for sibling in self.getSiblings(): - deps = [dep.UID() for dep in sibling.getDependents()] + for sibling in self.getSiblings(retracted=retracted): + # We get all analyses that depend on me, also if retracted (maybe + # I am one of those that are retracted!) + deps = sibling.getDependents(retracted=True) + deps = [dep.UID() for dep in deps] if self.UID() in deps: dependencies.append(sibling) return dependencies diff --git a/bika/lims/content/analysis.py b/bika/lims/content/analysis.py index 8d25f056e3..be182ce232 100644 --- a/bika/lims/content/analysis.py +++ b/bika/lims/content/analysis.py @@ -11,6 +11,8 @@ from bika.lims.content.abstractroutineanalysis import AbstractRoutineAnalysis from bika.lims.content.abstractroutineanalysis import schema from bika.lims.interfaces import IRoutineAnalysis, ISamplePrepWorkflow +from bika.lims.workflow import getCurrentState, in_state +from bika.lims.workflow.analysis import STATE_RETRACTED, STATE_REJECTED from zope.interface import implements schema = schema.copy() + Schema(( @@ -32,15 +34,33 @@ def getSample(self): return sample @security.public - def getSiblings(self): - """Returns the list of analyses of the Analysis Request to which this - analysis belongs to, but with the current analysis excluded + def getSiblings(self, retracted=False): + """ + Returns the list of analyses of the Analysis Request to which this + analysis belongs to, but with the current analysis excluded. + :param retracted: If false, retracted/rejected siblings are dismissed + :type retracted: bool + :return: list of siblings for this analysis + :rtype: list of IAnalysis """ - siblings = [] request = self.getRequest() - if request: - ans = request.getAnalyses(full_objects=True) - siblings = [an for an in ans if an.UID() != self.UID()] + if not request: + return [] + + siblings = [] + retracted_states = [STATE_RETRACTED, STATE_REJECTED] + ans = request.getAnalyses(full_objects=True) + for sibling in ans: + if sibling.UID() == self.UID(): + # Exclude me from the list + continue + + if retracted is False and in_state(sibling, retracted_states): + # Exclude retracted analyses + continue + + siblings.append(sibling) + return siblings diff --git a/bika/lims/content/duplicateanalysis.py b/bika/lims/content/duplicateanalysis.py index 4339ff606b..a1137bb8ef 100644 --- a/bika/lims/content/duplicateanalysis.py +++ b/bika/lims/content/duplicateanalysis.py @@ -14,6 +14,8 @@ from bika.lims.interfaces import IDuplicateAnalysis from bika.lims.interfaces.analysis import IRequestAnalysis from bika.lims.subscribers import skip +from bika.lims.workflow import in_state +from bika.lims.workflow.analysis import STATE_RETRACTED, STATE_REJECTED from bika.lims.workflow.duplicateanalysis import events from zope.interface import implements @@ -70,10 +72,15 @@ def getWorksheet(self): return self.aq_parent @security.public - def getSiblings(self): - """Returns the list of duplicate analyses that share the same Request - and are included in the same Worksheet as the current. The current - duplicate is excluded from the list + def getSiblings(self, retracted=False): + """ + Return the list of duplicate analyses that share the same Request and + are included in the same Worksheet as the current analysis. The current + duplicate is excluded from the list. + :param retracted: If false, retracted/rejected siblings are dismissed + :type retracted: bool + :return: list of siblings for this analysis + :rtype: list of IAnalysis """ worksheet = self.getWorksheet() requestuid = self.getRequestUID() @@ -81,16 +88,29 @@ def getSiblings(self): return [] siblings = [] + retracted_states = [STATE_RETRACTED, STATE_REJECTED] analyses = worksheet.getAnalyses() for analysis in analyses: if analysis.UID() == self.UID(): # Exclude me from the list continue - if IRequestAnalysis.providedBy(analysis): - # We exclude here all analyses that do not have an analysis - # request associated (e.g. IReferenceAnalysis) - if analysis.getRequestUID() == requestuid: - siblings.append(analysis) + + if IRequestAnalysis.providedBy(analysis) is False: + # Exclude analyses that do not have an analysis request + # associated + continue + + if analysis.getRequestUID() != requestuid: + # Exclude those analyses that does not belong to the same + # analysis request I belong to + continue + + if retracted is False and in_state(analysis, retracted_states): + # Exclude retracted analyses + continue + + siblings.append(analysis) + return siblings @security.public diff --git a/bika/lims/content/referenceanalysis.py b/bika/lims/content/referenceanalysis.py index 4dfb838967..ca39453bec 100644 --- a/bika/lims/content/referenceanalysis.py +++ b/bika/lims/content/referenceanalysis.py @@ -135,7 +135,7 @@ def getServiceDefaultInstrumentURL(self): return ins.absolute_url_path() return '' - def getDependencies(self): + def getDependencies(self, retracted=False): """It doesn't make sense for a ReferenceAnalysis to use dependencies, since them are only used in calculations for routine analyses diff --git a/bika/lims/workflow/__init__.py b/bika/lims/workflow/__init__.py index f959304c3b..2b95a9cb49 100644 --- a/bika/lims/workflow/__init__.py +++ b/bika/lims/workflow/__init__.py @@ -376,6 +376,13 @@ def getCurrentState(obj, stateflowid='review_state'): wf = getToolByName(obj, 'portal_workflow') return wf.getInfoFor(obj, stateflowid, '') +def in_state(obj, states, stateflowid='review_state'): + """ Returns if the object passed matches with the states passed in + """ + if not states: + return False + obj_state = getCurrentState(obj, stateflowid=stateflowid) + return obj_state in states def getTransitionActor(obj, action_id): """Returns the actor that performed a given transition. If transition has diff --git a/bika/lims/workflow/analysis/__init__.py b/bika/lims/workflow/analysis/__init__.py index e69de29bb2..cfe775729c 100644 --- a/bika/lims/workflow/analysis/__init__.py +++ b/bika/lims/workflow/analysis/__init__.py @@ -0,0 +1,2 @@ +STATE_REJECTED = 'rejected' +STATE_RETRACTED = 'retracted'