BatchBookView.update()   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 2
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-2025 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import re
22
from collections import OrderedDict
23
from collections import defaultdict
24
from operator import itemgetter
25
26
from bika.lims import api
27
from bika.lims.utils import get_link_for
28
from bika.lims import bikaMessageFactory as _
29
from bika.lims.api.security import check_permission
30
from bika.lims.browser.bika_listing import BikaListingView
31
from bika.lims.interfaces import IBatchBookView
32
from senaite.core.permissions import AddAnalysisRequest
33
from senaite.core.permissions import FieldEditAnalysisResult
34
from senaite.core.i18n import translate as t
35
from Products.CMFCore.permissions import ModifyPortalContent
36
from zope.interface import implementer
37
38
DESCRIPTION = _("The batch book allows to introduce analysis results for all "
39
                "samples in this batch. Please note that submitting the "
40
                "results for verification is only possible within samples or "
41
                "worksheets, because additional information like e.g. the "
42
                "instrument or the method need to be set additionally.")
43
44
45
@implementer(IBatchBookView)
46
class BatchBookView(BikaListingView):
47
48
    def __init__(self, context, request):
49
        super(BatchBookView, self).__init__(context, request)
50
51
        self.context_actions = {}
52
        self.contentFilter = {"sort_on": "created"}
53
        self.title = context.Title()
54
        self.description = t(DESCRIPTION)
55
56
        self.show_select_column = True
57
        self.show_search = False
58
        self.pagesize = 999999
59
        self.form_id = "batchbook"
60
        self.show_categories = True
61
        self.expand_all_categories = True
62
        self.show_column_toggles = False
63
64
        self.icon = "{}{}".format(
65
            self.portal_url, "/senaite_theme/icon/batchbook")
66
67
        self.columns = OrderedDict((
68
            ("AnalysisRequest", {
69
                "title": _("Sample"),
70
                "sortable": False,
71
            }),
72
            ("SampleType", {
73
                "title": _("Sample Type"),
74
                "sortable": False,
75
            }),
76
            ("SamplePoint", {
77
                "title": _("Sample Point"),
78
                "sortable": False,
79
            }),
80
            ("ClientOrderNumber", {
81
                "title": _("Client Order Number"),
82
                "sortable": False,
83
            }),
84
            ("created", {
85
                "title": _("Date Registered"),
86
                "sortable": False,
87
            }),
88
            ("state_title", {
89
                "title": _("State"),
90
                "index": "review_state",
91
                "sortable": False,
92
            }),
93
        ))
94
95
        self.review_states = [
96
            {
97
                "id": "default",
98
                "title": _("All"),
99
                "contentFilter": {},
100
                "columns": self.columns.keys()
101
            },
102
        ]
103
104
    def is_copy_to_new_allowed(self):
105
        """Checks if it is allowed to copy the sample
106
        """
107
        if check_permission(AddAnalysisRequest, self.context):
108
            return True
109
        return False
110
111
    def can_edit_analysis_result(self, analysis):
112
        """Checks if the current user can edit the analysis result
113
        """
114
        return check_permission(FieldEditAnalysisResult, analysis)
115
116
    def update(self):
117
        """Update hook
118
        """
119
        super(BatchBookView, self).update()
120
        # check if the use can modify
121
        self.allow_edit = check_permission(ModifyPortalContent, self.context)
122
        # append copy to new transition
123
        if self.is_copy_to_new_allowed():
124
            self.add_copy_transition()
125
126
    def add_copy_transition(self):
127
        """Add copy transtion
128
        """
129
        review_states = []
130
        for review_state in self.review_states:
131
            custom_transitions = review_state.get("custom_transitions", [])
132
            base_url = api.get_url(self.context)
133
            custom_transitions.append({
134
                "id": "copy_to_new",
135
                "title": _("Copy to new"),
136
                "url": "{}/workflow_action?action=copy_to_new".format(base_url)
137
            })
138
            review_state["custom_transitions"] = custom_transitions
139
            review_states.append(review_state)
140
        self.review_states = review_states
141
142
    def before_render(self):
143
        """Before render hook
144
        """
145
        super(BatchBookView, self).before_render()
146
147
    def folderitems(self):
148
        """Accumulate a list of all AnalysisRequest objects contained in
149
        this Batch, as well as those which are inherited.
150
        """
151
        ars = self.context.getAnalysisRequests(is_active=True)
152
        self.total = len(ars)
