Skip to content

Commit

Permalink
Refactored and fixed inconsistencies with Analysis Turnaround Time lo…
Browse files Browse the repository at this point in the history
…gic (#1032)

* Earliness of analysis is not expressed as minutes

Erliness is the remaining time *in minutes* for an analysis to be
completed based on its maximum turn around time. The function was
not returning the value in minutes.

* Simplify a bit

* Added to_minutes function to api

* Simplify getDuration() and getEarliness() functions

* Make Analysis' due date to be based on start process date

It was using the date the sample partition was received instead.

* getLate function from Analysis Request was inconsistent

* Added test for TAT and CHANGES.rst update

* Some cleaning in late analyses portlet (not in use)

* Return setup's default TAT if analysis has no TAT set

* Added to_dhm_format function in API

* First late analysis is enough for the ar to be late too

* Move getMaxTimeAllowed to base class with schema field

* Test updated (default TAT from setup)

* Do not force to choose an analyst in productivity reports

* Change TAT is not displayed correctly. Add days

* Refactor report Analysis turnaround time over time

* Update CHANGES.rst

* utils.formatDuration deprecated in favor of api.to_dhm_format

* Remove function no longer used

* Added upgrade step to refresh getDueDate of Analyses

* Param to_int -> round_to_int in api.to_minutes()

* PEP8

* Return None to be more explicit

* Fix API's get_minutes test
  • Loading branch information
xispa authored and ramonski committed Oct 1, 2018
1 parent caa1882 commit 9d0ad32
Show file tree
Hide file tree
Showing 16 changed files with 574 additions and 270 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Changelog

**Changed**

- #1032 Refactored and fixed inconsistencies with Analysis TAT logic
- #1027 Refactored relationship between invalidated ARs and retests
- #1027 Rename `retract_ar` transition to `invalidate`
- #1012 Refactored Contacts listing
Expand All @@ -28,6 +29,10 @@ Changelog

**Fixed**

- #1030 Earliness of analysis is not expressed as minutes
- #1029 TAT in Analysis TAT over time report does not display days
- #1029 TAT in Analysis TAT over time report with decimals
- #1029 Need to always choose an analyst in productivity reports
- #1034 Attachments assigned to Analyses break and get orphaned when the referenced Analysis was removed
- #1028 Numbers for productivity report "Analyses by client" are all zero
- #1022 Date Received saved as UTC time
Expand Down
28 changes: 28 additions & 0 deletions bika/lims/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from AccessControl.PermissionRole import rolesForPermissionOn

from datetime import datetime
from datetime import timedelta
from DateTime import DateTime

from Products.CMFPlone.utils import base_hasattr, safe_unicode
Expand Down Expand Up @@ -1265,6 +1266,33 @@ def to_date(value, default=None):
return to_date(default)


def to_minutes(days=0, hours=0, minutes=0, seconds=0, milliseconds=0,
round_to_int=True):
"""Returns the computed total number of minutes
"""
total = float(days)*24*60 + float(hours)*60 + float(minutes) + \
float(seconds)/60 + float(milliseconds)/1000/60
return int(round(total)) if round_to_int else total


def to_dhm_format(days=0, hours=0, minutes=0, seconds=0, milliseconds=0):
"""Returns a representation of time in a string in xd yh zm format
"""
minutes = to_minutes(days=days, hours=hours, minutes=minutes,
seconds=seconds, milliseconds=milliseconds)
delta = timedelta(minutes=int(round(minutes)))
d = delta.days
h = delta.seconds // 3600
m = (delta.seconds // 60) % 60
m = m and "{}m ".format(str(m)) or ""
d = d and "{}d ".format(str(d)) or ""
if m and d:
h = "{}h ".format(str(h))
else:
h = h and "{}h ".format(str(h)) or ""
return "".join([d, h, m]).strip()


def to_int(value, default=_marker):
"""Tries to convert the value to int.
Truncates at the decimal point if the value is a float
Expand Down
14 changes: 2 additions & 12 deletions bika/lims/browser/analyses/late.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from AccessControl import getSecurityManager
from DateTime import DateTime
from Products.CMFCore.utils import getToolByName
from bika.lims import api
from bika.lims import bikaMessageFactory as _
from bika.lims.utils import t
from bika.lims.browser.bika_listing import BikaListingView
Expand Down Expand Up @@ -103,18 +104,7 @@ def folderitems(self):
contact.getFullname())
items[x]['DateReceived'] = self.ulocalized_time(sample.getDateReceived())
items[x]['DueDate'] = self.ulocalized_time(obj.getDueDate())

late = DateTime() - obj.getDueDate()
days = int(late / 1)
hours = int((late % 1 ) * 24)
mins = int((((late % 1) * 24) % 1) * 60)
late_str = days and "%s day%s" % (days, days > 1 and 's' or '') or ""
if days < 2:
late_str += hours and " %s hour%s" % (hours, hours > 1 and 's' or '') or ""
if not days and not hours:
late_str = "%s min%s" % (mins, mins > 1 and 's' or '')

items[x]['Late'] = late_str
items[x]['Late'] = api.to_dhm_format(obj.getLateness())
return items

def isItemAllowed(self, obj):
Expand Down
2 changes: 2 additions & 0 deletions bika/lims/browser/analyses/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,8 @@ def _folder_item_duedate(self, analysis_brain, item):
# returns the date when the ReferenceSample expires. If the analysis is
# a duplicate, `getDueDate` returns the due date of the source analysis
due_date = analysis_brain.getDueDate
if not due_date:
return None
due_date_str = self.ulocalized_time(due_date, long_format=0)
item['DueDate'] = due_date_str

Expand Down
14 changes: 7 additions & 7 deletions bika/lims/browser/reports/productivity_analysestats.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
from Products.CMFCore.utils import getToolByName
from bika.lims.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from bika.lims import api
from bika.lims import bikaMessageFactory as _
from bika.lims.utils import t
from bika.lims.utils import formatDateQuery, formatDateParms, formatDuration, \
from bika.lims.utils import formatDateQuery, formatDateParms, \
logged_in_client
from plone.app.layout.globals.interfaces import IViewView
from zope.interface import implements
Expand Down Expand Up @@ -122,16 +123,15 @@ def __call__(self):
services[service_uid]['ave_early'] = ''
else:
avemins = (mins_early) / count_early
services[service_uid]['ave_early'] = formatDuration(self.context,
avemins)
services[service_uid]['ave_early'] = \
api.to_dhm_format(minutes=avemins)
count_late = services[service_uid]['count_late']
mins_late = services[service_uid]['mins_late']
if count_late == 0:
services[service_uid]['ave_late'] = ''
else:
avemins = mins_late / count_late
services[service_uid]['ave_late'] = formatDuration(self.context,
avemins)
services[service_uid]['ave_late'] = api.to_dhm_format(avemins)

# and now lets do the actual report lines
formats = {'columns': 7,
Expand Down Expand Up @@ -265,7 +265,7 @@ def __call__(self):

if total_count_late:
ave_mins = total_mins_late / total_count_late
footline.append({'value': formatDuration(self.context, ave_mins),
footline.append({'value': api.to_dhm_format(minutes=ave_mins),
'class': 'total number'})
else:
footline.append({'value': ''})
Expand All @@ -275,7 +275,7 @@ def __call__(self):

if total_count_early:
ave_mins = total_mins_early / total_count_early
footline.append({'value': formatDuration(self.context, ave_mins),
footline.append({'value': api.to_dhm_format(minutes=ave_mins),
'class': 'total number'})
else:
footline.append({'value': '',
Expand Down
Loading

0 comments on commit 9d0ad32

Please sign in to comment.