From 55980f45852221a996041152d0ee5069d57725e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Thu, 9 Apr 2020 11:12:23 +0200 Subject: [PATCH] Patient listings show wrong age (#180) * Display current age in Patients listing * Remove getAgeSplittedStr and getAge from Patient portal_type * Changelog Co-authored-by: Pau Soliva --- CHANGES.rst | 2 +- bika/health/browser/patients/folder_view.py | 25 ++++--- bika/health/catalog/patient_catalog.py | 1 - bika/health/content/patient.py | 81 ++++----------------- bika/health/upgrade/v01_02_003.py | 31 +++++++- bika/health/utils.py | 64 ++++++++++++++-- 6 files changed, 119 insertions(+), 85 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 36f536ee..11ab4132 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,13 +14,13 @@ Changelog - #163 Cannot override behavior of Patients folder when using `before_render` - #157 Changed Base Catalog Tool - **Removed** - #170 Removal of stale javascripts and css **Fixed** +- #180 Fix Patient listings show wrong age - #181 Patient historic results are not displayed - #172 Fix sporadical errors when contacts do not have a valid email address - #168 Cannot create Patient inside Client (content type not allowed) diff --git a/bika/health/browser/patients/folder_view.py b/bika/health/browser/patients/folder_view.py index 4dac40b4..83e96a78 100644 --- a/bika/health/browser/patients/folder_view.py +++ b/bika/health/browser/patients/folder_view.py @@ -20,20 +20,21 @@ import collections +from plone.app.content.browser.interfaces import IFolderContentsView +from plone.app.layout.globals.interfaces import IViewView +from zope.interface import implements + from bika.health import bikaMessageFactory as _ -from bika.health.interfaces import IPatients -from bika.health.utils import get_resource_url from bika.health.catalog import CATALOG_PATIENTS +from bika.health.interfaces import IPatients from bika.health.permissions import AddPatient +from bika.health.utils import get_age_ymd +from bika.health.utils import get_resource_url from bika.lims import api -from bika.lims.api import security +from bika.lims.api.security import check_permission from bika.lims.browser.bika_listing import BikaListingView from bika.lims.interfaces import IClient from bika.lims.utils import get_link -from plone.app.content.browser.interfaces import IFolderContentsView -from plone.app.layout.globals.interfaces import IViewView -from zope.interface import implements -from bika.lims.api.security import check_permission class PatientsView(BikaListingView): @@ -78,7 +79,7 @@ def __init__(self, context, request): 'toggle': True, 'sortable': False}), - ('getAgeSplittedStr', { + ('age', { 'title': _('Age'), 'toggle': True, 'sortable': False}), @@ -163,7 +164,13 @@ def folderitems(self, full_objects=False, classic=False): return BikaListingView.folderitems(self, classic=classic) def folderitem(self, obj, item, index): - item['getBirthDate'] = self.ulocalized_time(obj.getBirthDate) + # Date of Birth + dob = obj.getBirthDate + item['getBirthDate'] = self.ulocalized_time(dob) + + # Patient's current age + item["age"] = get_age_ymd(dob) + # make the columns patient title, patient ID and client patient ID # redirect to the Analysis Requests of the patient ars_url = "{}/{}".format(api.get_url(obj), "analysisrequests") diff --git a/bika/health/catalog/patient_catalog.py b/bika/health/catalog/patient_catalog.py index 71f548ac..56ae3ba1 100644 --- a/bika/health/catalog/patient_catalog.py +++ b/bika/health/catalog/patient_catalog.py @@ -51,7 +51,6 @@ "getPrimaryReferrerUID", # Columns without index counterpart - "getAgeSplittedStr", "getBirthDate", "getGender", "getMenstrualStatus", diff --git a/bika/health/content/patient.py b/bika/health/content/patient.py index cb20fe0b..90a18743 100644 --- a/bika/health/content/patient.py +++ b/bika/health/content/patient.py @@ -18,9 +18,6 @@ # Copyright 2018-2019 by it's authors. # Some rights reserved, see README and LICENSE. -from datetime import datetime - -from Products.ATContentTypes.utils import DT2dt from Products.ATExtensions.ateapi import RecordsField from Products.Archetypes import atapi from Products.Archetypes.public import * @@ -32,6 +29,7 @@ from bika.health import logger from bika.health.config import * from bika.health.interfaces import IPatient +from bika.health.utils import get_relative_delta from bika.health.utils import translate_i18n as t from bika.health.widgets import SplittedDateWidget from bika.health.widgets.patientmenstrualstatuswidget import \ @@ -151,13 +149,6 @@ label=_('Age'), ), ), - ComputedField( - 'AgeSplittedStr', - expression="context.getAgeSplittedStr()", - widget=ComputedWidget( - visible=False - ), - ), AddressField( 'CountryState', widget=AddressWidget( @@ -923,64 +914,18 @@ def getPatientIdentifiersStr(self): return " ".join(ids) def getAgeSplitted(self): - - if self.getBirthDate(): - dob = DT2dt(self.getBirthDate()).replace(tzinfo=None) - now = datetime.today() - - currentday = now.day - currentmonth = now.month - currentyear = now.year - birthday = dob.day - birthmonth = dob.month - birthyear = dob.year - ageday = currentday - birthday - agemonth = 0 - ageyear = 0 - months31days = [1, 3, 5, 7, 8, 10, 12] - - if ageday < 0: - currentmonth -= 1 - if currentmonth < 1: - currentyear -= 1 - currentmonth = currentmonth + 12 - - dayspermonth = 30 - if currentmonth in months31days: - dayspermonth = 31 - elif currentmonth == 2: - dayspermonth = 28 - if(currentyear % 4 == 0 - and (currentyear % 100 > 0 or currentyear % 400 == 0)): - dayspermonth += 1 - - ageday = ageday + dayspermonth - - agemonth = currentmonth - birthmonth - if agemonth < 0: - currentyear -= 1 - agemonth = agemonth + 12 - - ageyear = currentyear - birthyear - - return [{'year': ageyear, - 'month': agemonth, - 'day': ageday}] - else: - return [{'year': '', - 'month': '', - 'day': ''}] - - def getAge(self): - return self.getAgeSplitted()[0]['year'] - - def getAgeSplittedStr(self): - splitted = self.getAgeSplitted()[0] - arr = [] - arr.append(splitted['year'] and str(splitted['year']) + 'y' or '') - arr.append(splitted['month'] and str(splitted['month']) + 'm' or '') - arr.append(splitted['day'] and str(splitted['day']) + 'd' or '') - return ' '.join(arr) + """Getter used for "AgeSplitted" schema field + """ + data = {"year": "", "month": "", "day": ""} + dob = self.getBirthDate() + if dob: + delta = get_relative_delta(dob) + data.update({ + "year": delta.year, + "month": delta.month, + "day": delta.day, + }) + return [data] def getCountryState(self): return self.getField('CountryState').get(self) \ diff --git a/bika/health/upgrade/v01_02_003.py b/bika/health/upgrade/v01_02_003.py index 77e5d568..2be39ac1 100644 --- a/bika/health/upgrade/v01_02_003.py +++ b/bika/health/upgrade/v01_02_003.py @@ -53,6 +53,13 @@ "bika_health_standard_analysis_request.css", ] +# Metadata from catalogs to remove +METADATA_TO_REMOVE = [ + # AgeSplittedStr was only used in patients listing + # https://github.com/senaite/senaite.health/pull/180 + (CATALOG_PATIENTS, "getAgeSplittedStr"), +] + @upgradestep(PROJECTNAME, version) def upgrade(tool): @@ -98,6 +105,9 @@ def upgrade(tool): # https://github.com/senaite/senaite.health/pulls/172 fix_health_email_addresses(portal) + # Remove stale catalog columns + remove_stale_metadata(portal) + logger.info("{0} upgraded to version {1}".format(PROJECTNAME, version)) return True @@ -157,6 +167,7 @@ def fix_health_email_addresses(portal): fix_email_address(portal, portal_types=portal_types, catalog_id=CATALOG_PATIENTS) + def install_senaite_panic(portal): """Install the senaite.panic addon """ @@ -169,4 +180,22 @@ def install_senaite_panic(portal): if qi.isProductInstalled(profile): logger.info("'{}' is installed".format(profile)) return - qi.installProduct(profile) \ No newline at end of file + qi.installProduct(profile) + + +def remove_stale_metadata(portal): + logger.info("Removing stale metadata ...") + for catalog, column in METADATA_TO_REMOVE: + del_metadata(catalog, column) + logger.info("Removing stale metadata ... [DONE]") + + +def del_metadata(catalog_id, column): + logger.info("Removing '{}' metadata from '{}' ..." + .format(column, catalog_id)) + catalog = api.get_tool(catalog_id) + if column not in catalog.schema(): + logger.info("Metadata '{}' not in catalog '{}' [SKIP]" + .format(column, catalog_id)) + return + catalog.delColumn(column) diff --git a/bika/health/utils.py b/bika/health/utils.py index 07bc0520..11ecfc60 100644 --- a/bika/health/utils.py +++ b/bika/health/utils.py @@ -18,16 +18,20 @@ # Copyright 2018-2019 by it's authors. # Some rights reserved, see README and LICENSE. +from datetime import datetime + +from Products.ATContentTypes.utils import DT2dt +from dateutil.relativedelta import relativedelta +from zope.i18n import translate + from bika.health import logger from bika.health.interfaces import IPatient from bika.lims import api from bika.lims.api import _marker from bika.lims.interfaces import IBatch -from bika.lims.utils import render_html_attributes, to_utf8, to_unicode -from zope.i18n import translate -from Products.Archetypes.utils import addStatusMessage - -from bika.lims.utils import tmpID +from bika.lims.utils import render_html_attributes +from bika.lims.utils import to_unicode +from bika.lims.utils import to_utf8 def get_obj_from_field(instance, fieldname, default=_marker): @@ -163,3 +167,53 @@ def handle_after_submit(context, request, state): else: status_id = "success" return status_id + + +def get_age_ymd(birth_date, to_date=None): + """Returns the age at to_date if not None. Otherwise, current age + """ + delta = get_relative_delta(birth_date, to_date) + return to_ymd(delta) + + +def get_relative_delta(from_date, to_date=None): + """Returns the relative delta between two dates. If to_date is None, + compares the from_date with now + """ + from_date = to_datetime(from_date) + if not from_date: + raise TypeError("Type not supported: from_date") + + to_date = to_date or datetime.now() + to_date = to_datetime(to_date) + if not to_date: + raise TypeError("Type not supported: to_date") + + return relativedelta(to_date, from_date) + + +def to_datetime(date_value, default=None, tzinfo=None): + if isinstance(date_value, datetime): + return date_value + + # Get the DateTime + date_value = api.to_date(date_value, default=None) + if not date_value: + if default is None: + return None + return to_datetime(default, tzinfo=tzinfo) + + # Convert to datetime and strip + return DT2dt(date_value).replace(tzinfo=tzinfo) + + +def to_ymd(delta): + """Returns a representation of a relative delta in ymd format + """ + if not isinstance(delta, relativedelta): + raise TypeError("delta parameter must be a relative_delta") + + ymd = list("ymd") + diff = map(str, (delta.years, delta.months, delta.days)) + age = filter(lambda it: int(it[0]), zip(diff, ymd)) + return " ".join(map("".join, age))