Passed
Push — 2.x ( 2cb80c...f3c998 )
by Ramon
07:26
created

FolderView.remove_review_state()   A

Complexity

Conditions 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
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-2021 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import collections
22
import json
23
24
from bika.lims import api
25
from bika.lims import bikaMessageFactory as _
26
from bika.lims.browser.bika_listing import BikaListingView
27
from bika.lims.utils import get_display_list
28
from bika.lims.utils import get_link
29
from bika.lims.utils import get_progress_bar_html
30
from bika.lims.utils import getUsers
31
from bika.lims.utils import user_fullname
32
from Products.Archetypes.config import REFERENCE_CATALOG
33
from Products.CMFCore.utils import getToolByName
34
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
35
from senaite.core.catalog import WORKSHEET_CATALOG
36
from senaite.core.permissions import AddWorksheet
37
from senaite.core.permissions.worksheet import can_add_worksheet
38
from senaite.core.permissions.worksheet import can_edit_worksheet
39
from senaite.core.permissions.worksheet import can_manage_worksheets
40
from plone.memoize.view import memoize
41
42
43
class FolderView(BikaListingView):
44
    """Listing view for Worksheets
45
    """
46
    template = ViewPageTemplateFile("../templates/worksheets.pt")
47
48
    def __init__(self, context, request):
49
        super(FolderView, self).__init__(context, request)
50
51
        self.catalog = WORKSHEET_CATALOG
52
        self.contentFilter = {
53
            "review_state": ["open", "to_be_verified", "verified", "rejected"],
54
            "sort_on": "created",
55
            "sort_order": "reverse"
56
        }
57
58
        self.title = self.context.translate(_("Worksheets"))
59
        self.description = ""
60
61
        self.icon = "{}/{}".format(
62
            self.portal_url,
63
            "++plone++senaite.core.static/assets/icons/worksheet.svg"
64
        )
65
66
        self.context_actions = {
67
            _("Add"): {
68
                "url": "worksheet_add",
69
                "icon": "++resource++bika.lims.images/add.png",
70
                "class": "worksheet_add",
71
                "permission": AddWorksheet,
72
            }
73
        }
74
75
        self.show_select_column = True
76
        self.show_select_all_checkbox = True
77
        self.selected_state = ""
78
        self.analyst_choices = []
79
        self.can_reassign = False
80
        self.can_manage = False
81
82
        self.rc = getToolByName(self, REFERENCE_CATALOG)
83
84
        # this is a property of self, because self.getAnalysts returns it
85
        self.analysts = getUsers(self, ["Manager", "LabManager", "Analyst"])
86
        self.analysts = self.analysts.sortedByValue()
87
        self.analyst_choices = []
88
        for a in self.analysts:
89
            self.analyst_choices.append({
90
                "ResultValue": a,
91
                "ResultText": self.analysts.getValue(a),
92
            })
93
94
        self.columns = collections.OrderedDict((
95
            ("getProgressPercentage", {
96
               "title": _("Progress")}),
97
            ("Title", {
98
                "title": _("Worksheet"),
99
                "index": "getId"}),
100
            ("Analyst", {
101
                "title": _("Analyst"),
102
                "index": "getAnalyst"}),
103
            ("getWorksheetTemplateTitle", {
104
                "title": _("Template"),
105
                "replace_url": "getWorksheetTemplateURL"}),
106
            ("getNumberOfRegularSamples", {
107
                "title": _("Samples")}),
108
            ("getNumberOfQCAnalyses", {
109
                "title": _("QC Analyses")}),
110
            ("getNumberOfRegularAnalyses", {
111
                "title": _("Routine Analyses")}),
112
            ("CreationDate", {
113
                "title": _("Created"),
114
                "index": "created"}),
115
            ("state_title", {
116
                "title": _("State"),
117
                "index": "review_state",
118
                "attr": "state_title"}),
119
        ))