153
154
        self.categories = []
155
        analyses = defaultdict(list)
156
        items = []
157
        distinct = []  # distinct analyses (each one a different service)
158
        keywords = []
159
        for ar in ars:
160
            for analysis in ar.getAnalyses(full_objects=True):
161
                analyses[ar.getId()].append(analysis)
162
                if analysis.getKeyword() not in keywords:
163
                    # we use a keyword check, because versioned services are !=.
164
                    keywords.append(analysis.getKeyword())
165
                    distinct.append(analysis)
166
167
            batchlink = ""
168
            batch = ar.getBatch()
169
            if batch:
170
                batchlink = get_link_for(batch)
171
172
            arlink = get_link_for(ar)
173
174
            subgroup = ar.getSubGroup()
175
            sub_title = subgroup.Title() if subgroup else t(_("No Subgroup"))
176
            sub_sort = subgroup.getSortKey() if subgroup else "1"
177
            sub_class = re.sub(r"[^A-Za-z\w\d\-\_]", "", sub_title)
178
179
            if [sub_sort, sub_title] not in self.categories:
180
                self.categories.append([sub_sort, sub_title])
181
182
            wf = api.get_tool("portal_workflow")
183
            review_state = api.get_review_status(ar)
184
            state_title = wf.getTitleForStateOnType(
185
                review_state, "AnalysisRequest")
186
187
            item = {
188
                "obj": ar,
189
                "id": ar.id,
190
                "uid": ar.UID(),
191
                "category": sub_title,
192
                "title": ar.Title(),
193
                "type_class": "contenttype-AnalysisRequest",
194
                "url": ar.absolute_url(),
195
                "relative_url": ar.absolute_url(),
196
                "view_url": ar.absolute_url(),
197
                "created": self.ulocalized_time(ar.created(), long_format=1),
198
                "sort_key": ar.created(),
199
                "replace": {
200
                    "Batch": batchlink,
201
                    "AnalysisRequest": arlink,
202
                },
203
                "before": {},
204
                "after": {},
205
                "choices": {},
206
                "class": {"Batch": "Title"},
207
                "state_class": "state-active subgroup_{0}".format(sub_class) if sub_class else "state-active",
208
                "allow_edit": [],
209
                "Batch": "",
210
                "SamplePoint": ar.getSamplePoint().Title() if ar.getSamplePoint() else "",
211
                "SampleType": ar.getSampleType().Title() if ar.getSampleType() else "",
212
                "ClientOrderNumber": ar.getClientOrderNumber(),
213
                "AnalysisRequest": "",
214
                "state_title": state_title,
215
            }
216
            items.append(item)
217
218
        unitstr = '<em class="discreet" style="white-space:nowrap;">%s</em>'
219
220
        # Insert columns for analyses
221
        for d_a in distinct:
222
            keyword = d_a.getKeyword()
223
            short = d_a.getShortTitle()
224
            title = d_a.Title()
225
226
            self.columns[keyword] = {
227
                "title":  short if short else title,
228
                "sortable": False,
229
            }
230
            self.review_states[0]["columns"].insert(
231
                len(self.review_states[0]["columns"]) - 1, keyword)
232
233
            # Insert values for analyses
234
            for i, item in enumerate(items):
235
                for analysis in analyses[item["id"]]:
236
                    if keyword not in items[i]:
237
                        items[i][keyword] = ""
238
                    if analysis.getKeyword() != keyword:
239
                        continue
240
241
                    # check if the user can edit the analysis result
242
                    can_edit_result = self.can_edit_analysis_result(analysis)
243
244
                    calculation = analysis.getCalculation()
245
                    if self.allow_edit and can_edit_result and not calculation:
246
                        items[i]["allow_edit"].append(keyword)
247
                        self.columns[keyword]["ajax"] = True
248
249
                    value = analysis.getResult()
250
                    items[i][keyword] = value
251
                    items[i]["class"][keyword] = ""
252
253
                    if value or (can_edit_result and not calculation):
254
                        unit = unitstr % d_a.getUnit()
255
                        items[i]["after"][keyword] = unit
256
257
                if keyword not in items[i]["class"]:
258
                    items[i]["class"][keyword] = "empty"
259
260
        self.categories.sort()
261
        self.categories = [x[1] for x in self.categories]
262
263
        items = sorted(items, key=itemgetter("sort_key"), reverse=True)
264
265
        return items
266