Passed
Push — 2.x ( 041ff6...87c3c9 )
by Jordi
08:54
created

  A

Complexity

Conditions 4

Size

Total Lines 10
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 10
rs 9.9
c 0
b 0
f 0
cc 4
nop 1
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-2021 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
from AccessControl import ClassSecurityInfo
22
from bika.lims import api
23
from bika.lims import bikaMessageFactory as _
24
from bika.lims.browser.fields import ReferenceResultsField
25
from bika.lims.browser.fields import UIDReferenceField
26
from bika.lims.browser.widgets import DateTimeWidget as bika_DateTimeWidget
27
from bika.lims.browser.widgets import ReferenceResultsWidget
28
from bika.lims.config import PROJECTNAME
29
from bika.lims.content.bikaschema import BikaSchema
30
from bika.lims.interfaces import IDeactivable
31
from bika.lims.interfaces import IReferenceSample
32
from DateTime import DateTime
33
from Products.Archetypes.atapi import registerType
34
from Products.Archetypes.BaseFolder import BaseFolder
35
from Products.Archetypes.config import REFERENCE_CATALOG
36
from Products.Archetypes.Field import BooleanField
37
from Products.Archetypes.Field import ComputedField
38
from Products.Archetypes.Field import DateTimeField
39
from Products.Archetypes.Field import StringField
40
from Products.Archetypes.Field import TextField
41
from Products.Archetypes.Schema import Schema
42
from Products.Archetypes.Widget import BooleanWidget
43
from Products.Archetypes.Widget import ComputedWidget
44
from Products.Archetypes.Widget import StringWidget
45
from Products.Archetypes.Widget import TextAreaWidget
46
from Products.CMFCore.utils import getToolByName
47
from senaite.core.browser.widgets.referencewidget import ReferenceWidget
48
from senaite.core.catalog import SETUP_CATALOG
49
from zope.interface import implements
50
51
schema = BikaSchema.copy() + Schema((
52
    UIDReferenceField(
53
        "ReferenceDefinition",
54
        schemata="Description",
55
        allowed_types=("ReferenceDefinition",),
56
        widget=ReferenceWidget(
57
            label=_(
58
                "label_referencesample_referencedefinition",
59
                default="Reference Definition"),
60
            description=_(
61
                "description_referencesample_referencedefinition",
62
                default="Select the reference definition for this sample"),
63
            catalog=SETUP_CATALOG,
64
            query={
65
                "is_active": True,
66
                "sort_on": "sortable_title",
67
                "sort_order": "ascending"
68
            },
69
        ),
70
    ),
71
    BooleanField('Blank',
72
        schemata = 'Description',
73
        default = False,
74
        widget = BooleanWidget(
75
            label=_("Blank"),
76
            description=_("Reference sample values are zero or 'blank'"),
77
        ),
78
    ),
79
    BooleanField('Hazardous',
80
        schemata = 'Description',
81
        default = False,
82
        widget = BooleanWidget(
83
            label=_("Hazardous"),
84
            description=_("Samples of this type should be treated as hazardous"),
85
        ),
86
    ),
87
88
    UIDReferenceField(
89
        "Manufacturer",
90
        schemata="Description",
91
        allowed_types=("Manufacturer",),
92
        vocabulary="getManufacturers",
93
        widget=ReferenceWidget(
94
            label=_(
95
                "label_referencesample_manufacturer",
96
                default="Manufacturer"),
97
            description=_(
98
                "description_referencesample_manufacturer",
99
                default="Select the manufacturer for this sample"),
100
            catalog=SETUP_CATALOG,
101
            query={
102
                "is_active": True,
103
                "sort_on": "sortable_title",
104
                "sort_order": "ascending"
105
            },
106
        ),
107
    ),
108
109
    StringField('CatalogueNumber',
110
        schemata = 'Description',
111
        widget = StringWidget(
112
            label=_("Catalogue Number"),
113
        ),
114
    ),
115
    StringField('LotNumber',
116
        schemata = 'Description',
117
        widget = StringWidget(
118
            label=_("Lot Number"),
119
        ),
120
    ),
121
    TextField(
122
        "Remarks",
123
        allowable_content_types=("text/plain",),
124
        schemata="Description",
125
        widget=TextAreaWidget(
126
            label=_("Remarks"),
127
        )
128
    ),
129
    DateTimeField('DateSampled',
130
        schemata = 'Dates',
131
        widget = bika_DateTimeWidget(
132
            label=_("Date Sampled"),
133
        ),
134
    ),
135
    DateTimeField('DateReceived',
136
        schemata = 'Dates',
137
        default_method = 'current_date',
138
        widget = bika_DateTimeWidget(
139
            label=_("Date Received"),
140
        ),
141
    ),
142
    DateTimeField('DateOpened',
143
        schemata = 'Dates',
144
        widget = bika_DateTimeWidget(
145
            label=_("Date Opened"),
146
        ),
147
    ),
148
    DateTimeField('ExpiryDate',
149
        schemata = 'Dates',
150
        required = 1,
151
        widget = bika_DateTimeWidget(
152
            label=_("Expiry Date"),
153
        ),
154
    ),
155
    DateTimeField('DateExpired',
156
        schemata = 'Dates',
157
        widget = bika_DateTimeWidget(
158
            label=_("Date Expired"),
159
            visible = {'edit':'hidden'},
160
        ),
161
    ),
162
    DateTimeField('DateDisposed',
163
        schemata = 'Dates',
164
        widget = bika_DateTimeWidget(
165
            label=_("Date Disposed"),
166
            visible = {'edit':'hidden'},
167
        ),
168
    ),
169
    ReferenceResultsField('ReferenceResults',
170
        schemata = 'Reference Values',
171
        required = 1,
172
        subfield_validators = {
173
                    'result':'referencevalues_validator',},
174
        widget = ReferenceResultsWidget(
175
            label=_("Expected Values"),
176
        ),
177
    ),
178
    ComputedField('SupplierUID',
179
        expression = 'context.aq_parent.UID()',
180
        widget = ComputedWidget(
181
            visible = False,
182
        ),
183
    ),
184
    ComputedField('ReferenceDefinitionUID',
185
        expression = 'here.getReferenceDefinition() and here.getReferenceDefinition().UID() or None',
186
        widget = ComputedWidget(
187
            visible = False,
188
        ),
189
    ),
190
))
191
192
schema['title'].schemata = 'Description'
193
194
195
class ReferenceSample(BaseFolder):
196
    implements(IReferenceSample, IDeactivable)