120
        self.review_states = [
121
            {
122
                "id": "default",
123
                "title": _("Active"),
124
                "contentFilter": {
125
                    "review_state": [
126
                        "open",
127
                        "to_be_verified",
128
                    ],
129
                    "sort_on": "CreationDate",
130
                    "sort_order": "reverse"},
131
                "transitions": [],
132
                "custom_transitions": [],
133
                "columns": self.columns.keys(),
134
            }, {
135
                "id": "open",
136
                "title": _("Open"),
137
                "contentFilter": {
138
                    "review_state": "open",
139
                    "sort_on": "CreationDate",
140
                    "sort_order": "reverse"},
141
                "transitions": [],
142
                "custom_transitions": [],
143
                "columns": self.columns.keys(),
144
            }, {
145
                "id": "to_be_verified",
146
                "title": _("To be verified"),
147
                "contentFilter": {
148
                    "review_state": "to_be_verified",
149
                    "sort_on": "CreationDate",
150
                    "sort_order": "reverse"},
151
                "transitions": [],
152
                "custom_transitions": [],
153
                "columns": self.columns.keys()
154
            }, {
155
                "id": "verified",
156
                "title": _("Verified"),
157
                "contentFilter": {
158
                    "review_state": "verified",
159
                    "sort_on": "CreationDate",
160
                    "sort_order": "reverse"
161
                },
162
                "transitions": [],
163
                "custom_transitions": [],
164
                "columns": self.columns.keys(),
165
            }, {
166
                "id": "all",
167
                "title": _("All"),
168
                "contentFilter": {
169
                    "review_state": [
170
                        "open",
171
                        "to_be_verified",
172
                        "verified",
173
                        "rejected",
174
                    ],
175
                    "sort_on":"CreationDate",
176
                    "sort_order": "reverse"},
177
                "transitions":[],
178
                "custom_transitions": [],
179
                "columns": self.columns.keys(),
180
            }, {
181
                # getAuthenticatedMember does not work in __init__ so "mine" is
182
                # configured further in "folderitems" below.
183
                "id": "mine",
184
                "title": _("Mine"),
185
                "contentFilter": {
186
                    "review_state": [
187
                        "open",
188
                        "to_be_verified",
189
                        "verified",
190
                        "rejected"
191
                    ],
192
                    "sort_on":"CreationDate",
193
                    "sort_order": "reverse"},
194
                "transitions":[],
195
                "custom_transitions": [],
196
                "columns": self.columns.keys(),
197
            }
198
        ]
199
200
    def before_render(self):
201
        """Before render hook of the listing base view
202
        """
203
        super(FolderView, self).before_render()
204
205
        # disable the editable border of the Add-, Display- and Workflow menu
206
        self.request.set("disable_border", 1)
207
208
        # the current selected WF state
209
        self.selected_state = self.get_selected_state()
210
211
        self.allow_edit = self.is_edit_allowed()
212
        self.can_manage = self.is_manage_allowed()
213
214
        # Check if analysts can be assigned
215
        if self.is_analyst_assignment_allowed():
216
            self.can_reassign = True
217
            self.allow_analyst_reassignment()
218
219
        if not self.can_manage:
220
            # The current has no prvileges to manage WS.
221
            # Remove the add button
222
            self.context_actions = {}
223
224
        if self.show_only_mine():
225
            # Remove 'Mine' button and hide 'Analyst' column
226
            self.remove_review_state("mine")
227
            self.columns["Analyst"]["toggle"] = False
228
            self.contentFilter["getAnalyst"] = self.member.id
229
            for rvw in self.review_states:
230
                rvw["contentFilter"]["getAnalyst"] = self.member.id
231
232
    def remove_review_state(self, id):
233
        """Removes the review status button with the given id
234
        """
235
        ids = [review_state["id"] for review_state in self.review_states]
236
        if id not in ids:
237
            return
238
        index = ids.index(id)
239
        del self.review_states[index]
240
241
    def is_privileged_user(self):
242
        """Returns whether the current user is a privileged member
243
        """
244
        privileged = ["Manager", "LabManager", "RegulatoryInspector"]
245
        user_roles = self.member.getRoles()
246
        if set(privileged).intersection(user_roles):
247
            return True
248
        return False
249
250
    @memoize
251
    def show_only_mine(self):
252
        """Returns whether only the worksheets assigned to current user have
253
        to be displayed or not
254
        """
255
        # do not filter if user is a privileged member
256
        if self.is_privileged_user():
257
            return False
258
259
        # rely on setup's setting
260
        setup = api.get_setup()
261
        return setup.getRestrictWorksheetUsersAccess()
262
263
    def is_analyst_assignment_allowed(self):
264
        """Check if the analyst can be assigned
265
        """
266
        if not self.allow_edit:
267
            return False
268
        if not self.can_manage:
269
            return False
270
        if self.show_only_mine():
271
            return False
272
        return True
273
274
    def allow_analyst_reassignment(self):
275
        """Allow the Analyst reassignment
276
        """
277
        reassing_analyst_transition = {
278
            "id": "reassign",
279
            "title": _("Reassign")}
280
        for rs in self.review_states:
281
            if rs["id"] not in ["default", "mine", "open", "all"]:
282
                continue
