Skip to content

Commit 7200333

Browse files
authored
Fix sample transition in listings (senaite#1925)
* Don't set sample date if already set * Convert field getter names back to field name * Imports only * Fixed datetime conversion in sample listing when sample wf is active * Fixed workflow adapter for sample transition in listings * Changelog updated * Allow to set min/max values for datetime field
1 parent b0506d8 commit 7200333

File tree

5 files changed

+83
-57
lines changed

5 files changed

+83
-57
lines changed

CHANGES.rst

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Changelog
44
2.2.0 (unreleased)
55
------------------
66

7+
- #1925 Fix sample transition in listings
78
- #1924 Fix Login screen shows message error while rendering plone.htmlhead.socialtags
89
- #1923 Use native date input fields in reports
910
- #1920 Fix indexed attributes not set when adding a new index with catalog API

src/bika/lims/browser/workflow/analysisrequest.py

+28-9
Original file line numberDiff line numberDiff line change
@@ -295,14 +295,17 @@ class WorkflowActionSampleAdapter(WorkflowActionGenericAdapter):
295295

296296
def __call__(self, action, objects):
297297
# Assign the Sampler and DateSampled
298-
transitioned = filter(lambda obj: self.set_sampler_info(obj), objects)
299-
if not transitioned:
300-
return self.redirect(message=_("No changes made"), level="warning")
298+
for obj in objects:
299+
try:
300+
self.set_sampler_info(obj)
301+
except ValueError as e:
302+
return self.redirect(message=str(e), level="warning")
301303

302304
# Trigger "sample" transition
303-
transitioned = self.do_action(action, transitioned)
305+
transitioned = self.do_action(action, objects)
304306
if not transitioned:
305-
return self.redirect(message=_("No changes made"), level="warning")
307+
message = _("Could not transition samples to the sampled state")
308+
return self.redirect(message=message, level="warning")
306309

307310
# Redirect the user to success page
308311
return self.success(transitioned)
@@ -315,13 +318,29 @@ def set_sampler_info(self, sample):
315318
if sample.getSampler() and sample.getDateSampled():
316319
# Sampler and Date Sampled already set. This is correct
317320
return True
318-
sampler = self.get_form_value("Sampler", sample, sample.getSampler())
321+
322+
# Try to get the sampler and date sampled from the request.
323+
# This might happen when the "Sample" transition is triggered from the
324+
# samples listing view (form keys == column names of the listing)
325+
326+
# try to get the sampler from the request
327+
sampler = self.get_form_value("getSampler", sample,
328+
sample.getSampler())
329+
if not sampler:
330+
sid = api.get_id(sample)
331+
raise ValueError(_("Sampler required for sample %s" % sid))
332+
333+
# try to get the date sampled from the request
319334
sampled = self.get_form_value("getDateSampled", sample,
320335
sample.getDateSampled())
321-
if not all([sampler, sampled]):
322-
return False
336+
if not sampled:
337+
sid = api.get_id(sample)
338+
raise ValueError(_("Sample date required for sample %s" % sid))
339+
340+
# set the field values
323341
sample.setSampler(sampler)
324-
sample.setDateSampled(DateTime(sampled))
342+
sample.setDateSampled(sampled)
343+
325344
return True
326345

327346

src/bika/lims/workflow/analysisrequest/events.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,11 @@ def after_sample(analysis_request):
173173
"""Method triggered after "sample" transition for the Analysis Request
174174
passed in is performed
175175
"""
176-
analysis_request.setDateSampled(DateTime())
176+
# The date might be already set by the `Sample` listing workflow action
177+
date_sampled = analysis_request.getDateSampled()
178+
if not date_sampled:
179+
# set to current date when empty
180+
analysis_request.setDateSampled(DateTime())
177181

178182

179183
def after_rollback_to_receive(analysis_request):

src/senaite/core/browser/samples/view.py

+31-43
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
from bika.lims import _
2525
from bika.lims import api
26-
from senaite.core.catalog import SAMPLE_CATALOG
26+
from bika.lims.api.security import check_permission
2727
from bika.lims.config import PRIORITIES
2828
from bika.lims.permissions import AddAnalysisRequest
2929
from bika.lims.permissions import TransitionSampleSample
@@ -33,6 +33,7 @@
3333
from bika.lims.utils import t
3434
from DateTime import DateTime
3535
from senaite.app.listing import ListingView
36+
from senaite.core.catalog import SAMPLE_CATALOG
3637
from senaite.core.interfaces import ISamplesView
3738
from zope.interface import implementer
3839

@@ -66,6 +67,8 @@ def __init__(self, context, request):
6667
# Toggle some columns if the sampling workflow is enabled
6768
sampling_enabled = api.get_setup().getSamplingWorkflowEnabled()
6869

70+
now = DateTime().strftime("%Y-%m-%d %H:%M")
71+
6972
self.columns = collections.OrderedDict((
7073
("Priority", {
7174
"title": "",
@@ -102,12 +105,13 @@ def __init__(self, context, request):
102105
"title": _("Date Sampled"),
103106
"toggle": True,
104107
"type": "datetime",
105-
"input_width": "10"}),
108+
"max": now,
109+
"sortable": True}),
106110
("getDatePreserved", {
107111
"title": _("Date Preserved"),
108112
"toggle": False,
109113
"type": "datetime",
110-
"input_width": "10",
114+
"max": now,
111115
"sortable": False}), # no datesort without index
112116
("getDateReceived", {
113117
"title": _("Date Received"),
@@ -506,6 +510,7 @@ def folderitem(self, obj, item, index):
506510
# item['SubGroup'] = val.Title() if val else ''
507511

508512
item["SamplingDate"] = self.str_date(obj.getSamplingDate)
513+
item["getDateSampled"] = self.str_date(obj.getDateSampled)
509514
item["getDateReceived"] = self.str_date(obj.getDateReceived)
510515
item["getDueDate"] = self.str_date(obj.getDueDate)
511516
item["getDatePublished"] = self.str_date(obj.getDatePublished)
@@ -571,58 +576,34 @@ def folderitem(self, obj, item, index):
571576
# full object to check the user permissions, so far this is
572577
# a performance hit.
573578
if obj.getSamplingWorkflowEnabled:
574-
# We don't do anything with Sampling Date.
575-
# User can modify Sampling date
576-
# inside AR view. In this listing view,
577-
# we only let the user to edit Date Sampled
578-
# and Sampler if he wants to make 'sample' transaction.
579-
if not obj.getDateSampled:
580-
datesampled = self.ulocalized_time(
581-
DateTime(), long_format=True)
582-
item["class"]["getDateSampled"] = "provisional"
583-
else:
584-
datesampled = self.ulocalized_time(obj.getDateSampled,
585-
long_format=True)
586579

587580
sampler = obj.getSampler
588581
if sampler:
582+
item["getSampler"] = obj.getSampler
589583
item["replace"]["getSampler"] = obj.getSamplerFullName
590-
if "Sampler" in self.roles and not sampler:
591-
sampler = self.member.id
592-
item["class"]["getSampler"] = "provisional"
584+
593585
# sampling workflow - inline edits for Sampler and Date Sampled
594-
if item["review_state"] == 'to_be_sampled':
586+
if item["review_state"] == "to_be_sampled":
595587
# We need to get the full object in order to check
596588
# the permissions
597-
full_object = obj.getObject()
598-
checkPermission = \
599-
self.context.portal_membership.checkPermission
600-
601-
# TODO Do we really need this check?
602-
if checkPermission(TransitionSampleSample, full_object):
589+
full_object = api.get_object(obj)
590+
if check_permission(TransitionSampleSample, full_object):
591+
# make fields required and editable
603592
item["required"] = ["getSampler", "getDateSampled"]
604593
item["allow_edit"] = ["getSampler", "getDateSampled"]
605-
# TODO-performance: hit performance while getting the
606-
# sample object...
607-
# TODO Can LabManagers be a Sampler?!
608-
samplers = getUsers(full_object, ["Sampler", ])
609-
username = self.member.getUserName()
594+
date = obj.getDateSampled or DateTime()
595+
# provide date and time in a valid input format
596+
item["getDateSampled"] = self.to_datetime_input_value(date)
597+
sampler_roles = ["Sampler", "LabManager", ""]
598+
samplers = getUsers(full_object, sampler_roles)
610599
users = [({
611600
"ResultValue": u,
612601
"ResultText": samplers.getValue(u)}) for u in samplers]
613-
item['choices'] = {'getSampler': users}
614-
Sampler = sampler and sampler or (username in samplers.keys() and username) or ''
615-
sampler = Sampler
616-
else:
617-
datesampled = self.ulocalized_time(obj.getDateSampled,
618-
long_format=True)
619-
sampler = obj.getSamplerFullName if obj.getSampler else ''
620-
else:
621-
datesampled = self.ulocalized_time(obj.getDateSampled,
622-
long_format=True)
623-
sampler = ""
624-
item["getDateSampled"] = datesampled
625-
item["getSampler"] = sampler
602+
item["choices"] = {"getSampler": users}
603+
# preselect the current user as sampler
604+
if not sampler and "Sampler" in self.roles:
605+
sampler = self.member.getUserName()
606+
item["getSampler"] = sampler
626607

627608
# These don't exist on ARs
628609
# XXX This should be a list of preservers...
@@ -715,6 +696,13 @@ def str_date(self, date, long_format=1, default=""):
715696
return default
716697
return self.ulocalized_time(date, long_format=long_format)
717698

699+
def to_datetime_input_value(self, date):
700+
"""Converts to a compatible datetime format
701+
"""
702+
if not isinstance(date, DateTime):
703+
return ""
704+
return date.strftime("%Y-%m-%d %H:%M")
705+
718706
def getDefaultAddCount(self):
719707
return self.context.bika_setup.getDefaultNumberOfARsToAdd()
720708

src/senaite/core/datamanagers/sample.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,25 @@ def is_field_writeable(self, field):
2828
"""
2929
return field.checkPermission("set", self.context)
3030

31+
def get_field_by_name(self, name):
32+
"""Get the field by name
33+
"""
34+
field = self.fields.get(name)
35+
36+
# try to fetch the field w/o the `get` prefix
37+
# this might be the case is some listings
38+
if field is None:
39+
# ensure we do not have the field setter as column
40+
name = name.split("get", 1)[-1]
41+
field = self.fields.get(name)
42+
43+
return field
44+
3145
def get(self, name):
3246
"""Get sample field
3347
"""
34-
# schema field
35-
field = self.fields.get(name)
48+
# get the schema field
49+
field = self.get_field_by_name(name)
3650

3751
# check if the field exists
3852
if field is None:
@@ -56,8 +70,8 @@ def set(self, name, value):
5670
# set of updated objects
5771
updated_objects = set()
5872

59-
# schema field
60-
field = self.fields.get(name)
73+
# get the schema field
74+
field = self.get_field_by_name(name)
6175

6276
if field is None:
6377
raise AttributeError("Field '{}' not found".format(name))

0 commit comments

Comments
 (0)