Gnumed.business.gmPerson

Package Gnumed :: Package business :: Module gmPerson |
[frames] | no frames]

# Source Code for Module Gnumed.business.gmPerson

       1  # -*- coding: utf8 -*-
       2  """GNUmed patient objects.
       3
       4  This is a patient object intended to let a useful client-side
       5  API crystallize from actual use in true XP fashion.
       6  """
       7  #============================================================
       8  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
       9  __license__ = "GPL"
      10
      11  # std lib
      12  import sys
      13  import os.path
      14  import time
      15  import re as regex
      16  import datetime as pyDT
      17  import codecs
      18  import threading
      19  import logging
      20
      21
      22  # GNUmed
      23  if __name__ == '__main__':
      24          sys.path.insert(0, '../../')
      25  from Gnumed.pycommon import gmExceptions
      26  from Gnumed.pycommon import gmDispatcher
      27  from Gnumed.pycommon import gmBorg
      28  from Gnumed.pycommon import gmI18N
      29  from Gnumed.pycommon import gmNull
      30  from Gnumed.pycommon import gmBusinessDBObject
      31  from Gnumed.pycommon import gmTools
      32  from Gnumed.pycommon import gmPG2
      33  from Gnumed.pycommon import gmDateTime
      34  from Gnumed.pycommon import gmMatchProvider
      35  from Gnumed.pycommon import gmLog2
      36  from Gnumed.pycommon import gmHooks
      37
      38  from Gnumed.business import gmDemographicRecord
      39  from Gnumed.business import gmClinicalRecord
      40  from Gnumed.business import gmXdtMappings
      41  from Gnumed.business import gmProviderInbox
      42  from Gnumed.business.gmDocuments import cDocumentFolder
      43
      44
      45  _log = logging.getLogger('gm.person')
      46
      47  __gender_list = None
      48  __gender_idx = None
      49
      50  __gender2salutation_map = None
      51  __gender2string_map = None
      52
      53
      54  #============================================================
      55 -def external_id_exists(pk_issuer, value):
      56          cmd = u'SELECT COUNT(1) FROM dem.lnk_identity2ext_id WHERE fk_origin = %(issuer)s AND external_id = %(val)s'
      57          args = {'issuer': pk_issuer, 'val': value}
      58          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
      59          return rows[0][0]
      60
      61  #============================================================
      62 -def person_exists(lastnames, dob, firstnames=None):
      63          args = {
      64                  'last': lastnames,
      65                  'dob': dob
      66          }
      67          where_parts = [
      68                  u"lastnames = %(last)s",
      69                  u"dem.date_trunc_utc('day', dob) = dem.date_trunc_utc('day', %(dob)s)"
      70          ]
      71          if firstnames is not None:
      72                  if firstnames.strip() != u'':
      73                          #where_parts.append(u"position(%(first)s in firstnames) = 1")
      74                          where_parts.append(u"firstnames ~* %(first)s")
      75                          args['first'] = u'\\m' + firstnames
      76          cmd = u"""SELECT COUNT(1) FROM dem.v_basic_person WHERE %s""" % u' AND '.join(where_parts)
      77          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
      78          return rows[0][0]
      79
      80  #============================================================
      81  # FIXME: make this work as a mapping type, too
      82 -class cDTO_person(object):
      83
      84 -        def __init__(self):
      85                  self.identity = None
      86                  self.external_ids = []
      87                  self.comm_channels = []
      88                  self.addresses = []
      89          #--------------------------------------------------------
      90          # external API
      91          #--------------------------------------------------------
      92 -        def keys(self):
      93                  return 'firstnames lastnames dob gender'.split()
      94          #--------------------------------------------------------
      95 -        def delete_from_source(self):
      96                  pass
      97          #--------------------------------------------------------
      98 -        def get_candidate_identities(self, can_create=False):
      99                  """Generate generic queries.
     100
     101                  - not locale dependant
     102                  - data -> firstnames, lastnames, dob, gender
     103
     104                  shall we mogrify name parts ? probably not as external
     105                  sources should know what they do
     106
     107                  finds by inactive name, too, but then shows
     108                  the corresponding active name ;-)
     109
     110                  Returns list of matching identities (may be empty)
     111                  or None if it was told to create an identity but couldn't.
     112                  """
     113                  where_snippets = []
     114                  args = {}
     115
     116                  where_snippets.append(u'firstnames = %(first)s')
     117                  args['first'] = self.firstnames
     118
     119                  where_snippets.append(u'lastnames = %(last)s')
     120                  args['last'] = self.lastnames
     121
     122                  if self.dob is not None:
     123                          where_snippets.append(u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %(dob)s)")
     124                          args['dob'] = self.dob.replace(hour = 23, minute = 59, second = 59)
     125
     126                  if self.gender is not None:
     127                          where_snippets.append('gender = %(sex)s')
     128                          args['sex'] = self.gender
     129
     130                  cmd = u"""
     131  SELECT *, '%s' AS match_type
     132  FROM dem.v_basic_person
     133  WHERE
     134          pk_identity IN (
     135                  SELECT pk_identity FROM dem.v_person_names WHERE %s
     136          )
     137  ORDER BY lastnames, firstnames, dob""" % (
     138                  _('external patient source (name, gender, date of birth)'),
     139                  ' AND '.join(where_snippets)
     140                  )
     141
     142                  try:
     143                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx=True)
     144                  except:
     145                          _log.error(u'cannot get candidate identities for dto "%s"' % self)
     146                          _log.exception('query %s' % cmd)
     147                          rows = []
     148
     149                  if len(rows) == 0:
     150                          _log.debug('no candidate identity matches found')
     151                          if not can_create:
     152                                  return []
     153                          ident = self.import_into_database()
     154                          if ident is None:
     155                                  return None
     156                          identities = [ident]
     157                  else:
     158                          identities = [ cIdentity(row = {'pk_field': 'pk_identity', 'data': row, 'idx': idx}) for row in rows ]
     159
     160                  return identities
     161          #--------------------------------------------------------
     162 -        def import_into_database(self):
     163                  """Imports self into the database."""
     164
     165                  self.identity = create_identity (
     166                          firstnames = self.firstnames,
     167                          lastnames = self.lastnames,
     168                          gender = self.gender,
     169                          dob = self.dob
     170                  )
     171
     172                  if self.identity is None:
     173                          return None
     174
     175                  for ext_id in self.external_ids:
     176                          try:
     177                                  self.identity.add_external_id (
     178                                          type_name = ext_id['name'],
     179                                          value = ext_id['value'],
     180                                          issuer = ext_id['issuer'],
     181                                          comment = ext_id['comment']
     182                                  )
     183                          except StandardError:
     184                                  _log.exception('cannot import <external ID> from external data source')
     185                                  _log.log_stack_trace()
     186
     187                  for comm in self.comm_channels:
     188                          try:
     189                                  self.identity.link_comm_channel (
     190                                          comm_medium = comm['channel'],
     191                                          url = comm['url']
     192                                  )
     193                          except StandardError:
     194                                  _log.exception('cannot import <comm channel> from external data source')
     195                                  _log.log_stack_trace()
     196
     197                  for adr in self.addresses:
     198                          try:
     199                                  self.identity.link_address (
     200                                          number = adr['number'],
     201                                          street = adr['street'],
     202                                          postcode = adr['zip'],
     203                                          urb = adr['urb'],
     204                                          state = adr['region'],
     205                                          country = adr['country']
     206                                  )
     207                          except StandardError:
     208                                  _log.exception('cannot import <address> from external data source')
     209                                  _log.log_stack_trace()
     210
     211                  return self.identity
     212          #--------------------------------------------------------
     213 -        def import_extra_data(self, *args, **kwargs):
     214                  pass
     215          #--------------------------------------------------------
     216 -        def remember_external_id(self, name=None, value=None, issuer=None, comment=None):
     217                  value = value.strip()
     218                  if value == u'':
     219                          return
     220                  name = name.strip()
     221                  if name == u'':
     222                          raise ValueError(_('<name> cannot be empty'))
     223                  issuer = issuer.strip()
     224                  if issuer == u'':
     225                          raise ValueError(_('<issuer> cannot be empty'))
     226                  self.external_ids.append({'name': name, 'value': value, 'issuer': issuer, 'comment': comment})
     227          #--------------------------------------------------------
     228 -        def remember_comm_channel(self, channel=None, url=None):
     229                  url = url.strip()
     230                  if url == u'':
     231                          return
     232                  channel = channel.strip()
     233                  if channel == u'':
     234                          raise ValueError(_('<channel> cannot be empty'))
     235                  self.comm_channels.append({'channel': channel, 'url': url})
     236          #--------------------------------------------------------
     237 -        def remember_address(self, number=None, street=None, urb=None, region=None, zip=None, country=None):
     238                  number = number.strip()
     239                  if number == u'':
     240                          raise ValueError(_('<number> cannot be empty'))
     241                  street = street.strip()
     242                  if street == u'':
     243                          raise ValueError(_('<street> cannot be empty'))
     244                  urb = urb.strip()
     245                  if urb == u'':
     246                          raise ValueError(_('<urb> cannot be empty'))
     247                  zip = zip.strip()
     248                  if zip == u'':
     249                          raise ValueError(_('<zip> cannot be empty'))
     250                  country = country.strip()
     251                  if country == u'':
     252                          raise ValueError(_('<country> cannot be empty'))
     253                  region = region.strip()
     254                  if region == u'':
     255                          region = u'??'
     256                  self.addresses.append ({
     257                          u'number': number,
     258                          u'street': street,
     259                          u'zip': zip,
     260                          u'urb': urb,
     261                          u'region': region,
     262                          u'country': country
     263                  })
     264          #--------------------------------------------------------
     265          # customizing behaviour
     266          #--------------------------------------------------------
     267 -        def __str__(self):
     268                  return u'<%s @ %s: %s %s (%s) %s>' % (
     269                          self.__class__.__name__,
     270                          id(self),
     271                          self.firstnames,
     272                          self.lastnames,
     273                          self.gender,
     274                          self.dob
     275                  )
     276          #--------------------------------------------------------
     277 -        def __setattr__(self, attr, val):
     278                  """Do some sanity checks on self.* access."""
     279
     280                  if attr == 'gender':
     281                          glist, idx = get_gender_list()
     282                          for gender in glist:
     283                                  if str(val) in [gender[0], gender[1], gender[2], gender[3]]:
     284                                          val = gender[idx['tag']]
     285                                          object.__setattr__(self, attr, val)
     286                                          return
     287                          raise ValueError('invalid gender: [%s]' % val)
     288
     289                  if attr == 'dob':
     290                          if val is not None:
     291                                  if not isinstance(val, pyDT.datetime):
     292                                          raise TypeError('invalid type for DOB (must be datetime.datetime): %s [%s]' % (type(val), val))
     293                                  if val.tzinfo is None:
     294                                          raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % val.isoformat())
     295
     296                  object.__setattr__(self, attr, val)
     297                  return
     298          #--------------------------------------------------------
     299 -        def __getitem__(self, attr):
     300                  return getattr(self, attr)
     301  #============================================================
     302 -class cPersonName(gmBusinessDBObject.cBusinessDBObject):
     303          _cmd_fetch_payload = u"SELECT * FROM dem.v_person_names WHERE pk_name = %s"
     304          _cmds_store_payload = [
     305                  u"""UPDATE dem.names SET
     306                          active = FALSE
     307                  WHERE
     308                          %(active_name)s IS TRUE                         -- act only when needed and only
     309                                  AND
     310                          id_identity = %(pk_identity)s           -- on names of this identity
     311                                  AND
     312                          active IS TRUE                                          -- which are active
     313                                  AND
     314                          id != %(pk_name)s                                       -- but NOT *this* name
     315                          """,
     316                  u"""update dem.names set
     317                          active = %(active_name)s,
     318                          preferred = %(preferred)s,
     319                          comment = %(comment)s
     320                  where
     321                          id = %(pk_name)s and
     322                          id_identity = %(pk_identity)s and       -- belt and suspenders
     323                          xmin = %(xmin_name)s""",
     324                  u"""select xmin as xmin_name from dem.names where id = %(pk_name)s"""
     325          ]
     326          _updatable_fields = ['active_name', 'preferred', 'comment']
     327          #--------------------------------------------------------
     328 -        def __setitem__(self, attribute, value):
     329                  if attribute == 'active_name':
     330                          # cannot *directly* deactivate a name, only indirectly
     331                          # by activating another one
     332                          # FIXME: should be done at DB level
     333                          if self._payload[self._idx['active_name']] is True:
     334                                  return
     335                  gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
     336          #--------------------------------------------------------
     337 -        def _get_description(self):
     338                  return '%(last)s, %(title)s %(first)s%(nick)s' % {
     339                          'last': self._payload[self._idx['lastnames']],
     340                          'title': gmTools.coalesce (
     341                                  self._payload[self._idx['title']],
     342                                  map_gender2salutation(self._payload[self._idx['gender']])
     343                          ),
     344                          'first': self._payload[self._idx['firstnames']],
     345                          'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u" '%s'", u'%s')
     346                  }
     347
     348          description = property(_get_description, lambda x:x)
     349  #============================================================
     350 -class cIdentity(gmBusinessDBObject.cBusinessDBObject):
     351          _cmd_fetch_payload = u"SELECT * FROM dem.v_basic_person WHERE pk_identity = %s"
     352          _cmds_store_payload = [
     353                  u"""UPDATE dem.identity SET
     354                                  gender = %(gender)s,
     355                                  dob = %(dob)s,
     356                                  dob_is_estimated = %(dob_is_estimated)s,
     357                                  tob = %(tob)s,
     358                                  cob = gm.nullify_empty_string(%(cob)s),
     359                                  title = gm.nullify_empty_string(%(title)s),
     360                                  fk_marital_status = %(pk_marital_status)s,
     361                                  karyotype = gm.nullify_empty_string(%(karyotype)s),
     362                                  pupic = gm.nullify_empty_string(%(pupic)s),
     363                                  deceased = %(deceased)s,
     364                                  emergency_contact = gm.nullify_empty_string(%(emergency_contact)s),
     365                                  fk_emergency_contact = %(pk_emergency_contact)s,
     366                                  fk_primary_provider = %(pk_primary_provider)s,
     367                                  comment = gm.nullify_empty_string(%(comment)s)
     368                          WHERE
     369                                  pk = %(pk_identity)s and
     370                                  xmin = %(xmin_identity)s
     371                          RETURNING
     372                                  xmin AS xmin_identity"""
     373          ]
     374          _updatable_fields = [
     375                  "title",
     376                  "dob",
     377                  "tob",
     378                  "cob",
     379                  "gender",
     380                  "pk_marital_status",
     381                  "karyotype",
     382                  "pupic",
     383                  'deceased',
     384                  'emergency_contact',
     385                  'pk_emergency_contact',
     386                  'pk_primary_provider',
     387                  'comment',
     388                  'dob_is_estimated'
     389          ]
     390          #--------------------------------------------------------
     391 -        def _get_ID(self):
     392                  return self._payload[self._idx['pk_identity']]
     393 -        def _set_ID(self, value):
     394                  raise AttributeError('setting ID of identity is not allowed')
     395          ID = property(_get_ID, _set_ID)
     396          #--------------------------------------------------------
     397 -        def __setitem__(self, attribute, value):
     398
     399                  if attribute == 'dob':
     400                          if value is not None:
     401
     402                                  if isinstance(value, pyDT.datetime):
     403                                          if value.tzinfo is None:
     404                                                  raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % dt.isoformat())
     405                                  else:
     406                                          raise TypeError, '[%s]: type [%s] (%s) invalid for attribute [dob], must be datetime.datetime or None' % (self.__class__.__name__, type(value), value)
     407
     408                                  # compare DOB at seconds level
     409                                  if self._payload[self._idx['dob']] is not None:
     410                                          old_dob = gmDateTime.pydt_strftime (
     411                                                  self._payload[self._idx['dob']],
     412                                                  format = '%Y %m %d %H %M %S',
     413                                                  accuracy = gmDateTime.acc_seconds
     414                                          )
     415                                          new_dob = gmDateTime.pydt_strftime (
     416                                                  value,
     417                                                  format = '%Y %m %d %H %M %S',
     418                                                  accuracy = gmDateTime.acc_seconds
     419                                          )
     420                                          if new_dob == old_dob:
     421                                                  return
     422
     423                  gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
     424          #--------------------------------------------------------
     425 -        def cleanup(self):
     426                  pass
     427          #--------------------------------------------------------
     428 -        def _get_is_patient(self):
     429                  cmd = u"""
     430                          SELECT EXISTS (
     431                                  SELECT 1
     432                                  FROM clin.v_emr_journal
     433                                  WHERE
     434                                          pk_patient = %(pat)s
     435                                                  AND
     436                                          soap_cat IS NOT NULL
     437                  )"""
     438                  args = {'pat': self._payload[self._idx['pk_identity']]}
     439                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     440                  return rows[0][0]
     441
     442 -        def _set_is_patient(self, value):
     443                  raise AttributeError('setting is_patient status of identity is not allowed')
     444
     445          is_patient = property(_get_is_patient, _set_is_patient)
     446          #--------------------------------------------------------
     447 -        def _get_staff_id(self):
     448                  cmd = u"SELECT pk FROM dem.staff WHERE fk_identity = %(pk)s"
     449                  args = {'pk': self._payload[self._idx['pk_identity']]}
     450                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     451                  if len(rows) == 0:
     452                          return None
     453                  return rows[0][0]
     454
     455          staff_id = property(_get_staff_id, lambda x:x)
     456          #--------------------------------------------------------
     457          # identity API
     458          #--------------------------------------------------------
     459 -        def _get_gender_symbol(self):
     460                  return map_gender2symbol[self._payload[self._idx['gender']]]
     461
     462          gender_symbol = property(_get_gender_symbol, lambda x:x)
     463          #--------------------------------------------------------
     464 -        def _get_gender_string(self):
     465                  return map_gender2string(gender = self._payload[self._idx['gender']])
     466
     467          gender_string = property(_get_gender_string, lambda x:x)
     468          #--------------------------------------------------------
     469 -        def get_active_name(self):
     470                  names = self.get_names(active_only = True)
     471                  if len(names) == 0:
     472                          _log.error('cannot retrieve active name for patient [%s]', self._payload[self._idx['pk_identity']])
     473                          return None
     474                  return names[0]
     475
     476          active_name = property(get_active_name, lambda x:x)
     477          #--------------------------------------------------------
     478 -        def get_names(self, active_only=False, exclude_active=False):
     479
     480                  args = {'pk_pat': self._payload[self._idx['pk_identity']]}
     481                  where_parts = [u'pk_identity = %(pk_pat)s']
     482                  if active_only:
     483                          where_parts.append(u'active_name is True')
     484                  if exclude_active:
     485                          where_parts.append(u'active_name is False')
     486                  cmd = u"""
     487                          SELECT *
     488                          FROM dem.v_person_names
     489                          WHERE %s
     490                          ORDER BY active_name DESC, lastnames, firstnames
     491                  """ % u' AND '.join(where_parts)
     492                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     493
     494                  if len(rows) == 0:
     495                          # no names registered for patient
     496                          return []
     497
     498                  names = [ cPersonName(row = {'idx': idx, 'data': r, 'pk_field': 'pk_name'}) for r in rows ]
     499                  return names
     500          #--------------------------------------------------------
     501 -        def get_description_gender(self):
     502                  return _(u'%(last)s,%(title)s %(first)s%(nick)s (%(sex)s)') % {
     503                          'last': self._payload[self._idx['lastnames']],
     504                          'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s'),
     505                          'first': self._payload[self._idx['firstnames']],
     506                          'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u" '%s'"),
     507                          'sex': self.gender_symbol
     508                  }
     509          #--------------------------------------------------------
     510 -        def get_description(self):
     511                  return _(u'%(last)s,%(title)s %(first)s%(nick)s') % {
     512                          'last': self._payload[self._idx['lastnames']],
     513                          'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s'),
     514                          'first': self._payload[self._idx['firstnames']],
     515                          'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u" '%s'")
     516                  }
     517          #--------------------------------------------------------
     518 -        def add_name(self, firstnames, lastnames, active=True):
     519                  """Add a name.
     520
     521                  @param firstnames The first names.
     522                  @param lastnames The last names.
     523                  @param active When True, the new name will become the active one (hence setting other names to inactive)
     524                  @type active A types.BooleanType instance
     525                  """
     526                  name = create_name(self.ID, firstnames, lastnames, active)
     527                  if active:
     528                          self.refetch_payload()
     529                  return name
     530          #--------------------------------------------------------
     531 -        def delete_name(self, name=None):
     532                  cmd = u"delete from dem.names where id = %(name)s and id_identity = %(pat)s"
     533                  args = {'name': name['pk_name'], 'pat': self.ID}
     534                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     535                  # can't have been the active name as that would raise an
     536                  # exception (since no active name would be left) so no
     537                  # data refetch needed
     538          #--------------------------------------------------------
     539 -        def set_nickname(self, nickname=None):
     540                  """
     541                  Set the nickname. Setting the nickname only makes sense for the currently
     542                  active name.
     543                  @param nickname The preferred/nick/warrior name to set.
     544                  """
     545                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': u"select dem.set_nickname(%s, %s)", 'args': [self.ID, nickname]}])
     546                  self.refetch_payload()
     547                  return True
     548          #--------------------------------------------------------
     549 -        def get_tags(self, order_by=None):
     550                  if order_by is None:
     551                          order_by = u''
     552                  else:
     553                          order_by = u'ORDER BY %s' % order_by
     554
     555                  cmd = gmDemographicRecord._SQL_get_identity_tags % (u'pk_identity = %%(pat)s %s' % order_by)
     556                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {u'pat': self.ID}}], get_col_idx = True)
     557
     558                  return [ gmDemographicRecord.cIdentityTag(row = {'data': r, 'idx': idx, 'pk_field': 'pk_identity_tag'}) for r in rows ]
     559
     560          tags = property(get_tags, lambda x:x)
     561          #--------------------------------------------------------
     562 -        def add_tag(self, tag):
     563                  args = {
     564                          u'tag': tag,
     565                          u'identity': self.ID
     566                  }
     567
     568                  # already exists ?
     569                  cmd = u"SELECT pk FROM dem.identity_tag WHERE fk_tag = %(tag)s AND fk_identity = %(identity)s"
     570                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     571                  if len(rows) > 0:
     572                          return gmDemographicRecord.cIdentityTag(aPK_obj = rows[0]['pk'])
     573
     574                  # no, add
     575                  cmd = u"""
     576                          INSERT INTO dem.identity_tag (
     577                                  fk_tag,
     578                                  fk_identity
     579                          ) VALUES (
     580                                  %(tag)s,
     581                                  %(identity)s
     582                          )
     583                          RETURNING pk
     584                  """
     585                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
     586                  return gmDemographicRecord.cIdentityTag(aPK_obj = rows[0]['pk'])
     587          #--------------------------------------------------------
     588 -        def remove_tag(self, tag):
     589                  cmd = u"DELETE FROM dem.identity_tag WHERE pk = %(pk)s"
     590                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': tag}}])
     591          #--------------------------------------------------------
     592          # external ID API
     593          #
     594          # since external IDs are not treated as first class
     595          # citizens (classes in their own right, that is), we
     596          # handle them *entirely* within cIdentity, also they
     597          # only make sense with one single person (like names)
     598          # and are not reused (like addresses), so they are
     599          # truly added/deleted, not just linked/unlinked
     600          #--------------------------------------------------------
     601 -        def add_external_id(self, type_name=None, value=None, issuer=None, comment=None, pk_type=None):
     602                  """Adds an external ID to the patient.
     603
     604                  creates ID type if necessary
     605                  """
     606
     607                  # check for existing ID
     608                  if pk_type is not None:
     609                          cmd = u"""
     610                                  select * from dem.v_external_ids4identity where
     611                                  pk_identity = %(pat)s and
     612                                  pk_type = %(pk_type)s and
     613                                  value = %(val)s"""
     614                  else:
     615                          # by type/value/issuer
     616                          if issuer is None:
     617                                  cmd = u"""
     618                                          select * from dem.v_external_ids4identity where
     619                                          pk_identity = %(pat)s and
     620                                          name = %(name)s and
     621                                          value = %(val)s"""
     622                          else:
     623                                  cmd = u"""
     624                                          select * from dem.v_external_ids4identity where
     625                                                  pk_identity = %(pat)s and
     626                                                  name = %(name)s and
     627                                                  value = %(val)s and
     628                                                  issuer = %(issuer)s"""
     629                  args = {
     630                          'pat': self.ID,
     631                          'name': type_name,
     632                          'val': value,
     633                          'issuer': issuer,
     634                          'pk_type': pk_type
     635                  }
     636                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
     637
     638                  # create new ID if not found
     639                  if len(rows) == 0:
     640
     641                          args = {
     642                                  'pat': self.ID,
     643                                  'val': value,
     644                                  'type_name': type_name,
     645                                  'pk_type': pk_type,
     646                                  'issuer': issuer,
     647                                  'comment': comment
     648                          }
     649
     650                          if pk_type is None:
     651                                  cmd = u"""insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values (
     652                                          %(val)s,
     653                                          (select dem.add_external_id_type(%(type_name)s, %(issuer)s)),
     654                                          %(comment)s,
     655                                          %(pat)s
     656                                  )"""
     657                          else:
     658                                  cmd = u"""insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values (
     659                                          %(val)s,
     660                                          %(pk_type)s,
     661                                          %(comment)s,
     662                                          %(pat)s
     663                                  )"""
     664
     665                          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     666
     667                  # or update comment of existing ID
     668                  else:
     669                          row = rows[0]
     670                          if comment is not None:
     671                                  # comment not already there ?
     672                                  if gmTools.coalesce(row['comment'], '').find(comment.strip()) == -1:
     673                                          comment = '%s%s' % (gmTools.coalesce(row['comment'], '', '%s // '), comment.strip)
     674                                          cmd = u"update dem.lnk_identity2ext_id set comment = %(comment)s where id=%(pk)s"
     675                                          args = {'comment': comment, 'pk': row['pk_id']}
     676                                          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     677          #--------------------------------------------------------
     678 -        def update_external_id(self, pk_id=None, type=None, value=None, issuer=None, comment=None):
     679                  """Edits an existing external ID.
     680
     681                  Creates ID type if necessary.
     682                  """
     683                  cmd = u"""
     684                          UPDATE dem.lnk_identity2ext_id SET
     685                                  fk_origin = (SELECT dem.add_external_id_type(%(type)s, %(issuer)s)),
     686                                  external_id = %(value)s,
     687                                  comment = gm.nullify_empty_string(%(comment)s)
     688                          WHERE
     689                                  id = %(pk)s
     690                  """
     691                  args = {'pk': pk_id, 'value': value, 'type': type, 'issuer': issuer, 'comment': comment}
     692                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     693          #--------------------------------------------------------
     694 -        def get_external_ids(self, id_type=None, issuer=None):
     695                  where_parts = ['pk_identity = %(pat)s']
     696                  args = {'pat': self.ID}
     697
     698                  if id_type is not None:
     699                          where_parts.append(u'name = %(name)s')
     700                          args['name'] = id_type.strip()
     701
     702                  if issuer is not None:
     703                          where_parts.append(u'issuer = %(issuer)s')
     704                          args['issuer'] = issuer.strip()
     705
     706                  cmd = u"SELECT * FROM dem.v_external_ids4identity WHERE %s" % ' AND '.join(where_parts)
     707                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
     708
     709                  return rows
     710
     711          external_ids = property(get_external_ids, lambda x:x)
     712          #--------------------------------------------------------
     713 -        def delete_external_id(self, pk_ext_id=None):
     714                  cmd = u"""
     715  delete from dem.lnk_identity2ext_id
     716  where id_identity = %(pat)s and id = %(pk)s"""
     717                  args = {'pat': self.ID, 'pk': pk_ext_id}
     718                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     719          #--------------------------------------------------------
     720 -        def assimilate_identity(self, other_identity=None, link_obj=None):
     721                  """Merge another identity into this one.
     722
     723                  Keep this one. Delete other one."""
     724
     725                  if other_identity.ID == self.ID:
     726                          return True, None
     727
     728                  curr_pat = gmCurrentPatient()
     729                  if curr_pat.connected:
     730                          if other_identity.ID == curr_pat.ID:
     731                                  return False, _('Cannot merge active patient into another patient.')
     732
     733                  queries = []
     734                  args = {'pat2del': other_identity.ID, 'pat2keep': self.ID}
     735
     736                  # merge allergy state
     737                  queries.append ({
     738                          'cmd': u"""
     739                                  UPDATE clin.allergy_state SET
     740                                          has_allergy = greatest (
     741                                                  (SELECT has_allergy FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2del)s),
     742                                                  (SELECT has_allergy FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2keep)s)
     743                                          )
     744                                  WHERE
     745                                          pk = (SELECT pk_allergy_state FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2keep)s)
     746                          """,
     747                          'args': args
     748                  })
     749                  # delete old allergy state
     750                  queries.append ({
     751                          'cmd': u'delete from clin.allergy_state where pk = (select pk_allergy_state from clin.v_pat_allergy_state where pk_patient = %(pat2del)s)',
     752                          'args': args
     753                  })
     754
     755                  # transfer names
     756                  # 1) disambiguate names in old pat
     757                  queries.append ({
     758                          'cmd': u"""
     759                                  UPDATE dem.names d_n1 SET
     760                                          lastnames = lastnames || ' (%s)'
     761                                  WHERE
     762                                          d_n1.id_identity = %%(pat2del)s
     763                                                  AND
     764                                          EXISTS (
     765                                                  SELECT 1 FROM dem.names d_n2
     766                                                  WHERE
     767                                                          d_n2.id_identity = %%(pat2keep)s
     768                                                                  AND
     769                                                          d_n2.lastnames = d_n1.lastnames
     770                                                                  AND
     771                                                          d_n2.firstnames = d_n1.firstnames
     772                                          )""" % _('assimilated'),
     773                          'args': args
     774                  })
     775                  # 2) move inactive ones (but beware of dupes)
     776                  queries.append ({
     777                          'cmd': u"""
     778                                  UPDATE dem.names SET
     779                                          id_identity = %(pat2keep)s
     780                                  WHERE id_identity = %(pat2del)s AND active IS false""",
     781                          'args': args
     782                  })
     783                  # 3) copy active ones
     784                  queries.append ({
     785                          'cmd': u"""
     786                                  INSERT INTO dem.names (
     787                                          id_identity, active, lastnames, firstnames, preferred, comment
     788                                  ) SELECT
     789                                          %(pat2keep)s, false, lastnames, firstnames, preferred, comment
     790                                  FROM dem.names d_n
     791                                  WHERE d_n.id_identity = %(pat2del)s AND d_n.active IS true""",
     792                          'args': args
     793                  })
     794
     795                  # find FKs pointing to identity
     796                  FKs = gmPG2.get_foreign_keys2column (
     797                          schema = u'dem',
     798                          table = u'identity',
     799                          column = u'pk'
     800                  )
     801
     802                  # disambiguate potential dupes
     803                  # - same-url comm channels
     804                  queries.append ({
     805                          'cmd': u"""
     806                                  UPDATE dem.lnk_identity2comm
     807                                  SET url = url || ' (%s %s)'
     808                                  WHERE
     809                                          fk_identity = %%(pat2del)s
     810                                                  AND
     811                                          EXISTS (
     812                                                  SELECT 1 FROM dem.lnk_identity2comm d_li2c
     813                                                  WHERE d_li2c.fk_identity = %%(pat2keep)s AND d_li2c.url = url
     814                                          )
     815                                  """ % (_('merged'),     gmDateTime.pydt_strftime()),
     816                          'args': args
     817                  })
     818                  # - same-value external IDs
     819                  queries.append ({
     820                          'cmd': u"""
     821                                  UPDATE dem.lnk_identity2ext_id
     822                                  SET external_id = external_id || ' (%s %s)'
     823                                  WHERE
     824                                          id_identity = %%(pat2del)s
     825                                                  AND
     826                                          EXISTS (
     827                                                  SELECT 1 FROM dem.lnk_identity2ext_id d_li2e
     828                                                  WHERE
     829                                                          d_li2e.id_identity = %%(pat2keep)s
     830                                                                  AND
     831                                                          d_li2e.external_id = external_id
     832                                                                  AND
     833                                                          d_li2e.fk_origin = fk_origin
     834                                          )
     835                                  """ % (_('merged'),     gmDateTime.pydt_strftime()),
     836                          'args': args
     837                  })
     838                  # - same addresses
     839                  queries.append ({
     840                          'cmd': u"""
     841                                  DELETE FROM dem.lnk_person_org_address
     842                                  WHERE
     843                                          id_identity = %(pat2del)s
     844                                                  AND
     845                                          id_address IN (
     846                                                  SELECT id_address FROM dem.lnk_person_org_address d_lpoa
     847                                                  WHERE d_lpoa.id_identity = %(pat2keep)s
     848                                          )
     849                          """,
     850                          'args': args
     851                  })
     852
     853                  # generate UPDATEs
     854                  cmd_template = u'UPDATE %s SET %s = %%(pat2keep)s WHERE %s = %%(pat2del)s'
     855                  for FK in FKs:
     856                          if FK['referencing_table'] == u'dem.names':
     857                                  continue
     858                          queries.append ({
     859                                  'cmd': cmd_template % (FK['referencing_table'], FK['referencing_column'], FK['referencing_column']),
     860                                  'args': args
     861                          })
     862
     863                  # remove old identity entry
     864                  queries.append ({
     865                          'cmd': u'delete from dem.identity where pk = %(pat2del)s',
     866                          'args': args
     867                  })
     868
     869                  _log.warning('identity [%s] is about to assimilate identity [%s]', self.ID, other_identity.ID)
     870
     871                  gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, end_tx = True)
     872
     873                  self.add_external_id (
     874                          type_name = u'merged GNUmed identity primary key',
     875                          value = u'GNUmed::pk::%s' % other_identity.ID,
     876                          issuer = u'GNUmed'
     877                  )
     878
     879                  return True, None
     880          #--------------------------------------------------------
     881          #--------------------------------------------------------
     882 -        def put_on_waiting_list(self, urgency=0, comment=None, zone=None):
     883                  cmd = u"""
     884                          insert into clin.waiting_list (fk_patient, urgency, comment, area, list_position)
     885                          values (
     886                                  %(pat)s,
     887                                  %(urg)s,
     888                                  %(cmt)s,
     889                                  %(area)s,
     890                                  (select coalesce((max(list_position) + 1), 1) from clin.waiting_list)
     891                          )"""
     892                  args = {'pat': self.ID, 'urg': urgency, 'cmt': comment, 'area': zone}
     893                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], verbose = True)
     894          #--------------------------------------------------------
     895 -        def get_waiting_list_entry(self):
     896                  cmd = u"""SELECT * FROM clin.v_waiting_list WHERE pk_identity = %(pat)s"""
     897                  args = {'pat': self.ID}
     898                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
     899                  return rows
     900
     901          waiting_list_entries = property(get_waiting_list_entry, lambda x:x)
     902          #--------------------------------------------------------
     903 -        def _get_export_tray(self):
     904                  #return gmExportTray.cExportTray(self.tray_dir_name)
     905                  return None
     906
     907          export_tray = property(_get_export_tray, lambda x:x)
     908          #--------------------------------------------------------
     909 -        def export_as_gdt(self, filename=None, encoding='iso-8859-15', external_id_type=None):
     910
     911                  template = u'%s%s%s\r\n'
     912
     913                  file = codecs.open (
     914                          filename = filename,
     915                          mode = 'wb',
     916                          encoding = encoding,
     917                          errors = 'strict'
     918                  )
     919
     920                  file.write(template % (u'013', u'8000', u'6301'))
     921                  file.write(template % (u'013', u'9218', u'2.10'))
     922                  if external_id_type is None:
     923                          file.write(template % (u'%03d' % (9 + len(str(self.ID))), u'3000', self.ID))
     924                  else:
     925                          ext_ids = self.get_external_ids(id_type = external_id_type)
     926                          if len(ext_ids) > 0:
     927                                  file.write(template % (u'%03d' % (9 + len(ext_ids[0]['value'])), u'3000', ext_ids[0]['value']))
     928                  file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['lastnames']])), u'3101', self._payload[self._idx['lastnames']]))
     929                  file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['firstnames']])), u'3102', self._payload[self._idx['firstnames']]))
     930                  file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['dob']].strftime('%d%m%Y'))), u'3103', self._payload[self._idx['dob']].strftime('%d%m%Y')))
     931                  file.write(template % (u'010', u'3110', gmXdtMappings.map_gender_gm2xdt[self._payload[self._idx['gender']]]))
     932                  file.write(template % (u'025', u'6330', 'GNUmed::9206::encoding'))
     933                  file.write(template % (u'%03d' % (9 + len(encoding)), u'6331', encoding))
     934                  if external_id_type is None:
     935                          file.write(template % (u'029', u'6332', u'GNUmed::3000::source'))
     936                          file.write(template % (u'017', u'6333', u'internal'))
     937                  else:
     938                          if len(ext_ids) > 0:
     939                                  file.write(template % (u'029', u'6332', u'GNUmed::3000::source'))
     940                                  file.write(template % (u'%03d' % (9 + len(external_id_type)), u'6333', external_id_type))
     941
     942                  file.close()
     943          #--------------------------------------------------------
     944          # occupations API
     945          #--------------------------------------------------------
     946 -        def get_occupations(self):
     947                  return gmDemographicRecord.get_occupations(pk_identity = self.pk_obj)
     948          #--------------------------------------------------------
     949 -        def link_occupation(self, occupation=None, activities=None):
     950                  """Link an occupation with a patient, creating the occupation if it does not exists.
     951
     952                          @param occupation The name of the occupation to link the patient to.
     953                  """
     954                  if (activities is None) and (occupation is None):
     955                          return True
     956
     957                  occupation = occupation.strip()
     958                  if len(occupation) == 0:
     959                          return True
     960
     961                  if activities is not None:
     962                          activities = activities.strip()
     963
     964                  args = {'act': activities, 'pat_id': self.pk_obj, 'job': occupation}
     965
     966                  cmd = u"select activities from dem.v_person_jobs where pk_identity = %(pat_id)s and l10n_occupation = _(%(job)s)"
     967                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
     968
     969                  queries = []
     970                  if len(rows) == 0:
     971                          queries.append ({
     972                                  'cmd': u"INSERT INTO dem.lnk_job2person (fk_identity, fk_occupation, activities) VALUES (%(pat_id)s, dem.create_occupation(%(job)s), %(act)s)",
     973                                  'args': args
     974                          })
     975                  else:
     976                          if rows[0]['activities'] != activities:
     977                                  queries.append ({
     978                                          'cmd': u"update dem.lnk_job2person set activities=%(act)s where fk_identity=%(pat_id)s and fk_occupation=(select id from dem.occupation where _(name) = _(%(job)s))",
     979                                          'args': args
     980                                  })
     981
     982                  rows, idx = gmPG2.run_rw_queries(queries = queries)
     983
     984                  return True
     985          #--------------------------------------------------------
     986 -        def unlink_occupation(self, occupation=None):
     987                  if occupation is None:
     988                          return True
     989                  occupation = occupation.strip()
     990                  cmd = u"delete from dem.lnk_job2person where fk_identity=%(pk)s and fk_occupation in (select id from dem.occupation where _(name) = _(%(job)s))"
     991                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj, 'job': occupation}}])
     992                  return True
     993          #--------------------------------------------------------
     994          # comms API
     995          #--------------------------------------------------------
     996 -        def get_comm_channels(self, comm_medium=None):
     997                  cmd = u"select * from dem.v_person_comms where pk_identity = %s"
     998                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True)
     999
    1000                  filtered = rows
    1001
    1002                  if comm_medium is not None:
    1003                          filtered = []
    1004                          for row in rows:
    1005                                  if row['comm_type'] == comm_medium:
    1006                                          filtered.append(row)
    1007
    1008                  return [ gmDemographicRecord.cCommChannel(row = {
    1009                                          'pk_field': 'pk_lnk_identity2comm',
    1010                                          'data': r,
    1011                                          'idx': idx
    1012                                  }) for r in filtered
    1013                          ]
    1014          #--------------------------------------------------------
    1015 -        def link_comm_channel(self, comm_medium=None, url=None, is_confidential=False, pk_channel_type=None):
    1016                  """Link a communication medium with a patient.
    1017
    1018                  @param comm_medium The name of the communication medium.
    1019                  @param url The communication resource locator.
    1020                  @type url A types.StringType instance.
    1021                  @param is_confidential Wether the data must be treated as confidential.
    1022                  @type is_confidential A types.BooleanType instance.
    1023                  """
    1024                  comm_channel = gmDemographicRecord.create_comm_channel (
    1025                          comm_medium = comm_medium,
    1026                          url = url,
    1027                          is_confidential = is_confidential,
    1028                          pk_channel_type = pk_channel_type,
    1029                          pk_identity = self.pk_obj
    1030                  )
    1031                  return comm_channel
    1032          #--------------------------------------------------------
    1033 -        def unlink_comm_channel(self, comm_channel=None):
    1034                  gmDemographicRecord.delete_comm_channel (
    1035                          pk = comm_channel['pk_lnk_identity2comm'],
    1036                          pk_patient = self.pk_obj
    1037                  )
    1038          #--------------------------------------------------------
    1039          # contacts API
    1040          #--------------------------------------------------------
    1041 -        def get_addresses(self, address_type=None):
    1042
    1043                  cmd = u"SELECT * FROM dem.v_pat_addresses WHERE pk_identity = %(pat)s"
    1044                  args = {'pat': self.pk_obj}
    1045                  if address_type is not None:
    1046                          cmd = cmd + u" AND address_type = %(typ)s"
    1047                          args['typ'] = address_type
    1048
    1049                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1050
    1051                  return [
    1052                          gmDemographicRecord.cPatientAddress(row = {'idx': idx, 'data': r, 'pk_field': 'pk_address'})
    1053                          for r in rows
    1054                  ]
    1055          #--------------------------------------------------------
    1056 -        def link_address(self, number=None, street=None, postcode=None, urb=None, state=None, country=None, subunit=None, suburb=None, id_type=None, address=None):
    1057                  """Link an address with a patient, creating the address if it does not exists.
    1058
    1059                  @param id_type The primary key of the address type.
    1060                  """
    1061                  if address is None:
    1062                          address = gmDemographicRecord.create_address (
    1063                                  country = country,
    1064                                  state = state,
    1065                                  urb = urb,
    1066                                  suburb = suburb,
    1067                                  postcode = postcode,
    1068                                  street = street,
    1069                                  number = number,
    1070                                  subunit = subunit
    1071                          )
    1072
    1073                  if address is None:
    1074                          return None
    1075
    1076                  # already linked ?
    1077                  cmd = u"SELECT id_address FROM dem.lnk_person_org_address WHERE id_identity = %(pat)s AND id_address = %(adr)s"
    1078                  args = {'pat': self.pk_obj, 'adr': address['pk_address']}
    1079                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    1080
    1081                  # no, link to person
    1082                  if len(rows) == 0:
    1083                          args = {'id': self.pk_obj, 'adr': address['pk_address'], 'type': id_type}
    1084                          cmd = u"""
    1085                                  INSERT INTO dem.lnk_person_org_address(id_identity, id_address)
    1086                                  VALUES (%(id)s, %(adr)s)
    1087                                  RETURNING id_address"""
    1088                          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
    1089
    1090                  linked_adr = gmDemographicRecord.cPatientAddress(aPK_obj = rows[0]['id_address'])
    1091
    1092                  # possibly change type
    1093                  if id_type is not None:
    1094                          linked_adr['pk_address_type'] = id_type
    1095                          linked_adr.save()
    1096
    1097                  return linked_adr
    1098          #----------------------------------------------------------------------
    1099 -        def unlink_address(self, address=None, pk_address=None):
    1100                  """Remove an address from the patient.
    1101
    1102                  The address itself stays in the database.
    1103                  The address can be either cAdress or cPatientAdress.
    1104                  """
    1105                  if pk_address is None:
    1106                          args = {'person': self.pk_obj, 'adr': address['pk_address']}
    1107                  else:
    1108                          args = {'person': self.pk_obj, 'adr': pk_address}
    1109                  cmd = u"DELETE FROM dem.lnk_person_org_address WHERE id_identity = %(person)s AND id_address = %(adr)s"
    1110                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    1111          #----------------------------------------------------------------------
    1112          # relatives API
    1113          #----------------------------------------------------------------------
    1114 -        def get_relatives(self):
    1115                  cmd = u"""
    1116                          select
    1117                                  t.description,
    1118                                  vbp.pk_identity as id,
    1119                                  title,
    1120                                  firstnames,
    1121                                  lastnames,
    1122                                  dob,
    1123                                  cob,
    1124                                  gender,
    1125                                  karyotype,
    1126                                  pupic,
    1127                                  pk_marital_status,
    1128                                  marital_status,
    1129                                  xmin_identity,
    1130                                  preferred
    1131                          from
    1132                                  dem.v_basic_person vbp, dem.relation_types t, dem.lnk_person2relative l
    1133                          where
    1134                                  (
    1135                                          l.id_identity = %(pk)s and
    1136                                          vbp.pk_identity = l.id_relative and
    1137                                          t.id = l.id_relation_type
    1138                                  ) or (
    1139                                          l.id_relative = %(pk)s and
    1140                                          vbp.pk_identity = l.id_identity and
    1141                                          t.inverse = l.id_relation_type
    1142                                  )"""
    1143                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
    1144                  if len(rows) == 0:
    1145                          return []
    1146                  return [(row[0], cIdentity(row = {'data': row[1:], 'idx':idx, 'pk_field': 'pk'})) for row in rows]
    1147          #--------------------------------------------------------
    1148 -        def link_new_relative(self, rel_type = 'parent'):
    1149                  # create new relative
    1150                  id_new_relative = create_dummy_identity()
    1151
    1152                  relative = cIdentity(aPK_obj=id_new_relative)
    1153                  # pre-fill with data from ourselves
    1154  #               relative.copy_addresses(self)
    1155                  relative.add_name( '**?**', self.get_names()['lastnames'])
    1156                  # and link the two
    1157                  if self._ext_cache.has_key('relatives'):
    1158                          del self._ext_cache['relatives']
    1159                  cmd = u"""
    1160                          insert into dem.lnk_person2relative (
    1161                                  id_identity, id_relative, id_relation_type
    1162                          ) values (
    1163                                  %s, %s, (select id from dem.relation_types where description = %s)
    1164                          )"""
    1165                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [self.ID, id_new_relative, rel_type  ]}])
    1166                  return True
    1167          #----------------------------------------------------------------------
    1168 -        def delete_relative(self, relation):
    1169                  # unlink only, don't delete relative itself
    1170                  self.set_relative(None, relation)
    1171          #--------------------------------------------------------
    1172 -        def _get_emergency_contact_from_database(self):
    1173                  if self._payload[self._idx['pk_emergency_contact']] is None:
    1174                          return None
    1175                  return cIdentity(aPK_obj = self._payload[self._idx['pk_emergency_contact']])
    1176
    1177          emergency_contact_in_database = property(_get_emergency_contact_from_database, lambda x:x)
    1178          #----------------------------------------------------------------------
    1179          # age/dob related
    1180          #----------------------------------------------------------------------
    1181 -        def get_formatted_dob(self, format='%Y %b %d', encoding=None, none_string=None):
    1182                  return gmDateTime.format_dob (
    1183                          self._payload[self._idx['dob']],
    1184                          format = format,
    1185                          encoding = encoding,
    1186                          none_string = none_string,
    1187                          dob_is_estimated = self._payload[self._idx['dob_is_estimated']]
    1188                  )
    1189          #----------------------------------------------------------------------
    1190 -        def get_medical_age(self):
    1191                  dob = self['dob']
    1192
    1193                  if dob is None:
    1194                          return u'??'
    1195
    1196                  if dob > gmDateTime.pydt_now_here():
    1197                          return _('invalid age: DOB in the future')
    1198
    1199                  death = self['deceased']
    1200
    1201                  if death is None:
    1202                          return u'%s%s' % (
    1203                                  gmTools.bool2subst (
    1204                                          self._payload[self._idx['dob_is_estimated']],
    1205                                          gmTools.u_almost_equal_to,
    1206                                          u''
    1207                                  ),
    1208                                  gmDateTime.format_apparent_age_medically (
    1209                                          age = gmDateTime.calculate_apparent_age(start = dob)
    1210                                  )
    1211                          )
    1212
    1213                  if dob > death:
    1214                          return _('invalid age: DOB after death')
    1215
    1216                  return u'%s%s%s' % (
    1217                          gmTools.u_latin_cross,
    1218                          gmTools.bool2subst (
    1219                                  self._payload[self._idx['dob_is_estimated']],
    1220                                  gmTools.u_almost_equal_to,
    1221                                  u''
    1222                          ),
    1223                          gmDateTime.format_apparent_age_medically (
    1224                                  age = gmDateTime.calculate_apparent_age (
    1225                                          start = dob,
    1226                                          end = self['deceased']
    1227                                  )
    1228                          )
    1229                  )
    1230          #----------------------------------------------------------------------
    1231 -        def dob_in_range(self, min_distance=u'1 week', max_distance=u'1 week'):
    1232                  cmd = u'select dem.dob_is_in_range(%(dob)s, %(min)s, %(max)s)'
    1233                  rows, idx = gmPG2.run_ro_queries (
    1234                          queries = [{
    1235                                  'cmd': cmd,
    1236                                  'args': {'dob': self['dob'], 'min': min_distance, 'max': max_distance}
    1237                          }]
    1238                  )
    1239                  return rows[0][0]
    1240          #----------------------------------------------------------------------
    1241          # practice related
    1242          #----------------------------------------------------------------------
    1243 -        def get_last_encounter(self):
    1244                  cmd = u'select * from clin.v_most_recent_encounters where pk_patient=%s'
    1245                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self._payload[self._idx['pk_identity']]]}])
    1246                  if len(rows) > 0:
    1247                          return rows[0]
    1248                  else:
    1249                          return None
    1250          #--------------------------------------------------------
    1251 -        def get_messages(self, order_by=None):
    1252                  return gmProviderInbox.get_inbox_messages(pk_patient = self._payload[self._idx['pk_identity']], order_by = order_by)
    1253
    1254          messages = property(get_messages, lambda x:x)
    1255          #--------------------------------------------------------
    1256 -        def _get_overdue_messages(self):
    1257                  return gmProviderInbox.get_overdue_messages(pk_patient = self._payload[self._idx['pk_identity']])
    1258
    1259          overdue_messages = property(_get_overdue_messages, lambda x:x)
    1260          #--------------------------------------------------------
    1261 -        def delete_message(self, pk=None):
    1262                  return gmProviderInbox.delete_inbox_message(inbox_message = pk)
    1263          #--------------------------------------------------------
    1264 -        def _get_dynamic_hints(self):
    1265                  return gmProviderInbox.get_hints_for_patient(pk_identity = self._payload[self._idx['pk_identity']])
    1266
    1267          dynamic_hints = property(_get_dynamic_hints, lambda x:x)
    1268          #--------------------------------------------------------
    1269 -        def _get_primary_provider(self):
    1270                  if self._payload[self._idx['pk_primary_provider']] is None:
    1271                          return None
    1272                  from Gnumed.business import gmStaff
    1273                  return gmStaff.cStaff(aPK_obj = self._payload[self._idx['pk_primary_provider']])
    1274
    1275          primary_provider = property(_get_primary_provider, lambda x:x)
    1276          #----------------------------------------------------------------------
    1277          # convenience
    1278          #----------------------------------------------------------------------
    1279 -        def get_dirname(self):
    1280                  """Format patient demographics into patient specific path name fragment."""
    1281                  return (u'%s-%s%s-%s' % (
    1282                          self._payload[self._idx['lastnames']].replace(u' ', u'_'),
    1283                          self._payload[self._idx['firstnames']].replace(u' ', u'_'),
    1284                          gmTools.coalesce(self._payload[self._idx['preferred']], u'', template_initial = u'-(%s)').replace(u' ', u'_'),
    1285                          self.get_formatted_dob(format = '%Y-%m-%d', encoding = gmI18N.get_encoding())
    1286                  )).replace (
    1287                          u"'", u""
    1288                  ).replace (
    1289                          u'"', u''
    1290                  ).replace (
    1291                          u'/', u'_'
    1292                  ).replace (
    1293                          u'\\', u'_'
    1294                  ).replace (
    1295                          u'~', u''
    1296                  ).replace (
    1297                          u'|', u'_'
    1298                  ).replace (
    1299                          u'*', u''
    1300                  ).replace (
    1301                          u'\u2248', u''                  # "approximately", having been added by dob_is_estimated
    1302                  )
    1303
    1304          dirname = property(get_dirname, lambda x:x)
    1305          #----------------------------------------------------------------------
    1306 -        def _get_tray_dir_name(self):
    1307                  paths = gmTools.gmPaths()
    1308                  return os.path.join(paths.tmp_dir, self.dirname)
    1309
    1310          tray_dir_name = property(_get_tray_dir_name, lambda x:x)
    1311  #============================================================
    1312  # helper functions
    1313  #------------------------------------------------------------
    1314  #_spin_on_emr_access = None
    1315  #
    1316  #def set_emr_access_spinner(func=None):
    1317  #       if not callable(func):
    1318  #               _log.error('[%] not callable, not setting _spin_on_emr_access', func)
    1319  #               return False
    1320  #
    1321  #       _log.debug('setting _spin_on_emr_access to [%s]', func)
    1322  #
    1323  #       global _spin_on_emr_access
    1324  #       _spin_on_emr_access = func
    1325
    1326  #============================================================
    1327 -class cPatient(cIdentity):
    1328          """Represents a person which is a patient.
    1329
    1330          - a specializing subclass of cIdentity turning it into a patient
    1331          - its use is to cache subobjects like EMR and document folder
    1332          """
    1333 -        def __init__(self, aPK_obj=None, row=None):
    1334                  cIdentity.__init__(self, aPK_obj=aPK_obj, row=row)
    1335                  self.__db_cache = {}
    1336                  self.__emr_access_lock = threading.Lock()
    1337          #--------------------------------------------------------
    1338 -        def cleanup(self):
    1339                  """Do cleanups before dying.
    1340
    1341                  - note that this may be called in a thread
    1342                  """
    1343                  if self.__db_cache.has_key('clinical record'):
    1344                          self.__db_cache['clinical record'].cleanup()
    1345                  if self.__db_cache.has_key('document folder'):
    1346                          self.__db_cache['document folder'].cleanup()
    1347                  cIdentity.cleanup(self)
    1348          #----------------------------------------------------------
    1349 -        def get_emr(self, allow_user_interaction=True):
    1350                  if not self.__emr_access_lock.acquire(False):
    1351                          # maybe something slow is happening on the machine
    1352                          _log.debug('failed to acquire EMR access lock, sleeping for 500ms')
    1353                          time.sleep(0.5)
    1354                          if not self.__emr_access_lock.acquire(False):
    1355                                  _log.debug('still failed to acquire EMR access lock, aborting')
    1356                                  raise AttributeError('cannot lock access to EMR')
    1357                  try:
    1358                          self.__db_cache['clinical record']
    1359                  except KeyError:
    1360                          self.__db_cache['clinical record'] = gmClinicalRecord.cClinicalRecord(aPKey = self._payload[self._idx['pk_identity']], allow_user_interaction = allow_user_interaction)
    1361                  self.__emr_access_lock.release()
    1362                  return self.__db_cache['clinical record']
    1363
    1364          emr = property(get_emr, lambda x:x)
    1365          #--------------------------------------------------------
    1366 -        def get_document_folder(self):
    1367                  try:
    1368                          return self.__db_cache['document folder']
    1369                  except KeyError:
    1370                          pass
    1371
    1372                  self.__db_cache['document folder'] = cDocumentFolder(aPKey = self._payload[self._idx['pk_identity']])
    1373                  return self.__db_cache['document folder']
    1374
    1375          document_folder = property(get_document_folder, lambda x:x)
    1376  #============================================================
    1377 -class gmCurrentPatient(gmBorg.cBorg):
    1378          """Patient Borg to hold currently active patient.
    1379
    1380          There may be many instances of this but they all share state.
    1381          """
    1382 -        def __init__(self, patient=None, forced_reload=False):
    1383                  """Change or get currently active patient.
    1384
    1385                  patient:
    1386                  * None: get currently active patient
    1387                  * -1: unset currently active patient
    1388                  * cPatient instance: set active patient if possible
    1389                  """
    1390                  # make sure we do have a patient pointer
    1391                  try:
    1392                          tmp = self.patient
    1393                  except AttributeError:
    1394                          self.patient = gmNull.cNull()
    1395                          self.__register_interests()
    1396                          # set initial lock state,
    1397                          # this lock protects against activating another patient
    1398                          # when we are controlled from a remote application
    1399                          self.__lock_depth = 0
    1400                          # initialize callback state
    1401                          self.__pre_selection_callbacks = []
    1402
    1403                  # user wants copy of current patient
    1404                  if patient is None:
    1405                          return None
    1406
    1407                  # do nothing if patient is locked
    1408                  if self.locked:
    1409                          _log.error('patient [%s] is locked, cannot change to [%s]' % (self.patient['pk_identity'], patient))
    1410                          return None
    1411
    1412                  # user wants to explicitly unset current patient
    1413                  if patient == -1:
    1414                          _log.debug('explicitly unsetting current patient')
    1415                          if not self.__run_pre_selection_callbacks():
    1416                                  _log.debug('not unsetting current patient')
    1417                                  return None
    1418                          self.__send_pre_selection_notification()
    1419                          self.patient.cleanup()
    1420                          self.patient = gmNull.cNull()
    1421                          self.__send_selection_notification()
    1422                          return None
    1423
    1424                  # must be cPatient instance, then
    1425                  if not isinstance(patient, cPatient):
    1426                          _log.error('cannot set active patient to [%s], must be either None, -1 or cPatient instance' % str(patient))
    1427                          raise TypeError, 'gmPerson.gmCurrentPatient.__init__(): <patient> must be None, -1 or cPatient instance but is: %s' % str(patient)
    1428
    1429                  # same ID, no change needed
    1430                  if (self.patient['pk_identity'] == patient['pk_identity']) and not forced_reload:
    1431                          return None
    1432
    1433                  # user wants different patient
    1434                  _log.debug('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity'])
    1435
    1436                  # everything seems swell
    1437                  if not self.__run_pre_selection_callbacks():
    1438                          _log.debug('not changing current patient')
    1439                          return None
    1440                  self.__send_pre_selection_notification()
    1441                  self.patient.cleanup()
    1442                  self.patient = patient
    1443                  self.patient.get_emr()
    1444                  self.__send_selection_notification()
    1445
    1446                  return None
    1447          #--------------------------------------------------------
    1448 -        def __register_interests(self):
    1449                  gmDispatcher.connect(signal = u'dem.identity_mod_db', receiver = self._on_identity_change)
    1450                  gmDispatcher.connect(signal = u'dem.names_mod_db', receiver = self._on_identity_change)
    1451          #--------------------------------------------------------
    1452 -        def _on_identity_change(self):
    1453                  """Listen for patient *data* change."""
    1454                  self.patient.refetch_payload()
    1455          #--------------------------------------------------------
    1456          # external API
    1457          #--------------------------------------------------------
    1458 -        def register_pre_selection_callback(self, callback=None):
    1459                  if not callable(callback):
    1460                          raise TypeError(u'callback [%s] not callable' % callback)
    1461
    1462                  self.__pre_selection_callbacks.append(callback)
    1463          #--------------------------------------------------------
    1464 -        def _get_connected(self):
    1465                  return (not isinstance(self.patient, gmNull.cNull))
    1466
    1467 -        def _set_connected(self):
    1468                  raise AttributeError(u'invalid to set <connected> state')
    1469
    1470          connected = property(_get_connected, _set_connected)
    1471          #--------------------------------------------------------
    1472 -        def _get_locked(self):
    1473                  return (self.__lock_depth > 0)
    1474
    1475 -        def _set_locked(self, locked):
    1476                  if locked:
    1477                          self.__lock_depth = self.__lock_depth + 1
    1478                          gmDispatcher.send(signal='patient_locked')
    1479                  else:
    1480                          if self.__lock_depth == 0:
    1481                                  _log.error('lock/unlock imbalance, trying to refcount lock depth below 0')
    1482                                  return
    1483                          else:
    1484                                  self.__lock_depth = self.__lock_depth - 1
    1485                          gmDispatcher.send(signal='patient_unlocked')
    1486
    1487          locked = property(_get_locked, _set_locked)
    1488          #--------------------------------------------------------
    1489 -        def force_unlock(self):
    1490                  _log.info('forced patient unlock at lock depth [%s]' % self.__lock_depth)
    1491                  self.__lock_depth = 0
    1492                  gmDispatcher.send(signal='patient_unlocked')
    1493          #--------------------------------------------------------
    1494          # patient change handling
    1495          #--------------------------------------------------------
    1496 -        def __run_pre_selection_callbacks(self):
    1497                  if isinstance(self.patient, gmNull.cNull):
    1498                          return True
    1499
    1500                  for call_back in self.__pre_selection_callbacks:
    1501                          try:
    1502                                  successful = call_back()
    1503                          except:
    1504                                  _log.exception('callback [%s] failed', call_back)
    1505                                  print "*** pre-selection callback failed ***"
    1506                                  print type(call_back)
    1507                                  print call_back
    1508                                  return False
    1509
    1510                          if not successful:
    1511                                  _log.debug('callback [%s] returned False', call_back)
    1512                                  return False
    1513
    1514                  return True
    1515          #--------------------------------------------------------
    1516 -        def __send_pre_selection_notification(self):
    1517                  """Sends signal when another patient is about to become active.
    1518
    1519                  This does NOT wait for signal handlers to complete.
    1520                  """
    1521                  kwargs = {
    1522                          'signal': u'pre_patient_selection',
    1523                          'sender': id(self.__class__),
    1524                          'pk_identity': self.patient['pk_identity']
    1525                  }
    1526                  gmDispatcher.send(**kwargs)
    1527          #--------------------------------------------------------
    1528 -        def __send_selection_notification(self):
    1529                  """Sends signal when another patient has actually been made active."""
    1530                  kwargs = {
    1531                          'signal': u'post_patient_selection',
    1532                          'sender': id(self.__class__),
    1533                          'pk_identity': self.patient['pk_identity']
    1534                  }
    1535                  gmDispatcher.send(**kwargs)
    1536          #--------------------------------------------------------
    1537          # __getattr__ handling
    1538          #--------------------------------------------------------
    1539 -        def __getattr__(self, attribute):
    1540                  if attribute == 'patient':
    1541                          raise AttributeError
    1542                  if not isinstance(self.patient, gmNull.cNull):
    1543                          return getattr(self.patient, attribute)
    1544          #--------------------------------------------------------
    1545          # __get/setitem__ handling
    1546          #--------------------------------------------------------
    1547 -        def __getitem__(self, attribute = None):
    1548                  """Return any attribute if known how to retrieve it by proxy.
    1549                  """
    1550                  return self.patient[attribute]
    1551          #--------------------------------------------------------
    1552 -        def __setitem__(self, attribute, value):
    1553                  self.patient[attribute] = value
    1554  #============================================================
    1555  # match providers
    1556  #============================================================
    1557 -class cMatchProvider_Provider(gmMatchProvider.cMatchProvider_SQL2):
    1558 -        def __init__(self):
    1559                  gmMatchProvider.cMatchProvider_SQL2.__init__(
    1560                          self,
    1561                          queries = [
    1562                                  u"""SELECT
    1563                                                  pk_staff AS data,
    1564                                                  short_alias || ' (' || coalesce(title, '') || ' ' || firstnames || ' ' || lastnames || ')' AS list_label,
    1565                                                  short_alias || ' (' || coalesce(title, '') || ' ' || firstnames || ' ' || lastnames || ')' AS field_label
    1566                                          FROM dem.v_staff
    1567                                          WHERE
    1568                                                  is_active AND (
    1569                                                          short_alias %(fragment_condition)s OR
    1570                                                          firstnames %(fragment_condition)s OR
    1571                                                          lastnames %(fragment_condition)s OR
    1572                                                          db_user %(fragment_condition)s
    1573                                                  )
    1574                                  """
    1575                          ]
    1576                  )
    1577                  self.setThresholds(1, 2, 3)
    1578  #============================================================
    1579  # convenience functions
    1580  #============================================================
    1581 -def create_name(pk_person, firstnames, lastnames, active=False):
    1582          queries = [{
    1583                  'cmd': u"select dem.add_name(%s, %s, %s, %s)",
    1584                  'args': [pk_person, firstnames, lastnames, active]
    1585          }]
    1586          rows, idx = gmPG2.run_rw_queries(queries=queries, return_data=True)
    1587          name = cPersonName(aPK_obj = rows[0][0])
    1588          return name
    1589  #============================================================
    1590 -def create_identity(gender=None, dob=None, lastnames=None, firstnames=None):
    1591
    1592          cmd1 = u"""INSERT INTO dem.identity (gender, dob) VALUES (%s, %s)"""
    1593          cmd2 = u"""
    1594  INSERT INTO dem.names (
    1595          id_identity, lastnames, firstnames
    1596  ) VALUES (
    1597          currval('dem.identity_pk_seq'), coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx')
    1598  ) RETURNING id_identity"""
    1599          rows, idx = gmPG2.run_rw_queries (
    1600                  queries = [
    1601                          {'cmd': cmd1, 'args': [gender, dob]},
    1602                          {'cmd': cmd2, 'args': [lastnames, firstnames]}
    1603                  ],
    1604                  return_data = True
    1605          )
    1606          ident = cIdentity(aPK_obj=rows[0][0])
    1607          gmHooks.run_hook_script(hook = u'post_person_creation')
    1608          return ident
    1609  #============================================================
    1610 -def create_dummy_identity():
    1611          cmd = u"INSERT INTO dem.identity(gender) VALUES ('xxxDEFAULTxxx') RETURNING pk"
    1612          rows, idx = gmPG2.run_rw_queries (
    1613                  queries = [{'cmd': cmd}],
    1614                  return_data = True
    1615          )
    1616          return gmDemographicRecord.cIdentity(aPK_obj = rows[0][0])
    1617  #============================================================
    1618 -def set_active_patient(patient=None, forced_reload=False):
    1619          """Set active patient.
    1620
    1621          If patient is -1 the active patient will be UNset.
    1622          """
    1623          if isinstance(patient, cPatient):
    1624                  pat = patient
    1625          elif isinstance(patient, cIdentity):
    1626                  pat = cPatient(aPK_obj = patient['pk_identity'])
    1627  #       elif isinstance(patient, cStaff):
    1628  #               pat = cPatient(aPK_obj=patient['pk_identity'])
    1629          elif isinstance(patient, gmCurrentPatient):
    1630                  pat = patient.patient
    1631          elif patient == -1:
    1632                  pat = patient
    1633          else:
    1634                  # maybe integer ?
    1635                  success, pk = gmTools.input2int(initial = patient, minval = 1)
    1636                  if not success:
    1637                          raise ValueError('<patient> must be either -1, >0, or a cPatient, cIdentity or gmCurrentPatient instance, is: %s' % patient)
    1638                  # but also valid patient ID ?
    1639                  try:
    1640                          pat = cPatient(aPK_obj = pk)
    1641                  except:
    1642                          _log.exception('error changing active patient to [%s]' % patient)
    1643                          return False
    1644
    1645          # attempt to switch
    1646          try:
    1647                  gmCurrentPatient(patient = pat, forced_reload = forced_reload)
    1648          except:
    1649                  _log.exception('error changing active patient to [%s]' % patient)
    1650                  return False
    1651
    1652          return True
    1653  #============================================================
    1654  # gender related
    1655  #------------------------------------------------------------
    1656 -def get_gender_list():
    1657          """Retrieves the list of known genders from the database."""
    1658          global __gender_idx
    1659          global __gender_list
    1660
    1661          if __gender_list is None:
    1662                  cmd = u"select tag, l10n_tag, label, l10n_label, sort_weight from dem.v_gender_labels order by sort_weight desc"
    1663                  __gender_list, __gender_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
    1664
    1665          return (__gender_list, __gender_idx)
    1666  #------------------------------------------------------------
    1667  map_gender2mf = {
    1668          'm': u'm',
    1669          'f': u'f',
    1670          'tf': u'f',
    1671          'tm': u'm',
    1672          'h': u'mf'
    1673  }
    1674  #------------------------------------------------------------
    1675  # Maps GNUmed related i18n-aware gender specifiers to a unicode symbol.
    1676  map_gender2symbol = {
    1677          'm': u'\u2642',
    1678          'f': u'\u2640',
    1679          'tf': u'\u26A5\u2640',
    1680          'tm': u'\u26A5\u2642',
    1681          'h': u'\u26A5'
    1682  #       'tf': u'\u2642\u2640-\u2640',
    1683  #       'tm': u'\u2642\u2640-\u2642',
    1684  #       'h': u'\u2642\u2640'
    1685  }
    1686  #------------------------------------------------------------
    1687 -def map_gender2string(gender=None):
    1688          """Maps GNUmed related i18n-aware gender specifiers to a human-readable string."""
    1689
    1690          global __gender2string_map
    1691
    1692          if __gender2string_map is None:
    1693                  genders, idx = get_gender_list()
    1694                  __gender2string_map = {
    1695                          'm': _('male'),
    1696                          'f': _('female'),
    1697                          'tf': u'',
    1698                          'tm': u'',
    1699                          'h': u''
    1700                  }
    1701                  for g in genders:
    1702                          __gender2string_map[g[idx['l10n_tag']]] = g[idx['l10n_label']]
    1703                          __gender2string_map[g[idx['tag']]] = g[idx['l10n_label']]
    1704
    1705          return __gender2string_map[gender]
    1706  #------------------------------------------------------------
    1707 -def map_gender2salutation(gender=None):
    1708          """Maps GNUmed related i18n-aware gender specifiers to a human-readable salutation."""
    1709
    1710          global __gender2salutation_map
    1711
    1712          if __gender2salutation_map is None:
    1713                  genders, idx = get_gender_list()
    1714                  __gender2salutation_map = {
    1715                          'm': _('Mr'),
    1716                          'f': _('Mrs'),
    1717                          'tf': u'',
    1718                          'tm': u'',
    1719                          'h': u''
    1720                  }
    1721                  for g in genders:
    1722                          __gender2salutation_map[g[idx['l10n_tag']]] = __gender2salutation_map[g[idx['tag']]]
    1723                          __gender2salutation_map[g[idx['label']]] = __gender2salutation_map[g[idx['tag']]]
    1724                          __gender2salutation_map[g[idx['l10n_label']]] = __gender2salutation_map[g[idx['tag']]]
    1725
    1726          return __gender2salutation_map[gender]
    1727  #------------------------------------------------------------
    1728 -def map_firstnames2gender(firstnames=None):
    1729          """Try getting the gender for the given first name."""
    1730
    1731          if firstnames is None:
    1732                  return None
    1733
    1734          rows, idx = gmPG2.run_ro_queries(queries = [{
    1735                  'cmd': u"select gender from dem.name_gender_map where name ilike %(fn)s limit 1",
    1736                  'args': {'fn': firstnames}
    1737          }])
    1738
    1739          if len(rows) == 0:
    1740                  return None
    1741
    1742          return rows[0][0]
    1743  #============================================================
    1744 -def get_persons_from_pks(pks=None):
    1745          return [ cIdentity(aPK_obj = pk) for pk in pks ]
    1746  #============================================================
    1747 -def get_person_from_xdt(filename=None, encoding=None, dob_format=None):
    1748          from Gnumed.business import gmXdtObjects
    1749          return gmXdtObjects.read_person_from_xdt(filename=filename, encoding=encoding, dob_format=dob_format)
    1750  #============================================================
    1751 -def get_persons_from_pracsoft_file(filename=None, encoding='ascii'):
    1752          from Gnumed.business import gmPracSoftAU
    1753          return gmPracSoftAU.read_persons_from_pracsoft_file(filename=filename, encoding=encoding)
    1754  #============================================================
    1755  # main/testing
    1756  #============================================================
    1757  if __name__ == '__main__':
    1758
    1759          if len(sys.argv) == 1:
    1760                  sys.exit()
    1761
    1762          if sys.argv[1] != 'test':
    1763                  sys.exit()
    1764
    1765          import datetime
    1766
    1767          gmI18N.activate_locale()
    1768          gmI18N.install_domain()
    1769          gmDateTime.init()
    1770
    1771          #--------------------------------------------------------
    1772 -        def test_set_active_pat():
    1773
    1774                  ident = cIdentity(1)
    1775                  print "setting active patient with", ident
    1776                  set_active_patient(patient=ident)
    1777
    1778                  patient = cPatient(12)
    1779                  print "setting active patient with", patient
    1780                  set_active_patient(patient=patient)
    1781
    1782                  pat = gmCurrentPatient()
    1783                  print pat['dob']
    1784                  #pat['dob'] = 'test'
    1785
    1786  #               staff = cStaff()
    1787  #               print "setting active patient with", staff
    1788  #               set_active_patient(patient=staff)
    1789
    1790                  print "setting active patient with -1"
    1791                  set_active_patient(patient=-1)
    1792          #--------------------------------------------------------
    1793 -        def test_dto_person():
    1794                  dto = cDTO_person()
    1795                  dto.firstnames = 'Sepp'
    1796                  dto.lastnames = 'Herberger'
    1797                  dto.gender = 'male'
    1798                  dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone)
    1799                  print dto
    1800
    1801                  print dto['firstnames']
    1802                  print dto['lastnames']
    1803                  print dto['gender']
    1804                  print dto['dob']
    1805
    1806                  for key in dto.keys():
    1807                          print key
    1808          #--------------------------------------------------------
    1809 -        def test_identity():
    1810                  # create patient
    1811                  print '\n\nCreating identity...'
    1812                  new_identity = create_identity(gender='m', dob='2005-01-01', lastnames='test lastnames', firstnames='test firstnames')
    1813                  print 'Identity created: %s' % new_identity
    1814
    1815                  print '\nSetting title and gender...'
    1816                  new_identity['title'] = 'test title';
    1817                  new_identity['gender'] = 'f';
    1818                  new_identity.save_payload()
    1819                  print 'Refetching identity from db: %s' % cIdentity(aPK_obj=new_identity['pk_identity'])
    1820
    1821                  print '\nGetting all names...'
    1822                  for a_name in new_identity.get_names():
    1823                          print a_name
    1824                  print 'Active name: %s' % (new_identity.get_active_name())
    1825                  print 'Setting nickname...'
    1826                  new_identity.set_nickname(nickname='test nickname')
    1827                  print 'Refetching all names...'
    1828                  for a_name in new_identity.get_names():
    1829                          print a_name
    1830                  print 'Active name: %s' % (new_identity.get_active_name())
    1831
    1832                  print '\nIdentity occupations: %s' % new_identity['occupations']
    1833                  print 'Creating identity occupation...'
    1834                  new_identity.link_occupation('test occupation')
    1835                  print 'Identity occupations: %s' % new_identity['occupations']
    1836
    1837                  print '\nIdentity addresses: %s' % new_identity.get_addresses()
    1838                  print 'Creating identity address...'
    1839                  # make sure the state exists in the backend
    1840                  new_identity.link_address (
    1841                          number = 'test 1234',
    1842                          street = 'test street',
    1843                          postcode = 'test postcode',
    1844                          urb = 'test urb',
    1845                          state = 'SN',
    1846                          country = 'DE'
    1847                  )
    1848                  print 'Identity addresses: %s' % new_identity.get_addresses()
    1849
    1850                  print '\nIdentity communications: %s' % new_identity.get_comm_channels()
    1851                  print 'Creating identity communication...'
    1852                  new_identity.link_comm_channel('homephone', '1234566')
    1853                  print 'Identity communications: %s' % new_identity.get_comm_channels()
    1854          #--------------------------------------------------------
    1855 -        def test_name():
    1856                  for pk in range(1,16):
    1857                          name = cPersonName(aPK_obj=pk)
    1858                          print name.description
    1859                          print '  ', name
    1860          #--------------------------------------------------------
    1861 -        def test_gender_list():
    1862                  genders, idx = get_gender_list()
    1863                  print "\n\nRetrieving gender enum (tag, label, weight):"
    1864                  for gender in genders:
    1865                          print "%s, %s, %s" % (gender[idx['tag']], gender[idx['l10n_label']], gender[idx['sort_weight']])
    1866          #--------------------------------------------------------
    1867          #test_dto_person()
    1868          #test_identity()
    1869          #test_set_active_pat()
    1870          #test_search_by_dto()
    1871          #test_name()
    1872          test_gender_list()
    1873
    1874          #map_gender2salutation('m')
    1875          # module functions
    1876
    1877          #comms = get_comm_list()
    1878          #print "\n\nRetrieving communication media enum (id, description): %s" % comms
    1879
    1880  #============================================================
    1881