197
    security = ClassSecurityInfo()
198
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
199
    _at_rename_after_creation = True
200
201
    def _renameAfterCreation(self, check_auto_id=False):
202
        from senaite.core.idserver import renameAfterCreation
203
        renameAfterCreation(self)
204
205
    security.declarePublic('current_date')
206
    def current_date(self):
207
        return DateTime()
208
209
    security.declarePublic('getResultsRangeDict')
210
    def getResultsRangeDict(self):
211
        specs = {}
212
        for spec in self.getReferenceResults():
213
            uid = spec['uid']
214
            specs[uid] = {}
215
            specs[uid]['result'] = spec['result']
216
            specs[uid]['min'] = spec.get('min', '')
217
            specs[uid]['max'] = spec.get('max', '')
218
        return specs
219
220
    def getSupportedServices(self, only_uids=True):
221
        """Return a list with the services supported by this reference sample,
222
        those for which there is a valid results range assigned in reference
223
        results
224
        :param only_uids: returns a list of uids or a list of objects
225
        :return: list of uids or AnalysisService objects
226
        """
227
        uids = map(lambda range: range['uid'], self.getReferenceResults())
228
        uids = filter(api.is_uid, uids)
229
        if only_uids:
230
            return uids
231
        brains = api.search({'UID': uids}, 'uid_catalog')
232
        return map(api.get_object, brains)
233
234
    security.declarePublic('getReferenceAnalyses')
235
    def getReferenceAnalyses(self):
236
        """ return all analyses linked to this reference sample """
237
        return self.objectValues('ReferenceAnalysis')
238
239
    security.declarePublic('getServices')
240
    def getServices(self):
241
        """ get all services for this Sample """
242
        tool = getToolByName(self, REFERENCE_CATALOG)
243
        services = []
244
        for spec in self.getReferenceResults():
245
            service = tool.lookupObject(spec['uid'])
246
            services.append(service)
247
        return services
248
249
    def isValid(self):
250
        """
251
        Returns if the current Reference Sample is valid. This is, the sample
252
        hasn't neither been expired nor disposed.
253
        """
254
        today = DateTime()
255
        expiry_date = self.getExpiryDate()
256
        if expiry_date and today > expiry_date:
257
            return False
258
        # TODO: Do We really need ExpiryDate + DateExpired? Any difference?
259
        date_expired = self.getDateExpired()
260
        if date_expired and today > date_expired:
261
            return False
262
263
        date_disposed = self.getDateDisposed()
264
        if date_disposed and today > date_disposed:
265
            return False
266
267
        return True
268
269
    # XXX workflow methods
270
    def workflow_script_expire(self):
271
        """ expire sample """
272
        self.setDateExpired(DateTime())
273
        self.reindexObject()
274
275
    def workflow_script_dispose(self):
276
        """ dispose sample """
277
        self.setDateDisposed(DateTime())
278
        self.reindexObject()
279
280
281
registerType(ReferenceSample, PROJECTNAME)
282