Passed
Push — 2.x ( c4c08d...a84de2 )
by Jordi
18:37
created

bika.lims.content.batch.Batch.Title()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
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-2024 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 import deprecated
25
from bika.lims.browser.fields import UIDReferenceField
26
from bika.lims.browser.fields.remarksfield import RemarksField
27
from bika.lims.browser.widgets import DateTimeWidget
28
from bika.lims.browser.widgets import RemarksWidget
29
from bika.lims.config import PROJECTNAME
30
from bika.lims.content.bikaschema import BikaFolderSchema
31
from bika.lims.content.clientawaremixin import ClientAwareMixin
32
from bika.lims.interfaces import IBatch
33
from bika.lims.interfaces import ICancellable
34
from bika.lims.interfaces import IClient
35
from DateTime.DateTime import DateTime
36
from plone.app.folder.folder import ATFolder
37
from plone.indexer import indexer
38
from Products.Archetypes.public import DateTimeField
39
from Products.Archetypes.public import DisplayList
40
from Products.Archetypes.public import LinesField
41
from Products.Archetypes.public import MultiSelectionWidget
42
from Products.Archetypes.public import Schema
43
from Products.Archetypes.public import StringField
44
from Products.Archetypes.public import StringWidget
45
from Products.Archetypes.public import registerType
46
from Products.CMFCore.utils import getToolByName
47
from senaite.core.browser.widgets.referencewidget import ReferenceWidget
48
from senaite.core.catalog import CLIENT_CATALOG
49
from senaite.core.catalog import SAMPLE_CATALOG
50
from zope.interface import implements
51
52
53
@indexer(IBatch)
54
def BatchDate(instance):
55
    return instance.Schema().getField('BatchDate').get(instance)
56
57
58
schema = BikaFolderSchema.copy() + Schema((
59
60
    StringField(
61
        'BatchID',
62
        required=False,
63
        validators=('uniquefieldvalidator',),
64
        widget=StringWidget(
65
            # XXX This field can never hold a user value, because it is
66
            #     invisible (see custom getBatchID getter method)
67
            # => we should remove that field
68
            visible=False,
69
            label=_("Batch ID"),
70
        )
71
    ),
72
73
    UIDReferenceField(
74
        "Client",
75
        required=0,
76
        allowed_types=("Client",),
77
        widget=ReferenceWidget(
78
            label=_(
79
                "label_batch_client",
80
                default="Client"),
81
            description=_(
82
                "description_batch_client",
83
                default="Select the client of this batch"),
84
            catalog=CLIENT_CATALOG,
85
            query={
86
                "is_active": True,
87
                "sort_on": "sortable_title",
88
                "sort_order": "ascending"
89
            },
90
            columns=[
91
                {"name": "Title", "label": _("Name")},
92
                {"name": "getClientID", "label": _("Client ID")},
93
            ],
94
        ),
95
    ),
96
97
    StringField(
98
        'ClientBatchID',
99
        required=0,
100
        widget=StringWidget(
101
            label=_("Client Batch ID")
102
        )
103
    ),
104
105
    DateTimeField(
106
        'BatchDate',
107
        required=False,
108
        default_method=DateTime,
109
        widget=DateTimeWidget(
110
            label=_('Date'),
111
        ),
112
    ),
113
114
    LinesField(
115
        'BatchLabels',
116
        vocabulary="BatchLabelVocabulary",
117
        accessor="getLabelNames",
118
        widget=MultiSelectionWidget(
119
            label=_("Batch Labels"),
120
            format="checkbox",
121
        )
122
    ),
123
124
    RemarksField(
125
        'Remarks',
126
        widget=RemarksWidget(
127
            label=_('Remarks'),
128
        )
129
    ),
130
))
131
132
# Remove implicit `uniquefieldvalidator` coming from `BikaFolderSchema`
133
schema['title'].validators = ()
134
schema['title'].widget.description = _("If no value is entered, the Batch ID"
135
                                       " will be auto-generated.")
136
schema['title'].required = False
137
schema['title'].widget.visible = True
138
schema['title'].widget.description = _("If no Title value is entered,"
139
                                       " the Batch ID will be used.")
140
schema['description'].required = False
141
schema['description'].widget.visible = True
142
143
schema.moveField('ClientBatchID', before='description')
144
schema.moveField('BatchID', before='description')
145
schema.moveField('title', before='description')
146
schema.moveField('Client', after='title')
147
148
149
class Batch(ATFolder, ClientAwareMixin):
150
    """A Batch combines multiple ARs into a logical unit
151
    """
