Passed
Pull Request — 2.x (#1962)
by Ramon
05:22
created

senaite.core.browser.samples.view   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 752
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 69
eloc 568
dl 0
loc 752
rs 2.88
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
F SamplesView.folderitem() 0 152 29
A SamplesView.to_datetime_input_value() 0 6 2
A SamplesView.get_copy_to_new_transition() 0 21 5
A SamplesView.is_printing_workflow_enabled() 0 4 1
A SamplesView.flat_listing() 0 3 1
A SamplesView.before_render() 0 17 3
A SamplesView.str_date() 0 4 2
A SamplesView.getDefaultAddCount() 0 2 1
B SamplesView.add_custom_transitions() 0 29 5
B SamplesView.__init__() 0 382 1
A SamplesView.update() 0 17 1
B SamplesView.purge_review_states() 0 16 6
A SamplesView.purge_columns() 0 10 4
A SamplesView.can_create_worksheet() 0 15 5
A SamplesView.show_partitions() 0 8 3

How to fix   Complexity   

Complexity

Complex classes like senaite.core.browser.samples.view often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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
22
import collections
23
24
from bika.lims import _
25
from bika.lims import api
26
from bika.lims.api.security import check_permission
27
from bika.lims.config import PRIORITIES
28
from bika.lims.interfaces import IBatch
29
from bika.lims.interfaces import IClient
30
from bika.lims.permissions import AddAnalysisRequest
31
from bika.lims.permissions import EditWorksheet
32
from bika.lims.permissions import TransitionSampleSample
33
from bika.lims.utils import get_image
34
from bika.lims.utils import get_progress_bar_html
35
from bika.lims.utils import getUsers
36
from bika.lims.utils import t
37
from DateTime import DateTime
38
from senaite.app.listing import ListingView
39
from senaite.core.catalog import SAMPLE_CATALOG
40
from senaite.core.interfaces import ISamples
41
from senaite.core.interfaces import ISamplesView
42
from zope.interface import implementer
43
44
45
@implementer(ISamplesView)
46
class SamplesView(ListingView):
47
    """Listing View for Samples (AnalysisRequest content type) in the System
48
    """
49
50
    def __init__(self, context, request):
51
        super(SamplesView, self).__init__(context, request)
52
53
        self.catalog = SAMPLE_CATALOG
54
        self.contentFilter = {
55
            "sort_on": "created",
56
            "sort_order": "descending",
57
            "isRootAncestor": True,  # only root ancestors
58
        }
59
60
        self.title = self.context.translate(_("Samples"))
61
        self.description = ""
62
63
        self.show_select_column = True
64
        self.form_id = "samples"
65
        self.context_actions = {}
66
        self.icon = "{}{}".format(
67
            self.portal_url, "/senaite_theme/icon/sample")
68
69
        self.url = api.get_url(self.context)
70
71
        # Toggle some columns if the sampling workflow is enabled
72
        sampling_enabled = api.get_setup().getSamplingWorkflowEnabled()
73
74
        now = DateTime().strftime("%Y-%m-%d %H:%M")
75
76
        self.columns = collections.OrderedDict((
77
            ("Priority", {
78
                "title": "",
79
                "index": "getPrioritySortkey",
80
                "sortable": True, }),
81
            ("Progress", {
82
                "title": "Progress",
83
                "index": "getProgress",
84
                "sortable": True,
85
                "toggle": True}),
86
            ("getId", {
87
                "title": _("Sample ID"),
88
                "attr": "getId",
89
                "replace_url": "getURL",
90
                "index": "getId"}),
91
            ("getClientOrderNumber", {
92
                "title": _("Client Order"),
93
                "sortable": True,
94
                "toggle": False}),
95
            ("Creator", {
96
                "title": _("Creator"),
97
                "index": "getCreatorFullName",
98
                "sortable": True,
99
                "toggle": True}),
100
            ("Created", {
101
                "title": _("Date Registered"),
102
                "index": "created",
103
                "toggle": False}),
104
            ("SamplingDate", {
105
                "title": _("Expected Sampling Date"),
106
                "index": "getSamplingDate",
107
                "toggle": sampling_enabled}),
108
            ("getDateSampled", {
109
                "title": _("Date Sampled"),
110
                "toggle": True,
111
                "type": "datetime",
112
                "max": now,
113
                "sortable": True}),
114
            ("getDatePreserved", {
115
                "title": _("Date Preserved"),
116
                "toggle": False,
117
                "type": "datetime",
118
                "max": now,
119
                "sortable": False}),  # no datesort without index
120
            ("getDateReceived", {
121
                "title": _("Date Received"),
122
                "toggle": False}),
123
            ("getDueDate", {
124
                "title": _("Due Date"),
125
                "toggle": False}),
126
            ("getDateVerified", {
127
                "title": _("Date Verified"),
128
                "input_width": "10",
129
                "toggle": False}),
130
            ("getDatePublished", {
131
                "title": _("Date Published"),
132
                "toggle": False}),
133
            ("BatchID", {
134
                "title": _("Batch ID"),
135
                "index": "getBatchID",
136
                "sortable": True,
137
                "toggle": False}),
138
            ("Client", {
139
                "title": _("Client"),
140
                "index": "getClientTitle",
141
                "attr": "getClientTitle",
142
                "replace_url": "getClientURL",
143
                "toggle": True}),
144
            ("ClientID", {
145
                "title": _("Client ID"),
146
                "index": "getClientID",
147
                "attr": "getClientID",
148
                "replace_url": "getClientURL",
149
                "toggle": True}),
150
            ("Province", {
151
                "title": _("Province"),
152
                "sortable": True,
153
                "index": "getProvince",
154
                "attr": "getProvince",
155
                "toggle": False}),
156
            ("District", {
157
                "title": _("District"),
158
                "sortable": True,
159
                "index": "getDistrict",
160
                "attr": "getDistrict",
161
                "toggle": False}),
162
            ("getClientReference", {
163
                "title": _("Client Ref"),
164
                "sortable": True,
165
                "index": "getClientReference",
166
                "toggle": False}),
167
            ("getClientSampleID", {
168
                "title": _("Client SID"),
169
                "toggle": False}),
170
            ("ClientContact", {
171
                "title": _("Contact"),
172
                "sortable": True,
173
                "index": "getContactFullName",
174
                "toggle": False}),
175
            ("getSampleTypeTitle", {
176
                "title": _("Sample Type"),
177
                "sortable": True,
178
                "toggle": True}),
179
            ("getSamplePointTitle", {
180
                "title": _("Sample Point"),
181
                "sortable": True,
182
                "index": "getSamplePointTitle",
183
                "toggle": False}),
184
            ("getStorageLocation", {
185
                "title": _("Storage Location"),
186
                "sortable": True,
187
                "index": "getStorageLocationTitle",
188
                "toggle": False}),
189
            ("SamplingDeviation", {
190
                "title": _("Sampling Deviation"),
191
                "sortable": True,
192
                "index": "getSamplingDeviationTitle",
193
                "toggle": False}),
194
            ("getSampler", {
195
                "title": _("Sampler"),
196
                "toggle": sampling_enabled}),
197
            ("getPreserver", {
198
                "title": _("Preserver"),
199
                "sortable": False,
200
                "toggle": False}),
201
            ("getProfilesTitle", {
202
                "title": _("Profile"),
203
                "sortable": True,
204
                "index": "getProfilesTitle",
205
                "toggle": False}),
206
            ("getAnalysesNum", {
207
                "title": _("Number of Analyses"),
208
                "sortable": True,
209
                "index": "getAnalysesNum",
210
                "toggle": False}),
211
            ("getTemplateTitle", {
212
                "title": _("Template"),
213
                "sortable": True,
214
                "index": "getTemplateTitle",
215
                "toggle": False}),
216
            ("Printed", {
217
                "title": _("Printed"),
218
                "sortable": False,
219
                "index": "getPrinted",
220
                "toggle": False}),
221
            ("state_title", {
222
                "title": _("State"),
223
                "sortable": True,
224
                "index": "review_state"}),
225
        ))
226
227
        # custom print transition
228
        print_stickers = {
229
            "id": "print_stickers",
230
            "title": _("Print stickers"),
231
            "url": "{}/workflow_action?action=print_stickers".format(self.url)
232
        }
233
234
        self.review_states = [
235
            {
236
                "id": "default",
237
                "title": _("Active"),
238
                "contentFilter": {
239
                    "review_state": (
240
                        "sample_registered",
241
                        "scheduled_sampling",
242
                        "to_be_sampled",
243
                        "sample_due",
244
                        "sample_received",
245
                        "to_be_preserved",
246
                        "to_be_verified",
247
                        "verified",
248
                    ),
249
                    "sort_on": "created",
250
                    "sort_order": "descending",
251
                },
252
                "custom_transitions": [print_stickers],
253
                "columns": self.columns.keys(),
254
            }, {
255
                "id": "to_be_sampled",
256
                "title": _("To Be Sampled"),
257
                "contentFilter": {
258
                    "review_state": ("to_be_sampled",),
259
                    "sort_on": "created",
260
                    "sort_order": "descending"},
261
                "custom_transitions": [print_stickers],
262
                "columns": self.columns.keys()
263
            }, {
264
                "id": "to_be_preserved",
265
                "title": _("To Be Preserved"),
266
                "contentFilter": {
267
                    "review_state": ("to_be_preserved",),
268
                    "sort_on": "created",
269
                    "sort_order": "descending",
270
                },
271
                "custom_transitions": [print_stickers],
272
                "columns": self.columns.keys(),
273
            }, {
274
                "id": "scheduled_sampling",
275
                "title": _("Scheduled sampling"),
276
                "contentFilter": {
277
                    "review_state": ("scheduled_sampling",),
278
                    "sort_on": "created",
279
                    "sort_order": "descending",
280
                },
281
                "custom_transitions": [print_stickers],
282
                "columns": self.columns.keys(),
283
            }, {
284
                "id": "sample_due",
285
                "title": _("Due"),
286
                "contentFilter": {
287
                    "review_state": (
288
                        "to_be_sampled",
289
                        "to_be_preserved",
290
                        "sample_due"),
291
                    "sort_on": "created",
292
                    "sort_order": "descending"},
293
                "custom_transitions": [print_stickers],
294
                "columns": self.columns.keys(),
295
            }, {
296
                "id": "sample_received",
297
                "title": _("Received"),
298
                "contentFilter": {
299
                    "review_state": "sample_received",
300
                    "sort_on": "created",
301
                    "sort_order": "descending",
302
                },
303
                "custom_transitions": [print_stickers],
304
                "columns": self.columns.keys(),
305
            }, {
306
                "id": "to_be_verified",
307
                "title": _("To be verified"),
308
                "contentFilter": {
309
                    "review_state": "to_be_verified",
310
                    "sort_on": "created",
311
                    "sort_order": "descending",
312
                },
313
                "custom_transitions": [print_stickers],
314
                "columns": self.columns.keys(),
315
            }, {
316
                "id": "verified",
317
                "title": _("Verified"),
318
                "contentFilter": {
319
                    "review_state": "verified",
320
                    "sort_on": "created",
321
                    "sort_order": "descending",
322
                },
323
                "custom_transitions": [print_stickers],
324
                "columns": self.columns.keys(),
325
            }, {
326
                "id": "published",
327
                "title": _("Published"),
328
                "contentFilter": {
329
                    "review_state": ("published"),
330
                    "sort_on": "created",
331
                    "sort_order": "descending",
332
                },
333
                "custom_transitions": [],
334
                "columns": self.columns.keys(),
335
            }, {
336
                "id": "dispatched",
337
                "title": _("Dispatched"),
338
                "flat_listing": True,
339
                "confirm_transitions": ["restore"],
340
                "contentFilter": {
341
                    "review_state": ("dispatched"),
342
                    "sort_on": "created",
343
                    "sort_order": "descending",
344
                },
345
                "custom_transitions": [],
346
                "columns": self.columns.keys(),
347
            }, {
348
                "id": "cancelled",
349
                "title": _("Cancelled"),
350
                "contentFilter": {
351
                    "review_state": "cancelled",
352
                    "sort_on": "created",
353
                    "sort_order": "descending",
354
                },
355
                "custom_transitions": [],
356
                "columns": self.columns.keys(),
357
            }, {
358
                "id": "invalid",
359
                "title": _("Invalid"),
360
                "contentFilter": {
361
                    "review_state": "invalid",
362
                    "sort_on": "created",
363
                    "sort_order": "descending",
364
                },
365
                "custom_transitions": [print_stickers],
366
                "columns": self.columns.keys(),
367
            }, {
368
                "id": "all",
369
                "title": _("All"),
370
                "contentFilter": {
371
                    "sort_on": "created",
372
                    "sort_order": "descending",
373
                },
374
                "custom_transitions": [print_stickers],
375
                "columns": self.columns.keys(),
376
            }, {
377
                "id": "rejected",
378
                "title": _("Rejected"),
379
                "contentFilter": {
380
                    "review_state": "rejected",
381
                    "sort_on": "created",
382
                    "sort_order": "descending",
383
                },
384
                "custom_transitions": [print_stickers],
385
                "columns": self.columns.keys(),
386
            }, {
387
                "id": "assigned",
388
                "title": get_image("assigned.png",
389
                                   title=t(_("Assigned"))),
390
                "contentFilter": {
391
                    "assigned_state": "assigned",
392
                    "review_state": ("sample_received",),
393
                    "sort_on": "created",
394
                    "sort_order": "descending",
395
                },
396
                "custom_transitions": [print_stickers],
397
                "columns": self.columns.keys(),
398
            }, {
399
                "id": "unassigned",
400
                "title": get_image("unassigned.png",
401
                                   title=t(_("Unsassigned"))),
402
                "contentFilter": {
403
                    "assigned_state": "unassigned",
404
                    "review_state": (
405
                        "sample_received",
406
                    ),
407
                    "sort_on": "created",
408
                    "sort_order": "descending",
409
                },
410
                "custom_transitions": [print_stickers],
411
                "columns": self.columns.keys(),
412
            }, {
413
                "id": "late",
414
                "title": get_image("late.png",
415
                                   title=t(_("Late"))),
416
                "contentFilter": {
417
                    # Query only for unpublished ARs that are late
418
                    "review_state": (
419
                        "sample_received",
420
                        "to_be_verified",
421
                        "verified",
422
                    ),
423
                    "getDueDate": {
424
                        "query": DateTime(),
425
                        "range": "max",
426
                    },
427
                    "sort_on": "created",
428
                    "sort_order": "descending",
429
                },
430
                "custom_transitions": [print_stickers],
431
                "columns": self.columns.keys(),
432
            }
433
        ]
434
435
    def update(self):
436
        """Called before the listing renders
437
        """
438
        super(SamplesView, self).update()
439
440
        self.workflow = api.get_tool("portal_workflow")
441
        self.member = self.mtool.getAuthenticatedMember()
442
        self.roles = self.member.getRoles()
443
444
        # Remove unnecessary filters
445
        self.purge_review_states()
446
447
        # Remove unnecessary columns
448
        self.purge_columns()
449
450
        # Additional custom transitions
451
        self.add_custom_transitions()
452
453
    def before_render(self):
454
        """Before template render hook
455
        """
456
        # If the current user is a client contact, display those analysis
457
        # requests that belong to same client only
458
        super(SamplesView, self).before_render()
459
        client = api.get_current_client()
460
        if client:
461
            self.contentFilter['path'] = {
462
                "query": "/".join(client.getPhysicalPath()),
463
                "level": 0}
464
            # No need to display the Client column
465
            self.remove_column('Client')
466
467
        # remove query filter for root samples when listing is flat
468
        if self.flat_listing:
469
            self.contentFilter.pop("isRootAncestor", None)
470
471
    def folderitem(self, obj, item, index):
472
        # Additional info from AnalysisRequest to be added in the item
473
        # generated by default by bikalisting.
474
        # Call the folderitem method from the base class
475
        item = super(SamplesView, self).folderitem(obj, item, index)
476
        if not item:
477
            return None
478
479
        item["Creator"] = self.user_fullname(obj.Creator)
480
        # If we redirect from the folderitems view we should check if the
481
        # user has permissions to medify the element or not.
482
        priority_sort_key = obj.getPrioritySortkey
483
        if not priority_sort_key:
484
            # Default priority is Medium = 3.
485
            # The format of PrioritySortKey is <priority>.<created>
486
            priority_sort_key = "3.%s" % obj.created.ISO8601()
487
        priority = priority_sort_key.split(".")[0]
488
        priority_text = PRIORITIES.getValue(priority)
489
        priority_div = """<div class="priority-ico priority-%s">
490
                          <span class="notext">%s</span><div>
491
                       """
492
        item["replace"]["Priority"] = priority_div % (priority, priority_text)
493
        item["replace"]["getProfilesTitle"] = obj.getProfilesTitleStr
494
495
        analysesnum = obj.getAnalysesNum
496
        if analysesnum:
497
            num_verified = str(analysesnum[0])
498
            num_total = str(analysesnum[1])
499
            item["getAnalysesNum"] = "{0}/{1}".format(num_verified, num_total)
500
        else:
501
            item["getAnalysesNum"] = ""
502
503
        # Progress
504
        progress_perc = obj.getProgress
505
        item["Progress"] = progress_perc
506
        item["replace"]["Progress"] = get_progress_bar_html(progress_perc)
507
508
        item["BatchID"] = obj.getBatchID
509
        if obj.getBatchID:
510
            item['replace']['BatchID'] = "<a href='%s'>%s</a>" % \
511
                                         (obj.getBatchURL, obj.getBatchID)
512
        # TODO: SubGroup ???
513
        # val = obj.Schema().getField('SubGroup').get(obj)
514
        # item['SubGroup'] = val.Title() if val else ''
515
516
        item["SamplingDate"] = self.str_date(obj.getSamplingDate)
517
        item["getDateSampled"] = self.str_date(obj.getDateSampled)
518
        item["getDateReceived"] = self.str_date(obj.getDateReceived)
519
        item["getDueDate"] = self.str_date(obj.getDueDate)
520
        item["getDatePublished"] = self.str_date(obj.getDatePublished)
521
        item["getDateVerified"] = self.str_date(obj.getDateVerified)
522
523
        if self.is_printing_workflow_enabled:
524
            item["Printed"] = ""
525
            printed = obj.getPrinted if hasattr(obj, "getPrinted") else "0"
526
            print_icon = ""
527
            if printed == "0":
528
                print_icon = get_image("delete.png",
529
                                       title=t(_("Not printed yet")))
530
            elif printed == "1":
531
                print_icon = get_image("ok.png",
532
                                       title=t(_("Printed")))
533
            elif printed == "2":
534
                print_icon = get_image(
535
                    "exclamation.png",
536
                    title=t(_("Republished after last print")))
537
            item["after"]["Printed"] = print_icon
538
        item["SamplingDeviation"] = obj.getSamplingDeviationTitle
539
540
        item["getStorageLocation"] = obj.getStorageLocationTitle
541
542
        after_icons = ""
543
        if obj.assigned_state == 'assigned':
544
            after_icons += get_image("worksheet.png",
545
                                     title=t(_("All analyses assigned")))
546
        if item["review_state"] == 'invalid':
547
            after_icons += get_image("delete.png",
548
                                     title=t(_("Results have been withdrawn")))
549
550
        due_date = obj.getDueDate
551
        if due_date and due_date < (obj.getDatePublished or DateTime()):
552
            due_date_str = self.ulocalized_time(due_date)
553
            img_title = "{}: {}".format(t(_("Late Analyses")), due_date_str)
554
            after_icons += get_image("late.png", title=img_title)
555
556
        if obj.getSamplingDate and obj.getSamplingDate > DateTime():
557
            after_icons += get_image("calendar.png",
558
                                     title=t(_("Future dated sample")))
559
        if obj.getInvoiceExclude:
560
            after_icons += get_image("invoice_exclude.png",
561
                                     title=t(_("Exclude from invoice")))
562
        if obj.getHazardous:
563
            after_icons += get_image("hazardous.png",
564
                                     title=t(_("Hazardous")))
565
566
        if obj.getInternalUse:
567
            after_icons += get_image("locked.png", title=t(_("Internal use")))
568
569
        if after_icons:
570
            item['after']['getId'] = after_icons
571
572
        item['Created'] = self.ulocalized_time(obj.created, long_format=1)
573
        if obj.getContactUID:
574
            item['ClientContact'] = obj.getContactFullName
575
            item['replace']['ClientContact'] = "<a href='%s'>%s</a>" % \
576
                                               (obj.getContactURL, obj.getContactFullName)
577
        else:
578
            item["ClientContact"] = ""
579
        # TODO-performance: If SamplingWorkflowEnabled, we have to get the
580
        # full object to check the user permissions, so far this is
581
        # a performance hit.
582
        if obj.getSamplingWorkflowEnabled:
583
584
            sampler = obj.getSampler
585
            if sampler:
586
                item["getSampler"] = obj.getSampler
587
                item["replace"]["getSampler"] = obj.getSamplerFullName
588
589
            # sampling workflow - inline edits for Sampler and Date Sampled
590
            if item["review_state"] == "to_be_sampled":
591
                # We need to get the full object in order to check
592
                # the permissions
593
                full_object = api.get_object(obj)
594
                if check_permission(TransitionSampleSample, full_object):
595
                    # make fields required and editable
596
                    item["required"] = ["getSampler", "getDateSampled"]
597
                    item["allow_edit"] = ["getSampler", "getDateSampled"]
598
                    date = obj.getDateSampled or DateTime()
599
                    # provide date and time in a valid input format
600
                    item["getDateSampled"] = self.to_datetime_input_value(date)
601
                    sampler_roles = ["Sampler", "LabManager", ""]
602
                    samplers = getUsers(full_object, sampler_roles)
603
                    users = [({
604
                        "ResultValue": u,
605
                        "ResultText": samplers.getValue(u)}) for u in samplers]
606
                    item["choices"] = {"getSampler": users}
607
                    # preselect the current user as sampler
608
                    if not sampler and "Sampler" in self.roles:
609
                        sampler = self.member.getUserName()
610
                        item["getSampler"] = sampler
611
612
        # These don't exist on ARs
613
        # XXX This should be a list of preservers...
614
        item["getPreserver"] = ""
615
        item["getDatePreserved"] = ""
616
617
        # Assign parent and children partitions of this sample
618
        if self.show_partitions:
619
            item["parent"] = obj.getRawParentAnalysisRequest
620
            item["children"] = obj.getDescendantsUIDs or []
621
622
        return item
623
624
    def purge_review_states(self):
625
        """Purges unnecessary review statuses
626
        """
627
        remove_filters = []
628
        setup = api.get_bika_setup()
629
        if not setup.getSamplingWorkflowEnabled():
630
            remove_filters.append("to_be_sampled")
631
        if not setup.getScheduleSamplingEnabled():
632
            remove_filters.append("scheduled_sampling")
633
        if not setup.getSamplePreservationEnabled():
634
            remove_filters.append("to_be_preserved")
635
        if not setup.getRejectionReasons():
636
            remove_filters.append("rejected")
637
638
        self.review_states = filter(lambda r: r.get("id") not in remove_filters,
639
                                    self.review_states)
640
641
    def purge_columns(self):
642
        """Purges unnecessary columns
643
        """
644
        remove_columns = []
645
        if not self.is_printing_workflow_enabled:
646
            remove_columns.append("Printed")
647
648
        for rv in self.review_states:
649
            cols = rv.get("columns", [])
650
            rv["columns"] = filter(lambda c: c not in remove_columns, cols)
651
652
    def add_custom_transitions(self):
653
        """Adds custom transitions as required
654
        """
655
        custom_transitions = []
656
        if self.is_printing_workflow_enabled:
657
            custom_transitions.append({
658
                "id": "print_sample",
659
                "title": _("Print"),
660
                "url": "{}/workflow_action?action={}".format(
661
                    self.url, "print_sample")
662
            })
663
664
        copy_to_new = self.get_copy_to_new_transition()
665
        if copy_to_new:
666
            custom_transitions.append(copy_to_new)
667
668
        # Allow to create a worksheet for the selected samples
669
        if self.can_create_worksheet():
670
            custom_transitions.append({
671
                "id": "modal_create_worksheet",
672
                "title": _("Create Worksheet"),
673
                "url": "{}/create_worksheet_modal".format(
674
                    api.get_url(self.context)),
675
                "css_class": "btn btn-outline-secondary",
676
                "help": _("Create a new worksheet for the selected samples")
677
            })
678
679
        for rv in self.review_states:
680
            rv.setdefault("custom_transitions", []).extend(custom_transitions)
681
682
    def get_copy_to_new_transition(self):
683
        """Returns the copy to new custom transition if the current has enough
684
        privileges. Returns None otherwise
685
        """
686
        base_url = None
687
        mtool = api.get_tool("portal_membership")
688
        if mtool.checkPermission(AddAnalysisRequest, self.context):
689
            base_url = self.url
690
        else:
691
            client = api.get_current_client()
692
            if client and mtool.checkPermission(AddAnalysisRequest, client):
693
                base_url = api.get_url(client)
694
695
        if base_url:
696
            return {
697
                "id": "copy_to_new",
698
                "title": _("Copy to new"),
699
                "url": "{}/workflow_action?action=copy_to_new".format(base_url)
700
            }
701
702
        return None
703
704
    def can_create_worksheet(self):
705
        """Checks if the create worksheet transition should be rendered or not
706
        """
707
        if not check_permission(EditWorksheet, self.context):
708
            return False
709
710
        # restrict contexts to well known places
711
        if ISamples.providedBy(self.context):
712
            return True
713
        elif IBatch.providedBy(self.context):
714
            return True
715
        elif IClient.providedBy(self.context):
716
            return True
717
        else:
718
            return False
719
720
    @property
721
    def is_printing_workflow_enabled(self):
722
        setup = api.get_setup()
723
        return setup.getPrintingWorkflowEnabled()
724
725
    def str_date(self, date, long_format=1, default=""):
726
        if not date:
727
            return default
728
        return self.ulocalized_time(date, long_format=long_format)
729
730
    def to_datetime_input_value(self, date):
731
        """Converts to a compatible datetime format
732
        """
733
        if not isinstance(date, DateTime):
734
            return ""
735
        return date.strftime("%Y-%m-%d %H:%M")
736
737
    def getDefaultAddCount(self):
738
        return self.context.bika_setup.getDefaultNumberOfARsToAdd()
739
740
    @property
741
    def show_partitions(self):
742
        if self.flat_listing:
743
            return False
744
        if api.get_current_client():
745
            # If current user is a client contact, delegate to ShowPartitions
746
            return api.get_setup().getShowPartitions()
747
        return True
748
749
    @property
750
    def flat_listing(self):
751
        return self.review_state.get("flat_listing", False)
752