Gnumed.wxpython.gmEMRStructWidgets

Package Gnumed :: Package wxpython :: Module gmEMRStructWidgets |
[frames] | no frames]

# Source Code for Module Gnumed.wxpython.gmEMRStructWidgets

       1  """GNUmed EMR structure editors
       2
       3          This module contains widgets to create and edit EMR structural
       4          elements (issues, enconters, episodes).
       5
       6          This is based on initial work and ideas by Syan <kittylitter@swiftdsl.com.au>
       7          and Karsten <Karsten.Hilbert@gmx.net>.
       8  """
       9  #================================================================
      10  __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
      11  __license__ = "GPL v2 or later"
      12
      13  # stdlib
      14  import sys
      15  import time
      16  import logging
      17  import datetime as pydt
      18
      19
      20  # 3rd party
      21  import wx
      22
      23
      24  # GNUmed
      25  if __name__ == '__main__':
      26          sys.path.insert(0, '../../')
      27  from Gnumed.pycommon import gmI18N
      28  from Gnumed.pycommon import gmExceptions
      29  from Gnumed.pycommon import gmCfg
      30  from Gnumed.pycommon import gmDateTime
      31  from Gnumed.pycommon import gmTools
      32  from Gnumed.pycommon import gmDispatcher
      33  from Gnumed.pycommon import gmMatchProvider
      34
      35  from Gnumed.business import gmEMRStructItems
      36  from Gnumed.business import gmPraxis
      37  from Gnumed.business import gmPerson
      38
      39  from Gnumed.wxpython import gmPhraseWheel
      40  from Gnumed.wxpython import gmGuiHelpers
      41  from Gnumed.wxpython import gmListWidgets
      42  from Gnumed.wxpython import gmEditArea
      43  from Gnumed.wxpython import gmOrganizationWidgets
      44
      45
      46  _log = logging.getLogger('gm.ui')
      47
      48  #================================================================
      49  # EMR access helper functions
      50  #----------------------------------------------------------------
      51 -def emr_access_spinner(time2spin=0):
      52          """Spin time in seconds."""
      53          if time2spin == 0:
      54                  return
      55          sleep_time = 0.1
      56          total_rounds = int(time2spin / sleep_time)
      57          if total_rounds < 1:
      58                  return
      59          rounds = 0
      60          while rounds < total_rounds:
      61                  wx.Yield()
      62                  time.sleep(sleep_time)
      63                  rounds += 1
      64  #================================================================
      65  # performed procedure related widgets/functions
      66  #----------------------------------------------------------------
      67 -def manage_performed_procedures(parent=None):
      68
      69          pat = gmPerson.gmCurrentPatient()
      70          emr = pat.get_emr()
      71
      72          if parent is None:
      73                  parent = wx.GetApp().GetTopWindow()
      74          #-----------------------------------------
      75          def edit(procedure=None):
      76                  return edit_procedure(parent = parent, procedure = procedure)
      77          #-----------------------------------------
      78          def delete(procedure=None):
      79                  if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']):
      80                          return True
      81
      82                  gmDispatcher.send (
      83                          signal = u'statustext',
      84                          msg = _('Cannot delete performed procedure.'),
      85                          beep = True
      86                  )
      87                  return False
      88          #-----------------------------------------
      89          def refresh(lctrl):
      90                  procs = emr.get_performed_procedures()
      91
      92                  items = [
      93                          [
      94                                  u'%s%s' % (
      95                                          p['clin_when'].strftime('%Y-%m-%d'),
      96                                          gmTools.bool2subst (
      97                                                  p['is_ongoing'],
      98                                                  _(' (ongoing)'),
      99                                                  gmTools.coalesce (
     100                                                          initial = p['clin_end'],
     101                                                          instead = u'',
     102                                                          template_initial = u' - %s',
     103                                                          function_initial = ('strftime', u'%Y-%m-%d')
     104                                                  )
     105                                          )
     106                                  ),
     107                                  u'%s @ %s' % (p['unit'], p['organization']),
     108                                  p['episode'],
     109                                  p['performed_procedure']
     110                          ] for p in procs
     111                  ]
     112                  lctrl.set_string_items(items = items)
     113                  lctrl.set_data(data = procs)
     114          #-----------------------------------------
     115          gmListWidgets.get_choices_from_list (
     116                  parent = parent,
     117                  msg = _('\nSelect the procedure you want to edit !\n'),
     118                  caption = _('Editing performed procedures ...'),
     119                  columns = [_('When'), _('Where'), _('Episode'), _('Procedure')],
     120                  single_selection = True,
     121                  edit_callback = edit,
     122                  new_callback = edit,
     123                  delete_callback = delete,
     124                  refresh_callback = refresh
     125          )
     126  #----------------------------------------------------------------
     127 -def edit_procedure(parent=None, procedure=None):
     128          ea = cProcedureEAPnl(parent = parent, id = -1)
     129          ea.data = procedure
     130          ea.mode = gmTools.coalesce(procedure, 'new', 'edit')
     131          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
     132          dlg.SetTitle(gmTools.coalesce(procedure, _('Adding a procedure'), _('Editing a procedure')))
     133          if dlg.ShowModal() == wx.ID_OK:
     134                  dlg.Destroy()
     135                  return True
     136          dlg.Destroy()
     137          return False
     138  #----------------------------------------------------------------
     139  from Gnumed.wxGladeWidgets import wxgProcedureEAPnl
     140
     141 -class cProcedureEAPnl(wxgProcedureEAPnl.wxgProcedureEAPnl, gmEditArea.cGenericEditAreaMixin):
     142
     143 -        def __init__(self, *args, **kwargs):
     144                  wxgProcedureEAPnl.wxgProcedureEAPnl.__init__(self, *args, **kwargs)
     145                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     146
     147                  self.mode = 'new'
     148                  self.data = None
     149
     150                  self.__init_ui()
     151          #----------------------------------------------------------------
     152 -        def __init_ui(self):
     153                  self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus)
     154                  self._PRW_hospital_stay.set_context(context = 'pat', val = gmPerson.gmCurrentPatient().ID)
     155                  self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus)
     156                  self._DPRW_date.add_callback_on_lose_focus(callback = self._on_start_lost_focus)
     157                  self._DPRW_end.add_callback_on_lose_focus(callback = self._on_end_lost_focus)
     158
     159                  # procedure
     160                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     161                          queries = [
     162  u"""
     163  select distinct on (narrative) narrative, narrative
     164  from clin.procedure
     165  where narrative %(fragment_condition)s
     166  order by narrative
     167  limit 25
     168  """                     ]
     169                  )
     170                  mp.setThresholds(2, 4, 6)
     171                  self._PRW_procedure.matcher = mp
     172          #----------------------------------------------------------------
     173 -        def _on_hospital_stay_lost_focus(self):
     174                  stay = self._PRW_hospital_stay.GetData()
     175                  if stay is None:
     176                          self._PRW_hospital_stay.SetText()
     177                          self._PRW_location.Enable(True)
     178                          self._PRW_location.display_as_disabled(False)
     179                          self._PRW_episode.Enable(True)
     180                          self._PRW_episode.display_as_disabled(False)
     181                          self._LBL_hospital_details.SetLabel(u'')
     182                  else:
     183                          self._PRW_location.SetText()
     184                          self._PRW_location.Enable(False)
     185                          self._PRW_location.display_as_disabled(True)
     186                          self._PRW_episode.SetText()
     187                          self._PRW_episode.Enable(False)
     188                          self._PRW_episode.display_as_disabled(True)
     189                          self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = stay).format())
     190          #----------------------------------------------------------------
     191 -        def _on_location_lost_focus(self):
     192                  loc = self._PRW_location.GetData()
     193                  if loc is None:
     194                          self._PRW_hospital_stay.Enable(True)
     195                          self._PRW_hospital_stay.display_as_disabled(False)
     196                          self._PRW_episode.Enable(False)
     197                          self._PRW_episode.display_as_disabled(True)
     198                  else:
     199                          self._PRW_hospital_stay.SetText()
     200                          self._PRW_hospital_stay.Enable(False)
     201                          self._PRW_hospital_stay.display_as_disabled(True)
     202                          self._PRW_episode.Enable(True)
     203                          self._PRW_episode.display_as_disabled(False)
     204          #----------------------------------------------------------------
     205 -        def _on_start_lost_focus(self):
     206                  if not self._DPRW_date.is_valid_timestamp():
     207                          return
     208                  end = self._DPRW_end.GetData()
     209                  if end is None:
     210                          return
     211                  end = end.get_pydt()
     212                  start = self._DPRW_date.GetData().get_pydt()
     213                  if start < end:
     214                          return
     215                  self._DPRW_date.display_as_valid(False)
     216          #----------------------------------------------------------------
     217 -        def _on_end_lost_focus(self):
     218                  end = self._DPRW_end.GetData()
     219                  if end is None:
     220                          self._CHBOX_ongoing.Enable(True)
     221                          self._DPRW_end.display_as_valid(True)
     222                  else:
     223                          self._CHBOX_ongoing.Enable(False)
     224                          end = end.get_pydt()
     225                          now = gmDateTime.pydt_now_here()
     226                          if end > now:
     227                                  self._CHBOX_ongoing.SetValue(True)
     228                          else:
     229                                  self._CHBOX_ongoing.SetValue(False)
     230                          start = self._DPRW_date.GetData()
     231                          if start is None:
     232                                  self._DPRW_end.display_as_valid(True)
     233                          else:
     234                                  start = start.get_pydt()
     235                                  if end > start:
     236                                          self._DPRW_end.display_as_valid(True)
     237                                  else:
     238                                          self._DPRW_end.display_as_valid(False)
     239          #----------------------------------------------------------------
     240          # generic Edit Area mixin API
     241          #----------------------------------------------------------------
     242 -        def _valid_for_save(self):
     243
     244                  has_errors = False
     245
     246                  if not self._DPRW_date.is_valid_timestamp():
     247                          self._DPRW_date.display_as_valid(False)
     248                          has_errors = True
     249                  else:
     250                          self._DPRW_date.display_as_valid(True)
     251
     252                  start = self._DPRW_date.GetData()
     253                  end = self._DPRW_end.GetData()
     254                  self._DPRW_end.display_as_valid(True)
     255                  if end is not None:
     256                          end = end.get_pydt()
     257                          if start is not None:
     258                                  start = start.get_pydt()
     259                                  if end < start:
     260                                          has_errors = True
     261                                          self._DPRW_end.display_as_valid(False)
     262                          if self._CHBOX_ongoing.IsChecked():
     263                                  now = gmDateTime.pydt_now_here()
     264                                  if end < now:
     265                                          has_errors = True
     266                                          self._DPRW_end.display_as_valid(False)
     267
     268                  if self._PRW_hospital_stay.GetData() is None:
     269                          if self._PRW_episode.GetValue().strip() == u'':
     270                                  self._PRW_episode.display_as_valid(False)
     271                                  has_errors = True
     272                          else:
     273                                  self._PRW_episode.display_as_valid(True)
     274                  else:
     275                          self._PRW_episode.display_as_valid(True)
     276
     277                  if (self._PRW_procedure.GetValue() is None) or (self._PRW_procedure.GetValue().strip() == u''):
     278                          self._PRW_procedure.display_as_valid(False)
     279                          has_errors = True
     280                  else:
     281                          self._PRW_procedure.display_as_valid(True)
     282
     283                  invalid_location = (
     284                          (self._PRW_hospital_stay.GetData() is None) and (self._PRW_location.GetData() is None)
     285                                  or
     286                          (self._PRW_hospital_stay.GetData() is not None) and (self._PRW_location.GetData() is not None)
     287                  )
     288                  if invalid_location:
     289                          self._PRW_hospital_stay.display_as_valid(False)
     290                          self._PRW_location.display_as_valid(False)
     291                          has_errors = True
     292                  else:
     293                          self._PRW_hospital_stay.display_as_valid(True)
     294                          self._PRW_location.display_as_valid(True)
     295
     296                  gmDispatcher.send(signal = 'statustext', msg = _('Cannot save procedure.'), beep = True)
     297
     298                  return (has_errors is False)
     299          #----------------------------------------------------------------
     300 -        def _save_as_new(self):
     301
     302                  pat = gmPerson.gmCurrentPatient()
     303                  emr = pat.get_emr()
     304
     305                  stay = self._PRW_hospital_stay.GetData()
     306                  if stay is None:
     307                          epi = self._PRW_episode.GetData(can_create = True)
     308                  else:
     309                          epi = gmEMRStructItems.cHospitalStay(aPK_obj = stay)['pk_episode']
     310
     311                  proc = emr.add_performed_procedure (
     312                          episode = epi,
     313                          location = self._PRW_location.GetData(),
     314                          hospital_stay = stay,
     315                          procedure = self._PRW_procedure.GetValue().strip()
     316                  )
     317
     318                  proc['clin_when'] = self._DPRW_date.GetData().get_pydt()
     319                  if self._DPRW_end.GetData() is None:
     320                          proc['clin_end'] = None
     321                  else:
     322                          proc['clin_end'] = self._DPRW_end.GetData().get_pydt()
     323                  proc['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
     324                  proc.save()
     325
     326                  proc.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
     327
     328                  self.data = proc
     329
     330                  return True
     331          #----------------------------------------------------------------
     332 -        def _save_as_update(self):
     333                  self.data['clin_when'] = self._DPRW_date.GetData().get_pydt()
     334                  self.data['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
     335                  self.data['pk_org_unit'] = self._PRW_location.GetData()
     336                  self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData()
     337                  self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip()
     338                  if self._DPRW_end.GetData() is None:
     339                          self.data['clin_end'] = None
     340                  else:
     341                          self.data['clin_end'] = self._DPRW_end.GetData().get_pydt()
     342                  if self.data['pk_hospital_stay'] is None:
     343                          self.data['pk_episode'] = self._PRW_episode.GetData()
     344                  else:
     345                          self.data['pk_episode'] = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData())['pk_episode']
     346                  self.data.save()
     347
     348                  self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
     349
     350                  return True
     351          #----------------------------------------------------------------
     352 -        def _refresh_as_new(self):
     353                  self._DPRW_date.SetText()
     354                  self._DPRW_end.SetText()
     355                  self._CHBOX_ongoing.SetValue(False)
     356                  self._CHBOX_ongoing.Enable(True)
     357                  self._PRW_hospital_stay.SetText()
     358                  self._LBL_hospital_details.SetLabel(u'')
     359                  self._PRW_location.SetText()
     360                  self._PRW_episode.SetText()
     361                  self._PRW_procedure.SetText()
     362                  self._PRW_codes.SetText()
     363
     364                  self._PRW_procedure.SetFocus()
     365          #----------------------------------------------------------------
     366 -        def _refresh_from_existing(self):
     367                  self._DPRW_date.SetData(data = self.data['clin_when'])
     368                  if self.data['clin_end'] is None:
     369                          self._DPRW_end.SetText()
     370                          self._CHBOX_ongoing.Enable(True)
     371                          self._CHBOX_ongoing.SetValue(self.data['is_ongoing'])
     372                  else:
     373                          self._DPRW_end.SetData(data = self.data['clin_end'])
     374                          self._CHBOX_ongoing.Enable(False)
     375                          now = gmDateTime.pydt_now_here()
     376                          if self.data['clin_end'] > now:
     377                                  self._CHBOX_ongoing.SetValue(True)
     378                          else:
     379                                  self._CHBOX_ongoing.SetValue(False)
     380                  self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
     381                  self._PRW_procedure.SetText(value = self.data['performed_procedure'], data = self.data['performed_procedure'])
     382
     383                  if self.data['pk_hospital_stay'] is None:
     384                          self._PRW_hospital_stay.SetText()
     385                          self._PRW_hospital_stay.Enable(False)
     386                          self._PRW_hospital_stay.display_as_disabled(True)
     387                          self._LBL_hospital_details.SetLabel(u'')
     388                          self._PRW_location.SetText(value = u'%s @ %s' % (self.data['unit'], self.data['organization']), data = self.data['pk_org_unit'])
     389                          self._PRW_location.Enable(True)
     390                          self._PRW_location.display_as_disabled(False)
     391                          self._PRW_episode.Enable(True)
     392                          self._PRW_episode.display_as_disabled(False)
     393                  else:
     394                          self._PRW_hospital_stay.SetText(value = u'%s @ %s' % (self.data['unit'], self.data['organization']), data = self.data['pk_hospital_stay'])
     395                          self._PRW_hospital_stay.Enable(True)
     396                          self._PRW_hospital_stay.display_as_disabled(False)
     397                          self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = self.data['pk_hospital_stay']).format())
     398                          self._PRW_location.SetText()
     399                          self._PRW_location.Enable(False)
     400                          self._PRW_location.display_as_disabled(True)
     401                          self._PRW_episode.Enable(False)
     402                          self._PRW_episode.display_as_disabled(True)
     403
     404                  val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
     405                  self._PRW_codes.SetText(val, data)
     406
     407                  self._PRW_procedure.SetFocus()
     408          #----------------------------------------------------------------
     409 -        def _refresh_as_new_from_existing(self):
     410                  self._refresh_as_new()
     411
     412                  self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
     413
     414                  if self.data['pk_hospital_stay'] is None:
     415                          self._PRW_hospital_stay.SetText()
     416                          self._PRW_hospital_stay.Enable(False)
     417                          self._PRW_hospital_stay.display_as_disabled(True)
     418                          self._LBL_hospital_details.SetLabel(u'')
     419                          self._PRW_location.SetText(value = u'%s @ %s' % (self.data['unit'], self.data['organization']), data = self.data['pk_org_unit'])
     420                          self._PRW_location.Enable(True)
     421                          self._PRW_location.display_as_disabled(False)
     422                          self._PRW_episode.Enable(True)
     423                          self._PRW_episode.display_as_disabled(False)
     424                  else:
     425                          self._PRW_hospital_stay.SetText(value = u'%s @ %s' % (self.data['unit'], self.data['organization']), data = self.data['pk_hospital_stay'])
     426                          self._PRW_hospital_stay.Enable(True)
     427                          self._PRW_hospital_stay.display_as_disabled(False)
     428                          self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = self.data['pk_hospital_stay']).format())
     429                          self._PRW_location.SetText()
     430                          self._PRW_location.Enable(False)
     431                          self._PRW_location.display_as_disabled(True)
     432                          self._PRW_episode.Enable(False)
     433                          self._PRW_episode.display_as_disabled(True)
     434
     435                  self._PRW_procedure.SetFocus()
     436          #----------------------------------------------------------------
     437          # event handlers
     438          #----------------------------------------------------------------
     439 -        def _on_add_hospital_stay_button_pressed(self, evt):
     440                  # FIXME: this would benefit from setting the created stay
     441                  edit_hospital_stay(parent = self.GetParent())
     442                  evt.Skip()
     443          #----------------------------------------------------------------
     444 -        def _on_add_location_button_pressed(self, event):
     445                  gmOrganizationWidgets.manage_orgs(parent = self)        #self.GetParent())
     446                  event.Skip()
     447          #----------------------------------------------------------------
     448 -        def _on_ongoing_checkbox_checked(self, event):
     449                  if self._CHBOX_ongoing.IsChecked():
     450                          end = self._DPRW_end.GetData()
     451                          if end is None:
     452                                  self._DPRW_end.display_as_valid(True)
     453                          else:
     454                                  end = end.get_pydt()
     455                                  now = gmDateTime.pydt_now_here()
     456                                  if end > now:
     457                                          self._DPRW_end.display_as_valid(True)
     458                                  else:
     459                                          self._DPRW_end.display_as_valid(False)
     460                  else:
     461                          self._DPRW_end.is_valid_timestamp()
     462                  event.Skip()
     463
     464  #================================================================
     465  # hospitalizations related widgets/functions
     466  #----------------------------------------------------------------
     467 -def manage_hospital_stays(parent=None):
     468
     469          pat = gmPerson.gmCurrentPatient()
     470          emr = pat.get_emr()
     471
     472          if parent is None:
     473                  parent = wx.GetApp().GetTopWindow()
     474          #-----------------------------------------
     475          def get_tooltip(stay=None):
     476                  if stay is None:
     477                          return None
     478                  return stay.format (
     479                          include_procedures = True,
     480                          include_docs = True
     481                  )
     482          #-----------------------------------------
     483          def edit(stay=None):
     484                  return edit_hospital_stay(parent = parent, hospital_stay = stay)
     485          #-----------------------------------------
     486          def delete(stay=None):
     487                  if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']):
     488                          return True
     489                  gmDispatcher.send (
     490                          signal = u'statustext',
     491                          msg = _('Cannot delete hospitalization.'),
     492                          beep = True
     493                  )
     494                  return False
     495          #-----------------------------------------
     496          def refresh(lctrl):
     497                  stays = emr.get_hospital_stays()
     498                  items = [
     499                          [
     500                                  s['admission'].strftime('%Y-%m-%d'),
     501                                  gmTools.coalesce(s['discharge'], u'', function_initial = ('strftime', '%Y-%m-%d')),
     502                                  s['episode'],
     503                                  u'%s @ %s' % (s['ward'], s['hospital'])
     504                          ] for s in stays
     505                  ]
     506                  lctrl.set_string_items(items = items)
     507                  lctrl.set_data(data = stays)
     508          #-----------------------------------------
     509          gmListWidgets.get_choices_from_list (
     510                  parent = parent,
     511                  msg = _("The patient's hospitalizations:\n"),
     512                  caption = _('Editing hospitalizations ...'),
     513                  columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')],
     514                  single_selection = True,
     515                  edit_callback = edit,
     516                  new_callback = edit,
     517                  delete_callback = delete,
     518                  refresh_callback = refresh,
     519                  list_tooltip_callback = get_tooltip
     520          )
     521
     522  #----------------------------------------------------------------
     523 -def edit_hospital_stay(parent=None, hospital_stay=None):
     524          ea = cHospitalStayEditAreaPnl(parent = parent, id = -1)
     525          ea.data = hospital_stay
     526          ea.mode = gmTools.coalesce(hospital_stay, 'new', 'edit')
     527          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
     528          dlg.SetTitle(gmTools.coalesce(hospital_stay, _('Adding a hospitalization'), _('Editing a hospitalization')))
     529          if dlg.ShowModal() == wx.ID_OK:
     530                  dlg.Destroy()
     531                  return True
     532          dlg.Destroy()
     533          return False
     534
     535  #----------------------------------------------------------------
     536 -class cHospitalWardPhraseWheel(gmPhraseWheel.cPhraseWheel):
     537          """Phrasewheel to allow selection of a hospitalization."""
     538 -        def __init__(self, *args, **kwargs):
     539
     540                  gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
     541
     542                  query = u"""
     543                  SELECT data, list_label, field_label FROM (
     544                          SELECT DISTINCT ON (data) * FROM ((
     545
     546                                  -- already-used org_units
     547                                  SELECT
     548                                          pk_org_unit
     549                                                  AS data,
     550                                          ward || ' @ ' || hospital
     551                                                  AS list_label,
     552                                          ward || ' @ ' || hospital
     553                                                  AS field_label,
     554                                          1
     555                                                  AS rank
     556                                  FROM
     557                                          clin.v_hospital_stays
     558                                  WHERE
     559                                          ward %(fragment_condition)s
     560                                                  OR
     561                                          hospital %(fragment_condition)s
     562
     563                                  ) UNION ALL (
     564                                  -- wards
     565                                  SELECT
     566                                          pk_org_unit
     567                                                  AS data,
     568                                          unit || ' (' || l10n_unit_category || ') @ ' || organization
     569                                                  AS list_label,
     570                                          unit || ' @ ' || organization
     571                                                  AS field_label,
     572                                          2
     573                                                  AS rank
     574                                  FROM
     575                                          dem.v_org_units
     576                                  WHERE
     577                                          unit_category = 'Ward'
     578                                                  AND
     579                                          unit %(fragment_condition)s
     580                                                  AND
     581                                          NOT EXISTS (
     582                                                  SELECT 1 FROM clin.v_hospital_stays WHERE clin.v_hospital_stays.pk_org_unit = dem.v_org_units.pk_org_unit
     583                                          )
     584
     585                                  ) UNION ALL (
     586                                  -- hospital units
     587                                  SELECT
     588                                          pk_org_unit
     589                                                  AS data,
     590                                          unit || coalesce(' (' || l10n_unit_category || ')', '') || ' @ ' || organization || ' (' || l10n_organization_category || ')'
     591                                                  AS list_label,
     592                                          unit || ' @ ' || organization
     593                                                  AS field_label,
     594                                          3
     595                                                  AS rank
     596                                  FROM
     597                                          dem.v_org_units
     598                                  WHERE
     599                                          unit_category <> 'Ward'
     600                                                  AND
     601                                          organization_category = 'Hospital'
     602                                                  AND
     603                                          unit %(fragment_condition)s
     604                                                  AND
     605                                          NOT EXISTS (
     606                                                  SELECT 1 FROM clin.v_hospital_stays WHERE clin.v_hospital_stays.pk_org_unit = dem.v_org_units.pk_org_unit
     607                                          )
     608
     609                                  ) UNION ALL (
     610                                  -- any other units
     611                                  SELECT
     612                                          pk_org_unit
     613                                                  AS data,
     614                                          unit || coalesce(' (' || l10n_unit_category || ')', '') || ' @ ' || organization || ' (' || l10n_organization_category || ')'
     615                                                  AS list_label,
     616                                          unit || ' @ ' || organization
     617                                                  AS field_label,
     618                                          3
     619                                                  AS rank
     620                                  FROM
     621                                          dem.v_org_units
     622                                  WHERE
     623                                          unit_category <> 'Ward'
     624                                                  AND
     625                                          organization_category <> 'Hospital'
     626                                                  AND
     627                                          unit %(fragment_condition)s
     628                                                  AND
     629                                          NOT EXISTS (
     630                                                  SELECT 1 FROM clin.v_hospital_stays WHERE clin.v_hospital_stays.pk_org_unit = dem.v_org_units.pk_org_unit
     631                                          )
     632                          )) AS all_matches
     633                          ORDER BY data, rank
     634                  ) AS distinct_matches
     635                  ORDER BY rank, list_label
     636                  LIMIT 50
     637                  """
     638
     639                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query])
     640                  mp.setThresholds(2, 4, 6)
     641                  self.matcher = mp
     642                  self.selection_only = True
     643
     644  #----------------------------------------------------------------
     645 -class cHospitalStayPhraseWheel(gmPhraseWheel.cPhraseWheel):
     646          """Phrasewheel to allow selection of a hospital-type org_unit."""
     647 -        def __init__(self, *args, **kwargs):
     648
     649                  gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
     650
     651                  ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}}
     652
     653                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     654                          queries = [
     655  u"""
     656  SELECT
     657          pk_hospital_stay,
     658          descr
     659  FROM (
     660          SELECT DISTINCT ON (pk_hospital_stay)
     661                  pk_hospital_stay,
     662                  descr
     663          FROM
     664                  (SELECT
     665                          pk_hospital_stay,
     666                          (
     667                                  to_char(admission, 'YYYY-Mon-DD')
     668                                  || ' (' || ward || ' @ ' || hospital || '):'
     669                                  || episode
     670                                  || coalesce((' (' || health_issue || ')'), '')
     671                          ) AS descr
     672                   FROM
     673                          clin.v_hospital_stays
     674                   WHERE
     675                          %(ctxt_pat)s
     676
     677                          hospital %(fragment_condition)s
     678                                  OR
     679                          ward %(fragment_condition)s
     680                                  OR
     681                          episode %(fragment_condition)s
     682                                  OR
     683                          health_issue %(fragment_condition)s
     684                  ) AS the_stays
     685  ) AS distinct_stays
     686  ORDER BY descr
     687  LIMIT 25
     688  """                     ],
     689                          context = ctxt
     690                  )
     691                  mp.setThresholds(3, 4, 6)
     692                  mp.set_context('pat', gmPerson.gmCurrentPatient().ID)
     693
     694                  self.matcher = mp
     695                  self.selection_only = True
     696
     697  #----------------------------------------------------------------
     698  from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl
     699
     700 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
     701
     702 -        def __init__(self, *args, **kwargs):
     703                  wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl.__init__(self, *args, **kwargs)
     704                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     705          #----------------------------------------------------------------
     706          # generic Edit Area mixin API
     707          #----------------------------------------------------------------
     708 -        def _valid_for_save(self):
     709
     710                  valid = True
     711
     712                  if self._PRW_episode.GetValue().strip() == u'':
     713                          valid = False
     714                          self._PRW_episode.display_as_valid(False)
     715                          gmDispatcher.send(signal = 'statustext', msg = _('Must select an episode or enter a name for a new one. Cannot save hospitalization.'), beep = True)
     716                          self._PRW_episode.SetFocus()
     717
     718                  if not self._PRW_admission.is_valid_timestamp(allow_empty = False):
     719                          valid = False
     720                          gmDispatcher.send(signal = 'statustext', msg = _('Missing admission data. Cannot save hospitalization.'), beep = True)
     721                          self._PRW_admission.SetFocus()
     722
     723                  if self._PRW_discharge.is_valid_timestamp(allow_empty = True):
     724                          if self._PRW_discharge.date is not None:
     725                                  adm = self._PRW_admission.date
     726                                  discharge = self._PRW_discharge.date
     727                                  # normalize for comparison
     728                                  discharge = discharge.replace (
     729                                          hour = adm.hour,
     730                                          minute = adm.minute,
     731                                          second = adm.second,
     732                                          microsecond = adm.microsecond
     733                                  )
     734                                  if adm is not None:
     735                                          if discharge == adm:
     736                                                  self._PRW_discharge.SetData(discharge + pydt.timedelta(seconds = 1))
     737                                          elif not self._PRW_discharge.date > self._PRW_admission.date:
     738                                                  valid = False
     739                                                  self._PRW_discharge.display_as_valid(False)
     740                                                  gmDispatcher.send(signal = 'statustext', msg = _('Discharge date must be empty or later than admission. Cannot save hospitalization.'), beep = True)
     741                                                  self._PRW_discharge.SetFocus()
     742
     743                  if self._PRW_hospital.GetData() is None:
     744                          self._PRW_hospital.display_as_valid(False)
     745                          self.status_message = _('Must select a hospital. Cannot save hospitalization.')
     746                          self._PRW_hospital.SetFocus()
     747                  else:
     748                          self._PRW_hospital.display_as_valid(True)
     749
     750                  return (valid is True)
     751          #----------------------------------------------------------------
     752 -        def _save_as_new(self):
     753
     754                  pat = gmPerson.gmCurrentPatient()
     755                  emr = pat.get_emr()
     756                  stay = emr.add_hospital_stay(episode = self._PRW_episode.GetData(can_create = True), fk_org_unit = self._PRW_hospital.GetData())
     757                  stay['comment'] = self._TCTRL_comment.GetValue().strip()
     758                  stay['admission'] = self._PRW_admission.GetData()
     759                  stay['discharge'] = self._PRW_discharge.GetData()
     760                  stay['comment'] = self._TCTRL_comment.GetValue()
     761                  stay.save_payload()
     762
     763                  self.data = stay
     764                  return True
     765          #----------------------------------------------------------------
     766 -        def _save_as_update(self):
     767
     768                  self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True)
     769                  self.data['pk_org_unit'] = self._PRW_hospital.GetData()
     770                  self.data['admission'] = self._PRW_admission.GetData()
     771                  self.data['discharge'] = self._PRW_discharge.GetData()
     772                  self.data['comment'] = self._TCTRL_comment.GetValue()
     773                  self.data.save_payload()
     774
     775                  return True
     776          #----------------------------------------------------------------
     777 -        def _refresh_as_new(self):
     778                  self._PRW_hospital.SetText(value = u'', data = None)
     779                  self._PRW_episode.SetText(value = u'')
     780                  self._PRW_admission.SetText(data = gmDateTime.pydt_now_here())
     781                  self._PRW_discharge.SetText()
     782                  self._TCTRL_comment.SetValue(u'')
     783                  self._PRW_hospital.SetFocus()
     784          #----------------------------------------------------------------
     785 -        def _refresh_from_existing(self):
     786                  self._PRW_hospital.SetText(value = u'%s @ %s' % (self.data['ward'], self.data['hospital']), data = self.data['pk_org_unit'])
     787
     788                  if self.data['pk_episode'] is not None:
     789                          self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
     790
     791                  self._PRW_admission.SetText(data = self.data['admission'])
     792                  self._PRW_discharge.SetText(data = self.data['discharge'])
     793                  self._TCTRL_comment.SetValue(self.data['comment'])
     794
     795                  self._PRW_hospital.SetFocus()
     796          #----------------------------------------------------------------
     797 -        def _refresh_as_new_from_existing(self):
     798                  print "this was not expected to be used in this edit area"
     799
     800  #================================================================
     801  # episode related widgets/functions
     802  #----------------------------------------------------------------
     803 -def edit_episode(parent=None, episode=None):
     804          ea = cEpisodeEditAreaPnl(parent = parent, id = -1)
     805          ea.data = episode
     806          ea.mode = gmTools.coalesce(episode, 'new', 'edit')
     807          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
     808          dlg.SetTitle(gmTools.coalesce(episode, _('Adding a new episode'), _('Editing an episode')))
     809          if dlg.ShowModal() == wx.ID_OK:
     810                  return True
     811          return False
     812  #----------------------------------------------------------------
     813 -def promote_episode_to_issue(parent=None, episode=None, emr=None):
     814
     815          created_new_issue = False
     816
     817          try:
     818                  issue = gmEMRStructItems.cHealthIssue(name = episode['description'], patient = episode['pk_patient'])
     819          except gmExceptions.NoSuchBusinessObjectError:
     820                  issue = None
     821
     822          if issue is None:
     823                  issue = emr.add_health_issue(issue_name = episode['description'])
     824                  created_new_issue = True
     825          else:
     826                  # issue exists already, so ask user
     827                  dlg = gmGuiHelpers.c3ButtonQuestionDlg (
     828                          parent,
     829                          -1,
     830                          caption = _('Promoting episode to health issue'),
     831                          question = _(
     832                                  'There already is a health issue\n'
     833                                  '\n'
     834                                  ' %s\n'
     835                                  '\n'
     836                                  'What do you want to do ?'
     837                          ) % issue['description'],
     838                          button_defs = [
     839                                  {'label': _('Use existing'), 'tooltip': _('Move episode into existing health issue'), 'default': False},
     840                                  {'label': _('Create new'), 'tooltip': _('Create a new health issue with another name'), 'default': True}
     841                          ]
     842                  )
     843                  use_existing = dlg.ShowModal()
     844                  dlg.Destroy()
     845
     846                  if use_existing == wx.ID_CANCEL:
     847                          return
     848
     849                  # user wants to create new issue with alternate name
     850                  if use_existing == wx.ID_NO:
     851                          # loop until name modified but non-empty or cancelled
     852                          issue_name = episode['description']
     853                          while issue_name == episode['description']:
     854                                  dlg = wx.TextEntryDialog (
     855                                          parent = parent,
     856                                          message = _('Enter a short descriptive name for the new health issue:'),
     857                                          caption = _('Creating a new health issue ...'),
     858                                          defaultValue = issue_name,
     859                                          style = wx.OK | wx.CANCEL | wx.CENTRE
     860                                  )
     861                                  decision = dlg.ShowModal()
     862                                  if decision != wx.ID_OK:
     863                                          dlg.Destroy()
     864                                          return
     865                                  issue_name = dlg.GetValue().strip()
     866                                  dlg.Destroy()
     867                                  if issue_name == u'':
     868                                          issue_name = episode['description']
     869
     870                          issue = emr.add_health_issue(issue_name = issue_name)
     871                          created_new_issue = True
     872
     873          # eventually move the episode to the issue
     874          if not move_episode_to_issue(episode = episode, target_issue = issue, save_to_backend = True):
     875                  # user cancelled the move so delete just-created issue
     876                  if created_new_issue:
     877                          # shouldn't fail as it is completely new
     878                          gmEMRStructItems.delete_health_issue(health_issue = issue)
     879                  return
     880
     881          return
     882  #----------------------------------------------------------------
     883 -def move_episode_to_issue(episode=None, target_issue=None, save_to_backend=False):
     884          """Prepare changing health issue for an episode.
     885
     886          Checks for two-open-episodes conflict. When this
     887          function succeeds, the pk_health_issue has been set
     888          on the episode instance and the episode should - for
     889          all practical purposes - be ready for save_payload().
     890          """
     891          # episode is closed: should always work
     892          if not episode['episode_open']:
     893                  episode['pk_health_issue'] = target_issue['pk_health_issue']
     894                  if save_to_backend:
     895                          episode.save_payload()
     896                  return True
     897
     898          # un-associate: should always work, too
     899          if target_issue is None:
     900                  episode['pk_health_issue'] = None
     901                  if save_to_backend:
     902                          episode.save_payload()
     903                  return True
     904
     905          # try closing possibly expired episode on target issue if any
     906          db_cfg = gmCfg.cCfgSQL()
     907          epi_ttl = int(db_cfg.get2 (
     908                  option = u'episode.ttl',
     909                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     910                  bias = 'user',
     911                  default = 60                            # 2 months
     912          ))
     913          if target_issue.close_expired_episode(ttl=epi_ttl) is True:
     914                  gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
     915          existing_epi = target_issue.get_open_episode()
     916
     917          # no more open episode on target issue: should work now
     918          if existing_epi is None:
     919                  episode['pk_health_issue'] = target_issue['pk_health_issue']
     920                  if save_to_backend:
     921                          episode.save_payload()
     922                  return True
     923
     924          # don't conflict on SELF ;-)
     925          if existing_epi['pk_episode'] == episode['pk_episode']:
     926                  episode['pk_health_issue'] = target_issue['pk_health_issue']
     927                  if save_to_backend:
     928                          episode.save_payload()
     929                  return True
     930
     931          # we got two open episodes at once, ask user
     932          move_range = episode.get_access_range()
     933          exist_range = existing_epi.get_access_range()
     934          question = _(
     935                  'You want to associate the running episode:\n\n'
     936                  ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
     937                  'with the health issue:\n\n'
     938                  ' "%(issue_name)s"\n\n'
     939                  'There already is another episode running\n'
     940                  'for this health issue:\n\n'
     941                  ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
     942                  'However, there can only be one running\n'
     943                  'episode per health issue.\n\n'
     944                  'Which episode do you want to close ?'
     945          ) % {
     946                  'new_epi_name': episode['description'],
     947                  'new_epi_start': move_range[0].strftime('%m/%y'),
     948                  'new_epi_end': move_range[1].strftime('%m/%y'),
     949                  'issue_name': target_issue['description'],
     950                  'old_epi_name': existing_epi['description'],
     951                  'old_epi_start': exist_range[0].strftime('%m/%y'),
     952                  'old_epi_end': exist_range[1].strftime('%m/%y')
     953          }
     954          dlg = gmGuiHelpers.c3ButtonQuestionDlg (
     955                  parent = None,
     956                  id = -1,
     957                  caption = _('Resolving two-running-episodes conflict'),
     958                  question = question,
     959                  button_defs = [
     960                          {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
     961                          {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
     962                  ]
     963          )
     964          decision = dlg.ShowModal()
     965
     966          if decision == wx.ID_CANCEL:
     967                  # button 3: move cancelled by user
     968                  return False
     969
     970          elif decision == wx.ID_YES:
     971                  # button 1: close old episode
     972                  existing_epi['episode_open'] = False
     973                  existing_epi.save_payload()
     974
     975          elif decision == wx.ID_NO:
     976                  # button 2: close new episode
     977                  episode['episode_open'] = False
     978
     979          else:
     980                  raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
     981
     982          episode['pk_health_issue'] = target_issue['pk_health_issue']
     983          if save_to_backend:
     984                  episode.save_payload()
     985          return True
     986  #----------------------------------------------------------------
     987 -class cEpisodeListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
     988
     989          # FIXME: support pre-selection
     990
     991 -        def __init__(self, *args, **kwargs):
     992
     993                  episodes = kwargs['episodes']
     994                  del kwargs['episodes']
     995
     996                  gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
     997
     998                  self.SetTitle(_('Select the episodes you are interested in ...'))
     999                  self._LCTRL_items.set_columns([_('Episode'), _('Status'), _('Health Issue')])
    1000                  self._LCTRL_items.set_string_items (
    1001                          items = [
    1002                                  [       epi['description'],
    1003                                          gmTools.bool2str(epi['episode_open'], _('ongoing'), u''),
    1004                                          gmTools.coalesce(epi['health_issue'], u'')
    1005                                  ]
    1006                                  for epi in episodes ]
    1007                  )
    1008                  self._LCTRL_items.set_column_widths()
    1009                  self._LCTRL_items.set_data(data = episodes)
    1010  #----------------------------------------------------------------
    1011 -class cEpisodeDescriptionPhraseWheel(gmPhraseWheel.cPhraseWheel):
    1012          """Let user select an episode *description*.
    1013
    1014          The user can select an episode description from the previously
    1015          used descriptions across all episodes across all patients.
    1016
    1017          Selection is done with a phrasewheel so the user can
    1018          type the episode name and matches will be shown. Typing
    1019          "*" will show the entire list of episodes.
    1020
    1021          If the user types a description not existing yet a
    1022          new episode description will be returned.
    1023          """
    1024 -        def __init__(self, *args, **kwargs):
    1025
    1026                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    1027                          queries = [
    1028  u"""
    1029  SELECT DISTINCT ON (description)
    1030          description
    1031                  AS data,
    1032          description
    1033                  AS field_label,
    1034          description || ' ('
    1035          || CASE
    1036                  WHEN is_open IS TRUE THEN _('ongoing')
    1037                  ELSE _('closed')
    1038             END
    1039          || ')'
    1040                  AS list_label
    1041  FROM
    1042          clin.episode
    1043  WHERE
    1044          description %(fragment_condition)s
    1045  ORDER BY description
    1046  LIMIT 30
    1047  """
    1048                          ]
    1049                  )
    1050                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    1051                  self.matcher = mp
    1052  #----------------------------------------------------------------
    1053 -class cEpisodeSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
    1054          """Let user select an episode.
    1055
    1056          The user can select an episode from the existing episodes of a
    1057          patient. Selection is done with a phrasewheel so the user
    1058          can type the episode name and matches will be shown. Typing
    1059          "*" will show the entire list of episodes. Closed episodes
    1060          will be marked as such. If the user types an episode name not
    1061          in the list of existing episodes a new episode can be created
    1062          from it if the programmer activated that feature.
    1063
    1064          If keyword <patient_id> is set to None or left out the control
    1065          will listen to patient change signals and therefore act on
    1066          gmPerson.gmCurrentPatient() changes.
    1067          """
    1068 -        def __init__(self, *args, **kwargs):
    1069
    1070                  ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
    1071
    1072                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    1073                          queries = [
    1074  u"""(
    1075
    1076  SELECT
    1077          pk_episode
    1078                  as data,
    1079          description
    1080                  as field_label,
    1081          coalesce (
    1082                  description || ' - ' || health_issue,
    1083                  description
    1084          ) as list_label,
    1085          1 as rank
    1086  from
    1087          clin.v_pat_episodes
    1088  where
    1089          episode_open is true and
    1090          description %(fragment_condition)s
    1091          %(ctxt_pat)s
    1092
    1093  ) union all (
    1094
    1095  SELECT
    1096          pk_episode
    1097                  as data,
    1098          description
    1099                  as field_label,
    1100          coalesce (
    1101                  description || _(' (closed)') || ' - ' || health_issue,
    1102                  description || _(' (closed)')
    1103          ) as list_label,
    1104          2 as rank
    1105  from
    1106          clin.v_pat_episodes
    1107  where
    1108          description %(fragment_condition)s and
    1109          episode_open is false
    1110          %(ctxt_pat)s
    1111
    1112  )
    1113
    1114  order by rank, list_label
    1115  limit 30"""
    1116  ],
    1117                          context = ctxt
    1118                  )
    1119
    1120                  try:
    1121                          kwargs['patient_id']
    1122                  except KeyError:
    1123                          kwargs['patient_id'] = None
    1124
    1125                  if kwargs['patient_id'] is None:
    1126                          self.use_current_patient = True
    1127                          self.__register_patient_change_signals()
    1128                          pat = gmPerson.gmCurrentPatient()
    1129                          if pat.connected:
    1130                                  mp.set_context('pat', pat.ID)
    1131                  else:
    1132                          self.use_current_patient = False
    1133                          self.__patient_id = int(kwargs['patient_id'])
    1134                          mp.set_context('pat', self.__patient_id)
    1135
    1136                  del kwargs['patient_id']
    1137
    1138                  gmPhraseWheel.cPhraseWheel.__init__ (
    1139                          self,
    1140                          *args,
    1141                          **kwargs
    1142                  )
    1143                  self.matcher = mp
    1144          #--------------------------------------------------------
    1145          # external API
    1146          #--------------------------------------------------------
    1147 -        def set_patient(self, patient_id=None):
    1148                  if self.use_current_patient:
    1149                          return False
    1150                  self.__patient_id = int(patient_id)
    1151                  self.set_context('pat', self.__patient_id)
    1152                  return True
    1153          #--------------------------------------------------------
    1154 -        def GetData(self, can_create=False, as_instance=False, is_open=False):
    1155                  self.__is_open_for_create_data = is_open                # used (only) in _create_data()
    1156                  return gmPhraseWheel.cPhraseWheel.GetData(self, can_create = can_create, as_instance = as_instance)
    1157          #--------------------------------------------------------
    1158 -        def _create_data(self):
    1159
    1160                  epi_name = self.GetValue().strip()
    1161                  if epi_name == u'':
    1162                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
    1163                          _log.debug('cannot create episode without name')
    1164                          return
    1165
    1166                  if self.use_current_patient:
    1167                          pat = gmPerson.gmCurrentPatient()
    1168                  else:
    1169                          pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
    1170
    1171                  emr = pat.get_emr()
    1172                  epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
    1173                  if epi is None:
    1174                          self.data = {}
    1175                  else:
    1176                          self.SetText (
    1177                                  value = epi_name,
    1178                                  data = epi['pk_episode']
    1179                          )
    1180          #--------------------------------------------------------
    1181 -        def _data2instance(self):
    1182                  return gmEMRStructItems.cEpisode(aPK_obj = self.GetData())
    1183          #--------------------------------------------------------
    1184          # internal API
    1185          #--------------------------------------------------------
    1186 -        def __register_patient_change_signals(self):
    1187                  gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
    1188                  gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
    1189          #--------------------------------------------------------
    1190 -        def _pre_patient_selection(self):
    1191                  return True
    1192          #--------------------------------------------------------
    1193 -        def _post_patient_selection(self):
    1194                  if self.use_current_patient:
    1195                          patient = gmPerson.gmCurrentPatient()
    1196                          self.set_context('pat', patient.ID)
    1197                  return True
    1198  #----------------------------------------------------------------
    1199  from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
    1200
    1201 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
    1202
    1203 -        def __init__(self, *args, **kwargs):
    1204
    1205                  try:
    1206                          episode = kwargs['episode']
    1207                          del kwargs['episode']
    1208                  except KeyError:
    1209                          episode = None
    1210
    1211                  wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl.__init__(self, *args, **kwargs)
    1212                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    1213
    1214                  self.data = episode
    1215          #----------------------------------------------------------------
    1216          # generic Edit Area mixin API
    1217          #----------------------------------------------------------------
    1218 -        def _valid_for_save(self):
    1219
    1220                  errors = False
    1221
    1222                  if len(self._PRW_description.GetValue().strip()) == 0:
    1223                          errors = True
    1224                          self._PRW_description.display_as_valid(False)
    1225                          self._PRW_description.SetFocus()
    1226                  else:
    1227                          self._PRW_description.display_as_valid(True)
    1228                  self._PRW_description.Refresh()
    1229
    1230                  return not errors
    1231          #----------------------------------------------------------------
    1232 -        def _save_as_new(self):
    1233
    1234                  pat = gmPerson.gmCurrentPatient()
    1235                  emr = pat.get_emr()
    1236
    1237                  epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
    1238                  epi['summary'] = self._TCTRL_status.GetValue().strip()
    1239                  epi['episode_open'] = not self._CHBOX_closed.IsChecked()
    1240                  epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
    1241
    1242                  issue_name = self._PRW_issue.GetValue().strip()
    1243                  if len(issue_name) != 0:
    1244                          epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
    1245                          issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
    1246
    1247                          if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
    1248                                  gmDispatcher.send (
    1249                                          signal = 'statustext',
    1250                                          msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
    1251                                                  epi['description'],
    1252                                                  issue['description']
    1253                                          )
    1254                                  )
    1255                                  gmEMRStructItems.delete_episode(episode = epi)
    1256                                  return False
    1257
    1258                  epi.save()
    1259
    1260                  epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    1261
    1262                  self.data = epi
    1263                  return True
    1264          #----------------------------------------------------------------
    1265 -        def _save_as_update(self):
    1266
    1267                  self.data['description'] = self._PRW_description.GetValue().strip()
    1268                  self.data['summary'] = self._TCTRL_status.GetValue().strip()
    1269                  self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
    1270                  self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
    1271
    1272                  issue_name = self._PRW_issue.GetValue().strip()
    1273                  if len(issue_name) == 0:
    1274                          self.data['pk_health_issue'] = None
    1275                  else:
    1276                          self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
    1277                          issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
    1278
    1279                          if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
    1280                                  gmDispatcher.send (
    1281                                          signal = 'statustext',
    1282                                          msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
    1283                                                  self.data['description'],
    1284                                                  issue['description']
    1285                                          )
    1286                                  )
    1287                                  return False
    1288
    1289                  self.data.save()
    1290                  self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    1291
    1292                  return True
    1293          #----------------------------------------------------------------
    1294 -        def _refresh_as_new(self):
    1295                  if self.data is None:
    1296                          ident = gmPerson.gmCurrentPatient()
    1297                  else:
    1298                          ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
    1299                  self._TCTRL_patient.SetValue(ident.get_description_gender())
    1300                  self._PRW_issue.SetText()
    1301                  self._PRW_description.SetText()
    1302                  self._TCTRL_status.SetValue(u'')
    1303                  self._PRW_certainty.SetText()
    1304                  self._CHBOX_closed.SetValue(False)
    1305                  self._PRW_codes.SetText()
    1306          #----------------------------------------------------------------
    1307 -        def _refresh_from_existing(self):
    1308                  ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
    1309                  self._TCTRL_patient.SetValue(ident.get_description_gender())
    1310
    1311                  if self.data['pk_health_issue'] is not None:
    1312                          self._PRW_issue.SetText(self.data['health_issue'], data=self.data['pk_health_issue'])
    1313
    1314                  self._PRW_description.SetText(self.data['description'], data=self.data['description'])
    1315
    1316                  self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], u''))
    1317
    1318                  if self.data['diagnostic_certainty_classification'] is not None:
    1319                          self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
    1320
    1321                  self._CHBOX_closed.SetValue(not self.data['episode_open'])
    1322
    1323                  val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
    1324                  self._PRW_codes.SetText(val, data)
    1325          #----------------------------------------------------------------
    1326 -        def _refresh_as_new_from_existing(self):
    1327                  self._refresh_as_new()
    1328  #================================================================
    1329  # health issue related widgets/functions
    1330  #----------------------------------------------------------------
    1331 -def edit_health_issue(parent=None, issue=None):
    1332          ea = cHealthIssueEditAreaPnl(parent = parent, id = -1)
    1333          ea.data = issue
    1334          ea.mode = gmTools.coalesce(issue, 'new', 'edit')
    1335          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (issue is not None))
    1336          dlg.SetTitle(gmTools.coalesce(issue, _('Adding a new health issue'), _('Editing a health issue')))
    1337          if dlg.ShowModal() == wx.ID_OK:
    1338                  dlg.Destroy()
    1339                  return True
    1340          dlg.Destroy()
    1341          return False
    1342  #----------------------------------------------------------------
    1343 -def select_health_issues(parent=None, emr=None):
    1344
    1345          if parent is None:
    1346                  parent = wx.GetApp().GetTopWindow()
    1347          #-----------------------------------------
    1348          def refresh(lctrl):
    1349                  issues = emr.get_health_issues()
    1350                  items = [
    1351                          [
    1352                                  gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), u'', u''),
    1353                                  i['description'],
    1354                                  gmTools.bool2subst(i['clinically_relevant'], _('relevant'), u'', u''),
    1355                                  gmTools.bool2subst(i['is_active'], _('active'), u'', u''),
    1356                                  gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), u'', u'')
    1357                          ] for i in issues
    1358                  ]
    1359                  lctrl.set_string_items(items = items)
    1360                  lctrl.set_data(data = issues)
    1361          #-----------------------------------------
    1362          return gmListWidgets.get_choices_from_list (
    1363                  parent = parent,
    1364                  msg = _('\nSelect the health issues !\n'),
    1365                  caption = _('Showing health issues ...'),
    1366                  columns = [u'', _('Health issue'), u'', u'', u''],
    1367                  single_selection = False,
    1368                  #edit_callback = edit,
    1369                  #new_callback = edit,
    1370                  #delete_callback = delete,
    1371                  refresh_callback = refresh
    1372          )
    1373  #----------------------------------------------------------------
    1374 -class cIssueListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
    1375
    1376          # FIXME: support pre-selection
    1377
    1378 -        def __init__(self, *args, **kwargs):
    1379
    1380                  issues = kwargs['issues']
    1381                  del kwargs['issues']
    1382
    1383                  gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
    1384
    1385                  self.SetTitle(_('Select the health issues you are interested in ...'))
    1386                  self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
    1387
    1388                  for issue in issues:
    1389                          if issue['is_confidential']:
    1390                                  row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
    1391                                  self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
    1392                          else:
    1393                                  row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
    1394
    1395                          self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
    1396                          if issue['clinically_relevant']:
    1397                                  self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
    1398                          if issue['is_active']:
    1399                                  self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
    1400                          if issue['is_cause_of_death']:
    1401                                  self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
    1402
    1403                  self._LCTRL_items.set_column_widths()
    1404                  self._LCTRL_items.set_data(data = issues)
    1405  #----------------------------------------------------------------
    1406 -class cIssueSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
    1407          """Let the user select a health issue.
    1408
    1409          The user can select a health issue from the existing issues
    1410          of a patient. Selection is done with a phrasewheel so the user
    1411          can type the issue name and matches will be shown. Typing
    1412          "*" will show the entire list of issues. Inactive issues
    1413          will be marked as such. If the user types an issue name not
    1414          in the list of existing issues a new issue can be created
    1415          from it if the programmer activated that feature.
    1416
    1417          If keyword <patient_id> is set to None or left out the control
    1418          will listen to patient change signals and therefore act on
    1419          gmPerson.gmCurrentPatient() changes.
    1420          """
    1421 -        def __init__(self, *args, **kwargs):
    1422
    1423                  ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
    1424
    1425                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    1426                          # FIXME: consider clin.health_issue.clinically_relevant
    1427                          queries = [
    1428  u"""
    1429  SELECT
    1430          data,
    1431          field_label,
    1432          list_label
    1433  FROM ((
    1434          SELECT
    1435                  pk_health_issue AS data,
    1436                  description AS field_label,
    1437                  description AS list_label
    1438          FROM clin.v_health_issues
    1439          WHERE
    1440                  is_active IS true
    1441                          AND
    1442                  description %(fragment_condition)s
    1443                          AND
    1444                  %(ctxt_pat)s
    1445
    1446          ) UNION (
    1447
    1448          SELECT
    1449                  pk_health_issue AS data,
    1450                  description AS field_label,
    1451                  description || _(' (inactive)') AS list_label
    1452          FROM clin.v_health_issues
    1453          WHERE
    1454                  is_active IS false
    1455                          AND
    1456                  description %(fragment_condition)s
    1457                          AND
    1458                  %(ctxt_pat)s
    1459  )) AS union_query
    1460  ORDER BY
    1461          list_label"""],
    1462                          context = ctxt
    1463                  )
    1464
    1465                  try: kwargs['patient_id']
    1466                  except KeyError: kwargs['patient_id'] = None
    1467
    1468                  if kwargs['patient_id'] is None:
    1469                          self.use_current_patient = True
    1470                          self.__register_patient_change_signals()
    1471                          pat = gmPerson.gmCurrentPatient()
    1472                          if pat.connected:
    1473                                  mp.set_context('pat', pat.ID)
    1474                  else:
    1475                          self.use_current_patient = False
    1476                          self.__patient_id = int(kwargs['patient_id'])
    1477                          mp.set_context('pat', self.__patient_id)
    1478
    1479                  del kwargs['patient_id']
    1480
    1481                  gmPhraseWheel.cPhraseWheel.__init__ (
    1482                          self,
    1483                          *args,
    1484                          **kwargs
    1485                  )
    1486                  self.matcher = mp
    1487          #--------------------------------------------------------
    1488          # external API
    1489          #--------------------------------------------------------
    1490 -        def set_patient(self, patient_id=None):
    1491                  if self.use_current_patient:
    1492                          return False
    1493                  self.__patient_id = int(patient_id)
    1494                  self.set_context('pat', self.__patient_id)
    1495                  return True
    1496          #--------------------------------------------------------
    1497 -        def _create_data(self):
    1498                  issue_name = self.GetValue().strip()
    1499                  if issue_name == u'':
    1500                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True)
    1501                          _log.debug('cannot create health issue without name')
    1502                          return
    1503
    1504                  if self.use_current_patient:
    1505                          pat = gmPerson.gmCurrentPatient()
    1506                  else:
    1507                          pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
    1508
    1509                  emr = pat.get_emr()
    1510                  issue = emr.add_health_issue(issue_name = issue_name)
    1511
    1512                  if issue is None:
    1513                          self.data = {}
    1514                  else:
    1515                          self.SetText (
    1516                                  value = issue_name,
    1517                                  data = issue['pk_health_issue']
    1518                          )
    1519          #--------------------------------------------------------
    1520 -        def _data2instance(self):
    1521                  return gmEMRStructItems.cHealthIssue(aPK_obj = self.GetData())
    1522          #--------------------------------------------------------
    1523          # internal API
    1524          #--------------------------------------------------------
    1525 -        def __register_patient_change_signals(self):
    1526                  gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
    1527                  gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
    1528          #--------------------------------------------------------
    1529 -        def _pre_patient_selection(self):
    1530                  return True
    1531          #--------------------------------------------------------
    1532 -        def _post_patient_selection(self):
    1533                  if self.use_current_patient:
    1534                          patient = gmPerson.gmCurrentPatient()
    1535                          self.set_context('pat', patient.ID)
    1536                  return True
    1537  #------------------------------------------------------------
    1538  from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
    1539
    1540 -class cIssueSelectionDlg(wxgIssueSelectionDlg.wxgIssueSelectionDlg):
    1541
    1542 -        def __init__(self, *args, **kwargs):
    1543                  try:
    1544                          msg = kwargs['message']
    1545                  except KeyError:
    1546                          msg = None
    1547                  del kwargs['message']
    1548                  wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
    1549                  if msg is not None:
    1550                          self._lbl_message.SetLabel(label=msg)
    1551          #--------------------------------------------------------
    1552 -        def _on_OK_button_pressed(self, event):
    1553                  event.Skip()
    1554                  pk_issue = self._PhWheel_issue.GetData(can_create=True)
    1555                  if pk_issue is None:
    1556                          gmGuiHelpers.gm_show_error (
    1557                                  _('Cannot create new health issue:\n [%(issue)s]') % {'issue': self._PhWheel_issue.GetValue().strip()},
    1558                                  _('Selecting health issue')
    1559                          )
    1560                          return False
    1561                  return True
    1562  #------------------------------------------------------------
    1563  from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
    1564
    1565 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
    1566          """Panel encapsulating health issue edit area functionality."""
    1567
    1568 -        def __init__(self, *args, **kwargs):
    1569
    1570                  try:
    1571                          data = kwargs['issue']
    1572                          del kwargs['issue']
    1573                  except KeyError:
    1574                          data = None
    1575
    1576                  wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
    1577                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    1578
    1579                  self.mode = 'new'
    1580                  self.data = data
    1581                  if data is not None:
    1582                          self.mode = 'edit'
    1583
    1584                  self.__init_ui()
    1585          #----------------------------------------------------------------
    1586 -        def __init_ui(self):
    1587
    1588                  # FIXME: include more sources: coding systems/other database columns
    1589                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    1590                          queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
    1591                  )
    1592                  mp.setThresholds(1, 3, 5)
    1593                  self._PRW_condition.matcher = mp
    1594
    1595                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    1596                          queries = [u"""
    1597  SELECT DISTINCT ON (grouping) grouping, grouping from (
    1598
    1599          SELECT rank, grouping from ((
    1600
    1601                  SELECT
    1602                          grouping,
    1603                          1 as rank
    1604                  from
    1605                          clin.health_issue
    1606                  where
    1607                          grouping %%(fragment_condition)s
    1608                                  and
    1609                          (SELECT True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
    1610
    1611          ) union (
    1612
    1613                  SELECT
    1614                          grouping,
    1615                          2 as rank
    1616                  from
    1617                          clin.health_issue
    1618                  where
    1619                          grouping %%(fragment_condition)s
    1620
    1621          )) as union_result
    1622
    1623          order by rank
    1624
    1625  ) as order_result
    1626
    1627  limit 50""" % gmPerson.gmCurrentPatient().ID
    1628                          ]
    1629                  )
    1630                  mp.setThresholds(1, 3, 5)
    1631                  self._PRW_grouping.matcher = mp
    1632
    1633                  self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
    1634                  self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
    1635
    1636  #               self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
    1637  #               self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
    1638
    1639                  self._PRW_year_noted.Enable(True)
    1640
    1641                  self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
    1642
    1643          #----------------------------------------------------------------
    1644          # generic Edit Area mixin API
    1645          #----------------------------------------------------------------
    1646 -        def _valid_for_save(self):
    1647
    1648                  if self._PRW_condition.GetValue().strip() == '':
    1649                          self._PRW_condition.display_as_valid(False)
    1650                          self._PRW_condition.SetFocus()
    1651                          return False
    1652                  self._PRW_condition.display_as_valid(True)
    1653                  self._PRW_condition.Refresh()
    1654
    1655                  # FIXME: sanity check age/year diagnosed
    1656                  age_noted = self._PRW_age_noted.GetValue().strip()
    1657                  if age_noted != u'':
    1658                          if gmDateTime.str2interval(str_interval = age_noted) is None:
    1659                                  self._PRW_age_noted.display_as_valid(False)
    1660                                  self._PRW_age_noted.SetFocus()
    1661                                  return False
    1662                  self._PRW_age_noted.display_as_valid(True)
    1663                  return True
    1664          #----------------------------------------------------------------
    1665 -        def _save_as_new(self):
    1666                  pat = gmPerson.gmCurrentPatient()
    1667                  emr = pat.get_emr()
    1668
    1669                  issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
    1670
    1671                  side = u''
    1672                  if self._ChBOX_left.GetValue():
    1673                          side += u's'
    1674                  if self._ChBOX_right.GetValue():
    1675                          side += u'd'
    1676                  issue['laterality'] = side
    1677
    1678                  issue['summary'] = self._TCTRL_status.GetValue().strip()
    1679                  issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
    1680                  issue['grouping'] = self._PRW_grouping.GetValue().strip()
    1681                  issue['is_active'] = self._ChBOX_active.GetValue()
    1682                  issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
    1683                  issue['is_confidential'] = self._ChBOX_confidential.GetValue()
    1684                  issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
    1685
    1686                  age_noted = self._PRW_age_noted.GetData()
    1687                  if age_noted is not None:
    1688                          issue['age_noted'] = age_noted
    1689
    1690                  issue.save()
    1691
    1692                  issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    1693
    1694                  self.data = issue
    1695                  return True
    1696          #----------------------------------------------------------------
    1697 -        def _save_as_update(self):
    1698
    1699                  self.data['description'] = self._PRW_condition.GetValue().strip()
    1700
    1701                  side = u''
    1702                  if self._ChBOX_left.GetValue():
    1703                          side += u's'
    1704                  if self._ChBOX_right.GetValue():
    1705                          side += u'd'
    1706                  self.data['laterality'] = side
    1707
    1708                  self.data['summary'] = self._TCTRL_status.GetValue().strip()
    1709                  self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
    1710                  self.data['grouping'] = self._PRW_grouping.GetValue().strip()
    1711                  self.data['is_active'] = bool(self._ChBOX_active.GetValue())
    1712                  self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
    1713                  self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
    1714                  self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
    1715
    1716                  age_noted = self._PRW_age_noted.GetData()
    1717                  if age_noted is not None:
    1718                          self.data['age_noted'] = age_noted
    1719
    1720                  self.data.save()
    1721                  self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    1722
    1723                  return True
    1724          #----------------------------------------------------------------
    1725 -        def _refresh_as_new(self):
    1726                  self._PRW_condition.SetText()
    1727                  self._ChBOX_left.SetValue(0)
    1728                  self._ChBOX_right.SetValue(0)
    1729                  self._PRW_codes.SetText()
    1730                  self._on_leave_codes()
    1731                  self._PRW_certainty.SetText()
    1732                  self._PRW_grouping.SetText()
    1733                  self._TCTRL_status.SetValue(u'')
    1734                  self._PRW_age_noted.SetText()
    1735                  self._PRW_year_noted.SetText()
    1736                  self._ChBOX_active.SetValue(1)
    1737                  self._ChBOX_relevant.SetValue(1)
    1738                  self._ChBOX_confidential.SetValue(0)
    1739                  self._ChBOX_caused_death.SetValue(0)
    1740
    1741                  self._PRW_condition.SetFocus()
    1742                  return True
    1743          #----------------------------------------------------------------
    1744 -        def _refresh_from_existing(self):
    1745                  self._PRW_condition.SetText(self.data['description'])
    1746
    1747                  lat = gmTools.coalesce(self.data['laterality'], '')
    1748                  if lat.find('s') == -1:
    1749                          self._ChBOX_left.SetValue(0)
    1750                  else:
    1751                          self._ChBOX_left.SetValue(1)
    1752                  if lat.find('d') == -1:
    1753                          self._ChBOX_right.SetValue(0)
    1754                  else:
    1755                          self._ChBOX_right.SetValue(1)
    1756
    1757                  val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
    1758                  self._PRW_codes.SetText(val, data)
    1759                  self._on_leave_codes()
    1760
    1761                  if self.data['diagnostic_certainty_classification'] is not None:
    1762                          self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
    1763                  self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], u''))
    1764                  self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], u''))
    1765
    1766                  if self.data['age_noted'] is None:
    1767                          self._PRW_age_noted.SetText()
    1768                  else:
    1769                          self._PRW_age_noted.SetText (
    1770                                  value = '%sd' % self.data['age_noted'].days,
    1771                                  data = self.data['age_noted']
    1772                          )
    1773
    1774                  self._ChBOX_active.SetValue(self.data['is_active'])
    1775                  self._ChBOX_relevant.SetValue(self.data['clinically_relevant'])
    1776                  self._ChBOX_confidential.SetValue(self.data['is_confidential'])
    1777                  self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death'])
    1778
    1779                  self._TCTRL_status.SetFocus()
    1780
    1781                  return True
    1782          #----------------------------------------------------------------
    1783 -        def _refresh_as_new_from_existing(self):
    1784                  return self._refresh_as_new()
    1785          #--------------------------------------------------------
    1786          # internal helpers
    1787          #--------------------------------------------------------
    1788 -        def _on_leave_codes(self, *args, **kwargs):
    1789                  if not self._PRW_codes.IsModified():
    1790                          return True
    1791
    1792                  self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
    1793          #--------------------------------------------------------
    1794 -        def _on_leave_age_noted(self, *args, **kwargs):
    1795
    1796                  if not self._PRW_age_noted.IsModified():
    1797                          return True
    1798
    1799                  age_str = self._PRW_age_noted.GetValue().strip()
    1800
    1801                  if age_str == u'':
    1802                          return True
    1803
    1804                  issue_age = gmDateTime.str2interval(str_interval = age_str)
    1805
    1806                  if issue_age is None:
    1807                          self.status_message = _('Cannot parse [%s] into valid interval.') % age_str
    1808                          self._PRW_age_noted.display_as_valid(False)
    1809                          return True
    1810
    1811                  pat = gmPerson.gmCurrentPatient()
    1812                  if pat['dob'] is not None:
    1813                          max_issue_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
    1814                          if issue_age >= max_issue_age:
    1815                                  self.status_message = _('Health issue cannot have been noted at age %s. Patient is only %s old.') % (issue_age, pat.get_medical_age())
    1816                                  self._PRW_age_noted.display_as_valid(False)
    1817                                  return True
    1818
    1819                  self._PRW_age_noted.display_as_valid(True)
    1820                  self._PRW_age_noted.SetText(value = age_str, data = issue_age)
    1821
    1822                  if pat['dob'] is not None:
    1823                          fts = gmDateTime.cFuzzyTimestamp (
    1824                                  timestamp = pat['dob'] + issue_age,
    1825                                  accuracy = gmDateTime.acc_months
    1826                          )
    1827                          self._PRW_year_noted.SetText(value = str(fts), data = fts)
    1828
    1829                  return True
    1830          #--------------------------------------------------------
    1831 -        def _on_leave_year_noted(self, *args, **kwargs):
    1832
    1833                  if not self._PRW_year_noted.IsModified():
    1834                          return True
    1835
    1836                  year_noted = self._PRW_year_noted.GetData()
    1837
    1838                  if year_noted is None:
    1839                          if self._PRW_year_noted.GetValue().strip() == u'':
    1840                                  self._PRW_year_noted.display_as_valid(True)
    1841                                  return True
    1842                          self._PRW_year_noted.display_as_valid(False)
    1843                          return True
    1844
    1845                  year_noted = year_noted.get_pydt()
    1846
    1847                  if year_noted >= pydt.datetime.now(tz = year_noted.tzinfo):
    1848                          self.status_message = _('Condition diagnosed in the future.')
    1849                          self._PRW_year_noted.display_as_valid(False)
    1850                          return True
    1851
    1852                  self._PRW_year_noted.display_as_valid(True)
    1853
    1854                  pat = gmPerson.gmCurrentPatient()
    1855                  if pat['dob'] is not None:
    1856                          issue_age = year_noted - pat['dob']
    1857                          age_str = gmDateTime.format_interval_medically(interval = issue_age)
    1858                          self._PRW_age_noted.SetText(age_str, issue_age, True)
    1859
    1860                  return True
    1861          #--------------------------------------------------------
    1862 -        def _on_modified_age_noted(self, *args, **kwargs):
    1863                  wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
    1864                  return True
    1865          #--------------------------------------------------------
    1866 -        def _on_modified_year_noted(self, *args, **kwargs):
    1867                  wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
    1868                  return True
    1869  #================================================================
    1870  # diagnostic certainty related widgets/functions
    1871  #----------------------------------------------------------------
    1872 -class cDiagnosticCertaintyClassificationPhraseWheel(gmPhraseWheel.cPhraseWheel):
    1873
    1874 -        def __init__(self, *args, **kwargs):
    1875
    1876                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    1877
    1878                  self.selection_only = False                     # can be NULL, too
    1879
    1880                  mp = gmMatchProvider.cMatchProvider_FixedList (
    1881                          aSeq = [
    1882                                  {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
    1883                                  {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
    1884                                  {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
    1885                                  {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
    1886                          ]
    1887                  )
    1888                  mp.setThresholds(1, 2, 4)
    1889                  self.matcher = mp
    1890
    1891                  self.SetToolTipString(_(
    1892                          "The diagnostic classification or grading of this assessment.\n"
    1893                          "\n"
    1894                          "This documents how certain one is about this being a true diagnosis."
    1895                  ))
    1896
    1897  #================================================================
    1898  # MAIN
    1899  #----------------------------------------------------------------
    1900  if __name__ == '__main__':
    1901
    1902          from Gnumed.business import gmPersonSearch
    1903          from Gnumed.wxpython import gmPatSearchWidgets
    1904
    1905          #================================================================
    1906 -        class testapp (wx.App):
    1907                          """
    1908                          Test application for testing EMR struct widgets
    1909                          """
    1910                          #--------------------------------------------------------
    1911 -                        def OnInit (self):
    1912                                  """
    1913                                  Create test application UI
    1914                                  """
    1915                                  frame = wx.Frame (
    1916                                                          None,
    1917                                                          -4,
    1918                                                          'Testing EMR struct widgets',
    1919                                                          size=wx.Size(600, 400),
    1920                                                          style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
    1921                                                  )
    1922                                  filemenu= wx.Menu()
    1923                                  filemenu.AppendSeparator()
    1924                                  filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
    1925
    1926                                  # Creating the menubar.
    1927                                  menuBar = wx.MenuBar()
    1928                                  menuBar.Append(filemenu,"&File")
    1929
    1930                                  frame.SetMenuBar(menuBar)
    1931
    1932                                  txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
    1933                                  wx.DefaultPosition, wx.DefaultSize, 0 )
    1934
    1935                                  # event handlers
    1936                                  wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
    1937
    1938                                  # patient EMR
    1939                                  self.__pat = gmPerson.gmCurrentPatient()
    1940
    1941                                  frame.Show(1)
    1942                                  return 1
    1943                          #--------------------------------------------------------
    1944 -                        def OnCloseWindow (self, e):
    1945                                  """
    1946                                  Close test aplication
    1947                                  """
    1948                                  self.ExitMainLoop ()
    1949
    1950          #----------------------------------------------------------------
    1951 -        def test_epsiode_edit_area_pnl():
    1952                  app = wx.PyWidgetTester(size = (200, 300))
    1953                  emr = pat.get_emr()
    1954                  epi = emr.get_episodes()[0]
    1955                  pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
    1956                  app.frame.Show(True)
    1957                  app.MainLoop()
    1958          #----------------------------------------------------------------
    1959 -        def test_episode_edit_area_dialog():
    1960                  app = wx.PyWidgetTester(size = (200, 300))
    1961                  emr = pat.get_emr()
    1962                  epi = emr.get_episodes()[0]
    1963                  edit_episode(parent=app.frame, episode=epi)
    1964          #----------------------------------------------------------------
    1965 -        def test_hospital_stay_prw():
    1966                  app = wx.PyWidgetTester(size = (400, 40))
    1967                  app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
    1968                  app.MainLoop()
    1969          #----------------------------------------------------------------
    1970 -        def test_episode_selection_prw():
    1971                  app = wx.PyWidgetTester(size = (400, 40))
    1972                  app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
    1973  #               app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(350,20), pos=(10,20), patient_id=pat.ID)
    1974                  app.MainLoop()
    1975          #----------------------------------------------------------------
    1976 -        def test_health_issue_edit_area_dlg():
    1977                  app = wx.PyWidgetTester(size = (200, 300))
    1978                  edit_health_issue(parent=app.frame, issue=None)
    1979          #----------------------------------------------------------------
    1980 -        def test_health_issue_edit_area_pnl():
    1981                  app = wx.PyWidgetTester(size = (200, 300))
    1982                  app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
    1983                  app.MainLoop()
    1984          #----------------------------------------------------------------
    1985 -        def test_edit_procedure():
    1986                  app = wx.PyWidgetTester(size = (200, 300))
    1987                  edit_procedure(parent=app.frame)
    1988          #================================================================
    1989
    1990          if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
    1991
    1992                  gmI18N.activate_locale()
    1993                  gmI18N.install_domain()
    1994                  gmDateTime.init()
    1995
    1996                  # obtain patient
    1997                  pat = gmPersonSearch.ask_for_patient()
    1998                  if pat is None:
    1999                          print "No patient. Exiting gracefully..."
    2000                          sys.exit(0)
    2001                  gmPatSearchWidgets.set_active_patient(patient=pat)
    2002
    2003  #       try:
    2004                  # lauch emr dialogs test application
    2005  #               app = testapp(0)
    2006  #               app.MainLoop()
    2007  #       except StandardError:
    2008  #               _log.exception("unhandled exception caught !")
    2009                  # but re-raise them
    2010  #               raise
    2011
    2012                  #test_epsiode_edit_area_pnl()
    2013                  #test_episode_edit_area_dialog()
    2014                  #test_health_issue_edit_area_dlg()
    2015                  #test_episode_selection_prw()
    2016                  #test_hospital_stay_prw()
    2017                  test_edit_procedure()
    2018
    2019  #================================================================
    2020