Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot verify calculated analyses if retracted dependencies #439

Merged
merged 10 commits into from
Dec 1, 2017
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions bika/lims/content/abstractanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")

Expand Down
42 changes: 31 additions & 11 deletions bika/lims/content/abstractroutineanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
34 changes: 27 additions & 7 deletions bika/lims/content/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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((
Expand All @@ -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


Expand Down
38 changes: 29 additions & 9 deletions bika/lims/content/duplicateanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -70,27 +72,45 @@ 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()
if not requestuid or not worksheet:
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
Expand Down
2 changes: 1 addition & 1 deletion bika/lims/content/referenceanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions bika/lims/workflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that getCurrentState returns an empty string as default (return wf.getInfoFor(obj, stateflowid, ''))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I take note, this change requires a new PR imo, because has nothing to do with in_state rather with getCurrentState.


def getTransitionActor(obj, action_id):
"""Returns the actor that performed a given transition. If transition has
Expand Down
2 changes: 2 additions & 0 deletions bika/lims/workflow/analysis/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
STATE_REJECTED = 'rejected'
STATE_RETRACTED = 'retracted'