Passed
Push — master ( c0868d...0b39e4 )
by Ramon
04:34
created

DuplicateAnalysis.getSiblings()   C

Complexity

Conditions 9

Size

Total Lines 41
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 41
rs 6.6666
c 0
b 0
f 0
cc 9
nop 2
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE.
4
#
5
# SENAITE.CORE is free software: you can redistribute it and/or modify it under
6
# the terms of the GNU General Public License as published by the Free Software
7
# Foundation, version 2.
8
#
9
# This program is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
# details.
13
#
14
# You should have received a copy of the GNU General Public License along with
15
# this program; if not, write to the Free Software Foundation, Inc., 51
16
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
#
18
# Copyright 2018-2019 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
from AccessControl import ClassSecurityInfo
22
from Products.Archetypes.public import Schema, registerType
23
from Products.Archetypes.public import StringField
24
from bika.lims import api
25
from bika.lims.browser.fields import UIDReferenceField
26
from bika.lims.config import PROJECTNAME
27
from bika.lims.content.abstractroutineanalysis import AbstractRoutineAnalysis
28
from bika.lims.content.abstractroutineanalysis import schema
29
from bika.lims.content.analysisspec import ResultsRangeDict
30
from bika.lims.interfaces import IDuplicateAnalysis
31
from bika.lims.interfaces import ISubmitted
32
from bika.lims.interfaces.analysis import IRequestAnalysis
33
from bika.lims import logger
34
from bika.lims.workflow import in_state
35
from bika.lims.workflow.analysis import STATE_RETRACTED, STATE_REJECTED
36
from zope.interface import implements
37
38
# A reference back to the original analysis from which this one was duplicated.
39
Analysis = UIDReferenceField(
40
    'Analysis',
41
    required=1,
42
    allowed_types=('Analysis', 'ReferenceAnalysis'),
43
)
44
45
# TODO Analysis - Duplicates shouldn't have this attribute, only ReferenceAns
46
ReferenceAnalysesGroupID = StringField(
47
    'ReferenceAnalysesGroupID',
48
)
49
50
schema = schema.copy() + Schema((
51
    Analysis,
52
    ReferenceAnalysesGroupID,
53
))
54
55
56
class DuplicateAnalysis(AbstractRoutineAnalysis):
57
    implements(IDuplicateAnalysis)
58
    security = ClassSecurityInfo()
59
    displayContentsTab = False
60
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
61
62
    @security.public
63
    def getRequest(self):
64
        """Returns the Analysis Request of the original analysis.
65
        """
66
        analysis = self.getAnalysis()
67
        if analysis:
68
            return analysis.getRequest()
69
70
    @security.public
71
    def getAnalysisPortalType(self):
72
        """This returns the portal_type of the original analysis.
73
        """
74
        analysis = self.getAnalysis()
75
        if analysis:
76
            return analysis.portal_type
77
78
    @security.public
79
    def getWorksheet(self):
80
        return self.aq_parent
81
82
    @security.public
83
    def getSiblings(self, retracted=False):
84
        """
85
        Return the list of duplicate analyses that share the same Request and
86
        are included in the same Worksheet as the current analysis. The current
87
        duplicate is excluded from the list.
88
        :param retracted: If false, retracted/rejected siblings are dismissed
89
        :type retracted: bool
90
        :return: list of siblings for this analysis
91
        :rtype: list of IAnalysis
92
        """
93
        worksheet = self.getWorksheet()
94
        requestuid = self.getRequestUID()
95
        if not requestuid or not worksheet:
96
            return []
97
98
        siblings = []
99
        retracted_states = [STATE_RETRACTED, STATE_REJECTED]
100
        analyses = worksheet.getAnalyses()
101
        for analysis in analyses:
102
            if analysis.UID() == self.UID():
103
                # Exclude me from the list
104
                continue
105
106
            if not IRequestAnalysis.providedBy(analysis):
107
                # Exclude analyses that do not have an analysis request
108
                # associated
109
                continue
110
111
            if analysis.getRequestUID() != requestuid:
112
                # Exclude those analyses that does not belong to the same
113
                # analysis request I belong to
114
                continue
115
116
            if retracted is False and in_state(analysis, retracted_states):
117
                # Exclude retracted analyses
118
                continue
119
120
            siblings.append(analysis)
121
122
        return siblings
123
124
    @security.public
125
    def setAnalysis(self, analysis):
126
        # Copy all the values from the schema
127
        if not analysis:
128
            return
129
        discard = ['id', ]
130
        keys = analysis.Schema().keys()
131
        for key in keys:
132
            if key in discard:
133
                continue
134
            if key not in self.Schema().keys():
135
                continue
136
            val = analysis.getField(key).get(analysis)
137
            self.getField(key).set(self, val)
138
        self.getField('Analysis').set(self, analysis)
139
140
    @security.public
141
    def getResultsRange(self):
142
        """Returns the valid result range for this analysis duplicate, based on
143
        both on the result and duplicate variation set in the original analysis
144
145
        A Duplicate will be out of range if its result does not match with the
146
        result for the parent analysis plus the duplicate variation in % as the
147
        margin error.
148
149
        If the duplicate is from an analysis with result options and/or string
150
        results enabled (with non-numeric value), returns an empty result range
151
152
        :return: A dictionary with the keys min and max
153
        :rtype: dict
154
        """
155
        # Get the original analysis
156
        original_analysis = self.getAnalysis()
157
        if not original_analysis:
158
            logger.warn("Orphan duplicate: {}".format(repr(self)))
159
            return {}
160
161
        # Return empty if results option enabled (exact match expected)
162
        if original_analysis.getResultOptions():
163
            return {}
164
165
        # Return empty if non-floatable (exact match expected)
166
        original_result = original_analysis.getResult()
167
        if not api.is_floatable(original_result):
168
            return {}
169
170
        # Calculate the min/max based on duplicate variation %
171
        specs = ResultsRangeDict(uid=self.getServiceUID())
172
        dup_variation = original_analysis.getDuplicateVariation()
173
        dup_variation = api.to_float(dup_variation, default=0)
174
        if not dup_variation:
175
            # We expect an exact match
176
            specs.min = specs.max = original_result
177
            return specs
178
179
        original_result = api.to_float(original_result)
180
        margin = abs(original_result) * (dup_variation / 100.0)
181
        specs.min = str(original_result - margin)
182
        specs.max = str(original_result + margin)
183
        return specs
184
185
186
registerType(DuplicateAnalysis, PROJECTNAME)
187