diff --git a/docs/changelog.rst b/docs/changelog.rst index 5d68e7e..b1352a4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,7 @@ Changelog 1.4.0 (unreleased) ------------------ +- #54 Allow to set additional named phone numbers - #55 Optional MRN in Patient Add/Edit forms - #53 Add patient marital status diff --git a/src/senaite/patient/content/patient.py b/src/senaite/patient/content/patient.py index 583b45b..6072b3a 100644 --- a/src/senaite/patient/content/patient.py +++ b/src/senaite/patient/content/patient.py @@ -31,6 +31,7 @@ from senaite.core.behaviors import IClientShareable from senaite.core.schema import AddressField from senaite.core.schema import DatetimeField +from senaite.core.schema import PhoneField from senaite.core.schema.addressfield import OTHER_ADDRESS from senaite.core.schema.addressfield import PHYSICAL_ADDRESS from senaite.core.schema.addressfield import POSTAL_ADDRESS @@ -38,6 +39,7 @@ from senaite.core.schema.fields import DataGridRow from senaite.core.z3cform.widgets.datagrid import DataGridWidgetFactory from senaite.core.z3cform.widgets.datetimewidget import DatetimeWidget +from senaite.core.z3cform.widgets.phone import PhoneWidgetFactory from senaite.patient import api as patient_api from senaite.patient import messageFactory as _ from senaite.patient.catalog import PATIENT_CATALOG @@ -46,71 +48,15 @@ from senaite.patient.interfaces import IPatient from z3c.form.interfaces import NO_VALUE from zope import schema -from zope.interface import Interface from zope.interface import Invalid from zope.interface import implementer from zope.interface import invariant - -class IIdentifiersSchema(Interface): - """Schema definition for identifier records field - """ - - key = schema.Choice( - title=_("Type"), - description=_( - u"The type of identifier that holds the ID" - ), - source="senaite.patient.vocabularies.identifiers", - required=True, - ) - - value = schema.TextLine( - title=_(u"ID"), - description=_( - u"The identification number of the selected identifier" - ), - required=True, - ) - - -class IAdditionalEmailSchema(Interface): - """Schema definition for additional emails field - """ - - name = schema.TextLine( - title=_("Name"), - description=_(u"Private, Work, Other etc."), - required=True, - ) - - email = schema.TextLine( - title=_(u"Email"), - description=_(u"Email address"), - required=True, - ) - - -class IRaceSchema(Interface): - """Schema definition for patient race - """ - race = schema.Choice( - title=_("Race"), - description=_(u""), - source="senaite.patient.vocabularies.races", - required=False, - ) - - -class IEthnicitySchema(Interface): - """Schema definition for patient ethnicity - """ - ethnicity = schema.Choice( - title=_("Ethnicity"), - description=_(u""), - source="senaite.patient.vocabularies.ethnicities", - required=False, - ) +from .schema import IAdditionalEmailSchema +from .schema import IAdditionalPhoneNumbersSchema +from .schema import IEthnicitySchema +from .schema import IIdentifiersSchema +from .schema import IRaceSchema class IPatientSchema(model.Schema): @@ -137,9 +83,14 @@ class IPatientSchema(model.Schema): # contact fieldset fieldset( - "contact", - label=u"Contact", - fields=["email", "additional_emails", "phone", "mobile"]) + "email_and_phone", + label=u"Email and Phone", + fields=[ + "email", + "additional_emails", + "phone", + "additional_phone_numbers", + ]) # address fieldset fieldset( @@ -272,7 +223,7 @@ class IPatientSchema(model.Schema): email = schema.TextLine( title=_( u"label_primary_patient_email", - default=u"Primary Email" + default=u"Primary Email Address" ), description=_( u"description_patient_primary_email", @@ -289,7 +240,7 @@ class IPatientSchema(model.Schema): additional_emails = DataGridField( title=_( u"label_patient_additional_emails", - default=u"Additional Emails"), + default=u"Additional Email Addresses"), description=_( u"description_patient_additional_emails", default=u"Additional email addresses for this patient" @@ -302,16 +253,36 @@ class IPatientSchema(model.Schema): default=[], ) - phone = schema.TextLine( - title=_(u"label_patient_phone", default=u"Phone"), - description=_(u"Patient phone number"), + # primary phone number + directives.widget("phone", PhoneWidgetFactory) + phone = PhoneField( + title=_( + u"label_patient_primary_phone", + default=u"Primary Phone Number",), + description=_( + u"description_patient_primary_phone", + u"Primary phone number for this patient"), required=False, ) - mobile = schema.TextLine( - title=_(u"label_patient_mobile", default=u"Mobile"), - description=_(u"Patient mobile phone number"), + # additional phone numbers + directives.widget( + "additional_phone_numbers", + DataGridWidgetFactory, + allow_reorder=True, + auto_append=True) + additional_phone_numbers = DataGridField( + title=_(u"label_patient_additional_phone_numbers", + default=u"Additional Phone Numbers"), + description=_( + u"description_patient_additional_phone_numbers", + u"Additional phone numbers for this patient"), + value_type=DataGridRow( + title=u"Phone", + schema=IAdditionalPhoneNumbersSchema), required=False, + missing_value=[], + default=[], ) # Address @@ -643,9 +614,13 @@ def getFullname(self): parts = [self.getFirstname(), self.getMiddlename(), self.getLastname()] return " ".join(filter(None, parts)) + ### + # EMAIL AND PHONE + ### + @security.protected(permissions.View) def getEmail(self): - """Returns the email with the field accessor + """Get email with the field accessor """ accessor = self.accessor("email") value = accessor(self) or "" @@ -653,7 +628,7 @@ def getEmail(self): @security.protected(permissions.ModifyPortalContent) def setEmail(self, value): - """Set email by the field accessor + """Set email by the field mutator """ if not isinstance(value, string_types): value = u"" @@ -674,6 +649,36 @@ def setAdditionalEmails(self, value): mutator = self.mutator("additional_emails") mutator(self, value) + @security.protected(permissions.View) + def getPhone(self): + """Get phone by the field accessor + """ + accessor = self.accessor("phone") + return accessor(self) or "" + + @security.protected(permissions.ModifyPortalContent) + def setPhone(self, value): + """Set phone by the field mutator + """ + if not isinstance(value, string_types): + value = u"" + mutator = self.mutator("phone") + mutator(self, api.safe_unicode(value.strip())) + + @security.protected(permissions.View) + def getAdditionalPhoneNumbers(self): + """Get additional phone numbers by the field accessor + """ + accessor = self.accessor("additional_phone_numbers") + return accessor(self) or [] + + @security.protected(permissions.ModifyPortalContent) + def setAdditionalPhoneNumbers(self, value): + """Set additional phone numbers by the field mutator + """ + mutator = self.mutator("additional_phone_numbers") + mutator(self, value) + @security.protected(permissions.View) def getSex(self): """Returns the sex with the field accessor diff --git a/src/senaite/patient/content/schema.py b/src/senaite/patient/content/schema.py new file mode 100644 index 0000000..7825daa --- /dev/null +++ b/src/senaite/patient/content/schema.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- + +from plone.autoform import directives +from senaite.core.schema import PhoneField +from senaite.core.z3cform.widgets.phone import PhoneWidgetFactory +from senaite.patient import messageFactory as _ +from zope import schema +from zope.interface import Interface + + +class IIdentifiersSchema(Interface): + """Schema definition for identifiers field + """ + key = schema.Choice( + title=_( + u"label_patient_identifiers_key", + default=u"Type", + ), + description=_( + u"description_patient_identifiers_key", + default=u"The type of identifier that holds the ID", + ), + source="senaite.patient.vocabularies.identifiers", + required=True, + ) + + value = schema.TextLine( + title=_( + u"label_patient_identifiers_value", + default=u"ID", + ), + description=_( + u"description_patient_identifiers_value", + default=u"The identification number of the selected identifier", + ), + required=True, + ) + + +class IAdditionalEmailSchema(Interface): + """Schema definition for additional emails field + """ + name = schema.TextLine( + title=_( + u"label_patient_additional_emails_name", + default="Name" + ), + description=_( + u"description_patient_additional_emails_name", + default=u"Private, Work, Other etc.", + ), + required=True, + ) + + email = schema.TextLine( + title=_( + u"label_patient_additional_emails_email", + default=u"Email", + ), + description=_( + u"description_patient_additional_emails_email", + default=u"Email address"), + required=True, + ) + + +class IAdditionalPhoneNumbersSchema(Interface): + """Schema definition for additional phone numbers field + """ + name = schema.TextLine( + title=_( + u"label_patient_additional_phone_numbers_name", + default="Name", + ), + description=_( + u"description_patient_additional_phone_numbers_name", + default=u"Private, Work, Other etc." + ), + required=True, + ) + + directives.widget("phone", PhoneWidgetFactory) + phone = PhoneField( + title=_( + u"label_patient_additional_phone_numbers_phone", + default=u"Phone", + ), + description=_( + u"description_patient_additional_phone_numbers_phone", + default=u"Phone Number", + ), + required=True, + ) + + +class IRaceSchema(Interface): + """Schema definition for patient race + """ + race = schema.Choice( + title=_( + u"label_patient_race", + default=u"Race", + ), + source="senaite.patient.vocabularies.races", + required=False, + ) + + +class IEthnicitySchema(Interface): + """Schema definition for patient ethnicity + """ + ethnicity = schema.Choice( + title=_( + u"label_patient_ethnicity", + default=u"Ethnicity", + ), + source="senaite.patient.vocabularies.ethnicities", + required=False, + ) diff --git a/src/senaite/patient/profiles/default/metadata.xml b/src/senaite/patient/profiles/default/metadata.xml index aaab211..570ee5a 100644 --- a/src/senaite/patient/profiles/default/metadata.xml +++ b/src/senaite/patient/profiles/default/metadata.xml @@ -6,7 +6,7 @@ dependencies before installing this add-on own profile. --> - 1401 + 1402 diff --git a/src/senaite/patient/upgrade/v01_04_000.py b/src/senaite/patient/upgrade/v01_04_000.py index 4ce2338..d183033 100644 --- a/src/senaite/patient/upgrade/v01_04_000.py +++ b/src/senaite/patient/upgrade/v01_04_000.py @@ -18,9 +18,11 @@ # Copyright 2020-2022 by it's authors. # Some rights reserved, see README and LICENSE. +from bika.lims import api from senaite.core.upgrade import upgradestep from senaite.core.upgrade.utils import UpgradeUtils from senaite.patient import logger +from senaite.patient.api import patient_search from senaite.patient.config import PRODUCT_NAME from senaite.patient.setuphandlers import setup_catalogs @@ -63,4 +65,28 @@ def upgrade_marital_status(tool): # setup patient catalog setup_catalogs(portal) - logger.info("Upgrade patient marital status [DONCE]") + logger.info("Upgrade patient marital status [DONE]") + + +def upgrade_patient_mobile_phone_number(tool): + """Move mobile phone -> additional_phone_numbers records + + :param tool: portal_setup tool + """ + logger.info("Upgrade patient mobile number ...") + + patients = patient_search({"portal_type": "Patient"}) + + for brain in patients: + patient = api.get_object(brain) + mobile = getattr(patient, "mobile", None) + if not mobile: + continue + logger.info("Moving mobile phone '%s' -> additional_phone_numbers" + % mobile) + numbers = patient.getAdditionalPhoneNumbers() + numbers.append({"name": "Mobile", "phone": mobile}) + patient.setAdditionalPhoneNumbers(numbers) + delattr(patient, "mobile") + + logger.info("Upgrade patient mobile number [DONE]") diff --git a/src/senaite/patient/upgrade/v01_04_000.zcml b/src/senaite/patient/upgrade/v01_04_000.zcml index 2b1ab3e..86e8d3e 100644 --- a/src/senaite/patient/upgrade/v01_04_000.zcml +++ b/src/senaite/patient/upgrade/v01_04_000.zcml @@ -2,6 +2,16 @@ xmlns="http://namespaces.zope.org/zope" xmlns:genericsetup="http://namespaces.zope.org/genericsetup"> + + +