152
    implements(IBatch, ICancellable)
153
154
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
155
    displayContentsTab = False
156
    security = ClassSecurityInfo()
157
    _at_rename_after_creation = True
158
159
    def _renameAfterCreation(self, check_auto_id=False):
160
        from senaite.core.idserver import renameAfterCreation
161
        renameAfterCreation(self)
162
163
    def Title(self):
164
        """Returns the Title or ID
165
        """
166
        title = self.getField("title").get(self)
167
        return title or self.getId()
168
169
    def getClient(self):
170
        """Retrieves the Client the current Batch is assigned to
171
        """
172
        # We override here getClient from ClientAwareMixin because te schema's
173
        # field Client is only used to allow the user to assign the batch to a
174
        # client in edit form. The entered value is used in
175
        # ObjectModifiedEventHandler to move the batch to the Client's folder,
176
        # so the value stored in the Schema's is not used anymore
177
        # See https://github.com/senaite/senaite.core/pull/1450
178
        client = self.aq_parent
179
        if IClient.providedBy(client):
180
            return client
181
        return None
182
183
    def getContactTitle(self):
184
        return ""
185
186
    def getProfilesTitle(self):
187
        return ""
188
189
    def getAnalysisService(self):
190
        analyses = set()
191
        for ar in self.getAnalysisRequests():
192
            for an in ar.getAnalyses():
193
                analyses.add(an)
194
        value = []
195
        for analysis in analyses:
196
            val = analysis.Title
197
            if val not in value:
198
                value.append(val)
199
        return list(value)
200
201
    def getAnalysts(self):
202
        analyses = []
203
        for ar in self.getAnalysisRequests():
204
            analyses += list(ar.getAnalyses(full_objects=True))
205
        value = []
206
        for analysis in analyses:
207
            val = analysis.getAnalyst()
208
            if val not in value:
209
                value.append(val)
210
        return value
211
212
    security.declarePublic('getBatchID')
213
214
    @deprecated("Please use getId instead")
215
    def getBatchID(self):
216
        # NOTE This method is a custom getter of the invisible field "BatchID".
217
        #      Therefore, it is unlikely that it returns anything else than `getId`.
218
        if self.BatchID:
219
            return self.BatchID
220
        if self.checkCreationFlag():
221
            return self.BatchID
222
        return self.getId()
223
224
    def BatchLabelVocabulary(self):
225
        """Return all batch labels as a display list
226
        """
227
        bsc = getToolByName(self, 'senaite_catalog_setup')
228
        ret = []
229
        for p in bsc(portal_type='BatchLabel',
230
                     is_active=True,
231
                     sort_on='sortable_title'):
232
            ret.append((p.UID, p.Title))
233
        return DisplayList(ret)
234
235
    def getAnalysisRequestsBrains(self, **kwargs):
236
        """Return all the Analysis Requests brains linked to the Batch
237
        kargs are passed directly to the catalog.
238
        """
239
        kwargs["getBatchUID"] = self.UID()
240
        return api.search(kwargs, SAMPLE_CATALOG)
241
242
    def getAnalysisRequests(self, **kwargs):
243
        """Return all the Analysis Requests objects linked to the Batch kargs
244
        are passed directly to the catalog.
245
        """
246
        brains = self.getAnalysisRequestsBrains(**kwargs)
247
        return [b.getObject() for b in brains]
248
249
    def isOpen(self):
250
        """Returns true if the Batch is in 'open' state
251
        """
252
        return api.get_workflow_status_of(self) not in ["cancelled", "closed"]
253
254
    def getLabelNames(self):
255
        uc = getToolByName(self, 'uid_catalog')
256
        uids = [uid for uid in self.Schema().getField('BatchLabels').get(self)]
257
        labels = [label.getObject().title for label in uc(UID=uids)]
258
        return labels
259
260
    def getProgress(self):
261
        """Returns the progress in percent of all samples
262
        """
263
        total_progress = 0
264
        samples = self.getAnalysisRequests()
265
        total = len(samples)
266
        if total > 0:
267
            sample_progresses = map(lambda s: s.getProgress(), samples)
268
            total_progress = sum(sample_progresses) / total
269
        return total_progress
270
271
272
registerType(Batch, PROJECTNAME)
273