283
            rs["custom_transitions"].append(reassing_analyst_transition)
284
        self.show_select_column = True
285
        self.show_workflow_action_buttons = True
286
287
    def can_add(self):
288
        """Check if the user is allowed to add a worksheet
289
        """
290
        return can_add_worksheet(self.context)
291
292
    def is_manage_allowed(self):
293
        """Check if the User is allowed to manage
294
        """
295
        return can_manage_worksheets(self.context)
296
297
    def is_edit_allowed(self):
298
        """Check if edit is allowed
299
        """
300
        return can_edit_worksheet(self.context)
301
302
    def get_selected_state(self):
303
        """Returns the current selected state
304
        """
305
        form_key = "{}_review_state".format(self.form_id)
306
        return self.request.get(form_key, "default")
307
308
    def folderitem(self, obj, item, index):
309
        """Service triggered each time an item is iterated in folderitems.
310
311
        The use of this service prevents the extra-loops in child objects.
312
313
        :obj: the instance of the class to be foldered
314
        :item: dict containing the properties of the object to be used by
315
            the template
316
        :index: current index of the item
317
        """
318
319
        title = api.get_title(obj)
320
        url = api.get_url(obj)
321
322
        item["CreationDate"] = self.ulocalized_time(obj.created)
323
324
        title_link = "{}/{}".format(url, "add_analyses")
325
        if len(obj.getAnalysesUIDs) > 0:
326
            title_link = "{}/{}".format(url, "manage_results")
327
328
        item["Title"] = title
329
        item["replace"]["Title"] = get_link(title_link, value=title)
330
331
        # Total QC Analyses
332
        item["getNumberOfQCAnalyses"] = str(
333
            obj.getNumberOfQCAnalyses)
334
        # Total Routine Analyses
335
        item["getNumberOfRegularAnalyses"] = str(
336
            obj.getNumberOfRegularAnalyses)
337
        # Total Number of Samples
338
        item["getNumberOfRegularSamples"] = str(
339
            obj.getNumberOfRegularSamples)
340
341
        # Progress
342
        progress = obj.getProgressPercentage
343
        progress_bar_html = get_progress_bar_html(progress)
344
        item["replace"]["getProgressPercentage"] = progress_bar_html
345
346
        review_state = item["review_state"]
347
        if self.can_reassign and review_state == "open":
348
            item["Analyst"] = obj.getAnalyst
349
            item["allow_edit"] = ["Analyst"]
350
            item["required"] = ["Analyst"]
351
            item["choices"] = {"Analyst": self.analyst_choices}
352
        else:
353
            fullname = user_fullname(self.context, obj.getAnalyst)
354
            item["Analyst"] = fullname
355
356
        return item
357
358
    def getAnalysts(self):
359
        """Returns all analysts
360
        """
361
        return self.analysts
362
363
    def getWorksheetTemplates(self):
364
        """Returns a DisplayList with all active worksheet templates
365
366
        :return: DisplayList of worksheet templates (uid, title)
367
        :rtype: DisplayList
368
        """
369
        brains = self._get_worksheet_templates_brains()
370
        return get_display_list(brains)
371
372
    def getInstruments(self):
373
        """Returns a DisplayList with all active Instruments
374
375
        :return: DisplayList of worksheet templates (uid, title)
376
        :rtype: DisplayList
377
        """
378
        brains = self._get_instruments_brains()
379
        return get_display_list(brains)
380
381
    def getTemplateInstruments(self):
382
        """Returns worksheet templates as JSON
383
        """
384
        items = dict()
385
        templates = self._get_worksheet_templates_brains()
386
        for template in templates:
387
            template_obj = api.get_object(template)
388
            uid_template = api.get_uid(template_obj)
389
            instrument = template_obj.getInstrument()
390
            uid_instrument = ""
391
            if instrument:
392
                uid_instrument = api.get_uid(instrument)
393
            items[uid_template] = uid_instrument
394
395
        return json.dumps(items)
396
397
    def _get_worksheet_templates_brains(self):
398
        """Returns all active worksheet templates
399
400
        :returns: list of worksheet template brains
401
        """
402
        query = {
403
            "portal_type": "WorksheetTemplate",
404
            "is_active": True,
405
        }
406
        return api.search(query, "senaite_catalog_setup")
407
408
    def _get_instruments_brains(self):
409
        """Returns all active Instruments
410
411
        :returns: list of brains
412
        """
413
        query = {
414
            "portal_type": "Instrument",
415
            "is_active": True
416
        }
417
        return api.search(query, "senaite_catalog_setup")
418