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
      44
      45  _log = logging.getLogger('gm.ui')
      46  #================================================================
      47  # EMR access helper functions
      48  #----------------------------------------------------------------
      49 -def emr_access_spinner(time2spin=0):
      50          """Spin time in seconds."""
      51          if time2spin == 0:
      52                  return
      53          sleep_time = 0.1
      54          total_rounds = int(time2spin / sleep_time)
      55          if total_rounds < 1:
      56                  return
      57          rounds = 0
      58          while rounds < total_rounds:
      59                  wx.Yield()
      60                  time.sleep(sleep_time)
      61                  rounds += 1
      62  #================================================================
      63  # performed procedure related widgets/functions
      64  #----------------------------------------------------------------
      65 -def manage_performed_procedures(parent=None):
      66
      67          pat = gmPerson.gmCurrentPatient()
      68          emr = pat.get_emr()
      69
      70          if parent is None:
      71                  parent = wx.GetApp().GetTopWindow()
      72          #-----------------------------------------
      73          def edit(procedure=None):
      74                  return edit_procedure(parent = parent, procedure = procedure)
      75          #-----------------------------------------
      76          def delete(procedure=None):
      77                  if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']):
      78                          return True
      79
      80                  gmDispatcher.send (
      81                          signal = u'statustext',
      82                          msg = _('Cannot delete performed procedure.'),
      83                          beep = True
      84                  )
      85                  return False
      86          #-----------------------------------------
      87          def refresh(lctrl):
      88                  procs = emr.get_performed_procedures()
      89
      90                  items = [
      91                          [
      92                                  u'%s%s' % (
      93                                          p['clin_when'].strftime('%Y-%m-%d'),
      94                                          gmTools.bool2subst (
      95                                                  p['is_ongoing'],
      96                                                  _(' (ongoing)'),
      97                                                  gmTools.coalesce (
      98                                                          initial = p['clin_end'],
      99                                                          instead = u'',
     100                                                          template_initial = u' - %s',
     101                                                          function_initial = ('strftime', u'%Y-%m-%d')
     102                                                  )
     103                                          )
     104                                  ),
     105                                  p['clin_where'],
     106                                  p['episode'],
     107                                  p['performed_procedure']
     108                          ] for p in procs
     109                  ]
     110                  lctrl.set_string_items(items = items)
     111                  lctrl.set_data(data = procs)
     112          #-----------------------------------------
     113          gmListWidgets.get_choices_from_list (
     114                  parent = parent,
     115                  msg = _('\nSelect the procedure you want to edit !\n'),
     116                  caption = _('Editing performed procedures ...'),
     117                  columns = [_('When'), _('Where'), _('Episode'), _('Procedure')],
     118                  single_selection = True,
     119                  edit_callback = edit,
     120                  new_callback = edit,
     121                  delete_callback = delete,
     122                  refresh_callback = refresh
     123          )
     124  #----------------------------------------------------------------
     125 -def edit_procedure(parent=None, procedure=None):
     126          ea = cProcedureEAPnl(parent = parent, id = -1)
     127          ea.data = procedure
     128          ea.mode = gmTools.coalesce(procedure, 'new', 'edit')
     129          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
     130          dlg.SetTitle(gmTools.coalesce(procedure, _('Adding a procedure'), _('Editing a procedure')))
     131          if dlg.ShowModal() == wx.ID_OK:
     132                  dlg.Destroy()
     133                  return True
     134          dlg.Destroy()
     135          return False
     136  #----------------------------------------------------------------
     137  from Gnumed.wxGladeWidgets import wxgProcedureEAPnl
     138
     139 -class cProcedureEAPnl(wxgProcedureEAPnl.wxgProcedureEAPnl, gmEditArea.cGenericEditAreaMixin):
     140
     141 -        def __init__(self, *args, **kwargs):
     142                  wxgProcedureEAPnl.wxgProcedureEAPnl.__init__(self, *args, **kwargs)
     143                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     144
     145                  self.mode = 'new'
     146                  self.data = None
     147
     148                  self.__init_ui()
     149          #----------------------------------------------------------------
     150 -        def __init_ui(self):
     151                  self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus)
     152                  self._PRW_hospital_stay.set_context(context = 'pat', val = gmPerson.gmCurrentPatient().ID)
     153                  self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus)
     154                  self._DPRW_date.add_callback_on_lose_focus(callback = self._on_start_lost_focus)
     155                  self._DPRW_end.add_callback_on_lose_focus(callback = self._on_end_lost_focus)
     156
     157                  # location
     158                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     159                          queries = [
     160  u"""
     161  SELECT DISTINCT ON (data) data, location
     162  FROM (
     163          SELECT
     164                  clin_where as data,
     165                  clin_where as location
     166          FROM
     167                  clin.procedure
     168          WHERE
     169                  clin_where %(fragment_condition)s
     170
     171                  UNION ALL
     172
     173          SELECT
     174                  narrative as data,
     175                  narrative as location
     176          FROM
     177                  clin.hospital_stay
     178          WHERE
     179                  narrative %(fragment_condition)s
     180  ) as union_result
     181  ORDER BY data
     182  LIMIT 25"""
     183                          ]
     184                  )
     185                  mp.setThresholds(2, 4, 6)
     186                  self._PRW_location.matcher = mp
     187
     188                  # procedure
     189                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     190                          queries = [
     191  u"""
     192  select distinct on (narrative) narrative, narrative
     193  from clin.procedure
     194  where narrative %(fragment_condition)s
     195  order by narrative
     196  limit 25
     197  """                     ]
     198                  )
     199                  mp.setThresholds(2, 4, 6)
     200                  self._PRW_procedure.matcher = mp
     201          #----------------------------------------------------------------
     202 -        def _on_hospital_stay_lost_focus(self):
     203                  stay = self._PRW_hospital_stay.GetData()
     204                  if stay is None:
     205                          self._PRW_hospital_stay.SetText()
     206                          self._PRW_location.Enable(True)
     207                          self._PRW_episode.Enable(True)
     208                          self._LBL_hospital_details.SetLabel(u'')
     209                  else:
     210                          self._PRW_location.SetText()
     211                          self._PRW_location.Enable(False)
     212                          self._PRW_episode.SetText()
     213                          self._PRW_episode.Enable(False)
     214                          self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = stay).format())
     215          #----------------------------------------------------------------
     216 -        def _on_location_lost_focus(self):
     217                  if self._PRW_location.GetValue().strip() == u'':
     218                          self._PRW_hospital_stay.Enable(True)
     219  #                       self._PRW_episode.Enable(False)
     220                  else:
     221                          self._PRW_hospital_stay.SetText()
     222                          self._PRW_hospital_stay.Enable(False)
     223                          self._PRW_hospital_stay.display_as_valid(True)
     224  #                       self._PRW_episode.Enable(True)
     225          #----------------------------------------------------------------
     226 -        def _on_start_lost_focus(self):
     227                  if not self._DPRW_date.is_valid_timestamp():
     228                          return
     229                  end = self._DPRW_end.GetData()
     230                  if end is None:
     231                          return
     232                  end = end.get_pydt()
     233                  start = self._DPRW_date.GetData().get_pydt()
     234                  if start < end:
     235                          return
     236                  self._DPRW_date.display_as_valid(False)
     237          #----------------------------------------------------------------
     238 -        def _on_end_lost_focus(self):
     239                  end = self._DPRW_end.GetData()
     240                  if end is None:
     241                          self._CHBOX_ongoing.Enable(True)
     242                          self._DPRW_end.display_as_valid(True)
     243                  else:
     244                          self._CHBOX_ongoing.Enable(False)
     245                          end = end.get_pydt()
     246                          now = gmDateTime.pydt_now_here()
     247                          if end > now:
     248                                  self._CHBOX_ongoing.SetValue(True)
     249                          else:
     250                                  self._CHBOX_ongoing.SetValue(False)
     251                          start = self._DPRW_date.GetData()
     252                          if start is None:
     253                                  self._DPRW_end.display_as_valid(True)
     254                          else:
     255                                  start = start.get_pydt()
     256                                  if end > start:
     257                                          self._DPRW_end.display_as_valid(True)
     258                                  else:
     259                                          self._DPRW_end.display_as_valid(False)
     260          #----------------------------------------------------------------
     261          # generic Edit Area mixin API
     262          #----------------------------------------------------------------
     263 -        def _valid_for_save(self):
     264
     265                  has_errors = False
     266
     267                  if not self._DPRW_date.is_valid_timestamp():
     268                          self._DPRW_date.display_as_valid(False)
     269                          has_errors = True
     270                  else:
     271                          self._DPRW_date.display_as_valid(True)
     272
     273                  start = self._DPRW_date.GetData()
     274                  end = self._DPRW_end.GetData()
     275                  self._DPRW_end.display_as_valid(True)
     276                  if end is not None:
     277                          end = end.get_pydt()
     278                          if start is not None:
     279                                  start = start.get_pydt()
     280                                  if end < start:
     281                                          has_errors = True
     282                                          self._DPRW_end.display_as_valid(False)
     283                          if self._CHBOX_ongoing.IsChecked():
     284                                  now = gmDateTime.pydt_now_here()
     285                                  if end < now:
     286                                          has_errors = True
     287                                          self._DPRW_end.display_as_valid(False)
     288
     289                  if self._PRW_hospital_stay.GetData() is None:
     290                          if self._PRW_episode.GetData() is None:
     291                                  self._PRW_episode.display_as_valid(False)
     292                                  has_errors = True
     293                          else:
     294                                  self._PRW_episode.display_as_valid(True)
     295                  else:
     296                          self._PRW_episode.display_as_valid(True)
     297
     298                  if (self._PRW_procedure.GetValue() is None) or (self._PRW_procedure.GetValue().strip() == u''):
     299                          self._PRW_procedure.display_as_valid(False)
     300                          has_errors = True
     301                  else:
     302                          self._PRW_procedure.display_as_valid(True)
     303
     304                  invalid_location = (
     305                          (self._PRW_hospital_stay.GetData() is None) and (self._PRW_location.GetValue().strip() == u'')
     306                                  or
     307                          (self._PRW_hospital_stay.GetData() is not None) and (self._PRW_location.GetValue().strip() != u'')
     308                  )
     309                  if invalid_location:
     310                          self._PRW_hospital_stay.display_as_valid(False)
     311                          self._PRW_location.display_as_valid(False)
     312                          has_errors = True
     313                  else:
     314                          self._PRW_hospital_stay.display_as_valid(True)
     315                          self._PRW_location.display_as_valid(True)
     316
     317                  gmDispatcher.send(signal = 'statustext', msg = _('Cannot save procedure.'), beep = True)
     318
     319                  return (has_errors is False)
     320          #----------------------------------------------------------------
     321 -        def _save_as_new(self):
     322
     323                  pat = gmPerson.gmCurrentPatient()
     324                  emr = pat.get_emr()
     325
     326                  if self._PRW_hospital_stay.GetData() is None:
     327                          stay = None
     328                          epi = self._PRW_episode.GetData()
     329                          loc = self._PRW_location.GetValue().strip()
     330                  else:
     331                          stay = self._PRW_hospital_stay.GetData()
     332                          epi = gmEMRStructItems.cHospitalStay(aPK_obj = stay)['pk_episode']
     333                          loc = None
     334
     335                  proc = emr.add_performed_procedure (
     336                          episode = epi,
     337                          location = loc,
     338                          hospital_stay = stay,
     339                          procedure = self._PRW_procedure.GetValue().strip()
     340                  )
     341
     342                  proc['clin_when'] = self._DPRW_date.GetData().get_pydt()
     343                  if self._DPRW_end.GetData() is None:
     344                          proc['clin_end'] = None
     345                  else:
     346                          proc['clin_end'] = self._DPRW_end.GetData().get_pydt()
     347                  proc['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
     348                  proc.save()
     349
     350                  proc.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
     351
     352                  self.data = proc
     353
     354                  return True
     355          #----------------------------------------------------------------
     356 -        def _save_as_update(self):
     357                  self.data['clin_when'] = self._DPRW_date.GetData().get_pydt()
     358
     359                  if self._DPRW_end.GetData() is None:
     360                          self.data['clin_end'] = None
     361                  else:
     362                          self.data['clin_end'] = self._DPRW_end.GetData().get_pydt()
     363
     364                  self.data['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
     365
     366                  if self._PRW_hospital_stay.GetData() is None:
     367                          self.data['pk_hospital_stay'] = None
     368                          self.data['clin_where'] = self._PRW_location.GetValue().strip()
     369                          self.data['pk_episode'] = self._PRW_episode.GetData()
     370                  else:
     371                          self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData()
     372                          self.data['clin_where'] = None
     373                          stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData())
     374                          self.data['pk_episode'] = stay['pk_episode']
     375
     376                  self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip()
     377
     378                  self.data.save()
     379                  self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
     380
     381                  return True
     382          #----------------------------------------------------------------
     383 -        def _refresh_as_new(self):
     384                  self._DPRW_date.SetText()
     385                  self._DPRW_end.SetText()
     386                  self._CHBOX_ongoing.SetValue(False)
     387                  self._CHBOX_ongoing.Enable(True)
     388                  self._PRW_hospital_stay.SetText()
     389                  self._PRW_location.SetText()
     390                  self._PRW_episode.SetText()
     391                  self._PRW_procedure.SetText()
     392                  self._PRW_codes.SetText()
     393
     394                  self._PRW_procedure.SetFocus()
     395          #----------------------------------------------------------------
     396 -        def _refresh_from_existing(self):
     397                  self._DPRW_date.SetData(data = self.data['clin_when'])
     398                  if self.data['clin_end'] is None:
     399                          self._DPRW_end.SetText()
     400                          self._CHBOX_ongoing.Enable(True)
     401                          self._CHBOX_ongoing.SetValue(self.data['is_ongoing'])
     402                  else:
     403                          self._DPRW_end.SetData(data = self.data['clin_end'])
     404                          self._CHBOX_ongoing.Enable(False)
     405                          now = gmDateTime.pydt_now_here()
     406                          if self.data['clin_end'] > now:
     407                                  self._CHBOX_ongoing.SetValue(True)
     408                          else:
     409                                  self._CHBOX_ongoing.SetValue(False)
     410                  self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
     411                  self._PRW_procedure.SetText(value = self.data['performed_procedure'], data = self.data['performed_procedure'])
     412
     413                  if self.data['pk_hospital_stay'] is None:
     414                          self._PRW_hospital_stay.SetText()
     415                          self._LBL_hospital_details.SetLabel(u'')
     416                          self._PRW_location.SetText(value = self.data['clin_where'], data = self.data['clin_where'])
     417                  else:
     418                          self._PRW_hospital_stay.SetText(value = self.data['clin_where'], data = self.data['pk_hospital_stay'])
     419                          self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = self.data['pk_hospital_stay']).format())
     420                          self._PRW_location.SetText()
     421
     422                  val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
     423                  self._PRW_codes.SetText(val, data)
     424
     425                  self._PRW_procedure.SetFocus()
     426          #----------------------------------------------------------------
     427 -        def _refresh_as_new_from_existing(self):
     428                  self._refresh_as_new()
     429                  self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
     430                  if self.data['pk_hospital_stay'] is None:
     431                          self._PRW_hospital_stay.SetText()
     432                          self._PRW_location.SetText(value = self.data['clin_where'], data = self.data['clin_where'])
     433                  else:
     434                          self._PRW_hospital_stay.SetText(value = self.data['clin_where'], data = self.data['pk_hospital_stay'])
     435                          self._PRW_location.SetText()
     436
     437                  self._PRW_procedure.SetFocus()
     438          #----------------------------------------------------------------
     439          # event handlers
     440          #----------------------------------------------------------------
     441 -        def _on_add_hospital_stay_button_pressed(self, evt):
     442                  # FIXME: this would benefit from setting the created stay
     443                  edit_hospital_stay(parent = self.GetParent())
     444                  evt.Skip()
     445          #----------------------------------------------------------------
     446 -        def _on_ongoing_checkbox_checked(self, event):
     447                  if self._CHBOX_ongoing.IsChecked():
     448                          end = self._DPRW_end.GetData()
     449                          if end is None:
     450                                  self._DPRW_end.display_as_valid(True)
     451                          else:
     452                                  end = end.get_pydt()
     453                                  now = gmDateTime.pydt_now_here()
     454                                  if end > now:
     455                                          self._DPRW_end.display_as_valid(True)
     456                                  else:
     457                                          self._DPRW_end.display_as_valid(False)
     458                  else:
     459                          self._DPRW_end.is_valid_timestamp()
     460                  event.Skip()
     461  #================================================================
     462  # hospitalizations related widgets/functions
     463  #----------------------------------------------------------------
     464 -def manage_hospital_stays(parent=None):
     465
     466          pat = gmPerson.gmCurrentPatient()
     467          emr = pat.get_emr()
     468
     469          if parent is None:
     470                  parent = wx.GetApp().GetTopWindow()
     471          #-----------------------------------------
     472          def get_tooltip(stay=None):
     473                  if stay is None:
     474                          return None
     475                  return stay.format (
     476                          include_procedures = True,
     477                          include_docs = True
     478                  )
     479          #-----------------------------------------
     480          def edit(stay=None):
     481                  return edit_hospital_stay(parent = parent, hospital_stay = stay)
     482          #-----------------------------------------
     483          def delete(stay=None):
     484                  if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']):
     485                          return True
     486                  gmDispatcher.send (
     487                          signal = u'statustext',
     488                          msg = _('Cannot delete hospitalization.'),
     489                          beep = True
     490                  )
     491                  return False
     492          #-----------------------------------------
     493          def refresh(lctrl):
     494                  stays = emr.get_hospital_stays()
     495                  items = [
     496                          [
     497                                  s['admission'].strftime('%Y-%m-%d'),
     498                                  gmTools.coalesce(s['discharge'], u'', function_initial = ('strftime', '%Y-%m-%d')),
     499                                  s['episode'],
     500                                  gmTools.coalesce(s['hospital'], u'')
     501                          ] for s in stays
     502                  ]
     503                  lctrl.set_string_items(items = items)
     504                  lctrl.set_data(data = stays)
     505          #-----------------------------------------
     506          gmListWidgets.get_choices_from_list (
     507                  parent = parent,
     508                  msg = _("The patient's hospitalizations:\n"),
     509                  caption = _('Editing hospitalizations ...'),
     510                  columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')],
     511                  single_selection = True,
     512                  edit_callback = edit,
     513                  new_callback = edit,
     514                  delete_callback = delete,
     515                  refresh_callback = refresh,
     516                  list_tooltip_callback = get_tooltip
     517          )
     518
     519  #----------------------------------------------------------------
     520 -def edit_hospital_stay(parent=None, hospital_stay=None):
     521          ea = cHospitalStayEditAreaPnl(parent = parent, id = -1)
     522          ea.data = hospital_stay
     523          ea.mode = gmTools.coalesce(hospital_stay, 'new', 'edit')
     524          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
     525          dlg.SetTitle(gmTools.coalesce(hospital_stay, _('Adding a hospitalization'), _('Editing a hospitalization')))
     526          if dlg.ShowModal() == wx.ID_OK:
     527                  dlg.Destroy()
     528                  return True
     529          dlg.Destroy()
     530          return False
     531  #----------------------------------------------------------------
     532 -class cHospitalStayPhraseWheel(gmPhraseWheel.cPhraseWheel):
     533          """Phrasewheel to allow selection of a hospitalization."""
     534 -        def __init__(self, *args, **kwargs):
     535
     536                  gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
     537
     538                  ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}}
     539
     540                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     541                          queries = [
     542  u"""
     543  select
     544          pk_hospital_stay,
     545          descr
     546  from (
     547          select distinct on (pk_hospital_stay)
     548                  pk_hospital_stay,
     549                  descr
     550          from
     551                  (select
     552                          pk_hospital_stay,
     553                          (
     554                                  to_char(admission, 'YYYY-Mon-DD')
     555                                  || coalesce((' (' || hospital || '):'), ': ')
     556                                  || episode
     557                                  || coalesce((' (' || health_issue || ')'), '')
     558                          ) as descr
     559                   from
     560                          clin.v_pat_hospital_stays
     561                   where
     562                          %(ctxt_pat)s
     563
     564                          hospital %(fragment_condition)s
     565                                  or
     566                          episode %(fragment_condition)s
     567                                  or
     568                          health_issue %(fragment_condition)s
     569                  ) as the_stays
     570  ) as distinct_stays
     571  order by descr
     572  limit 25
     573  """                     ],
     574                          context = ctxt
     575                  )
     576                  mp.setThresholds(3, 4, 6)
     577                  mp.set_context('pat', gmPerson.gmCurrentPatient().ID)
     578
     579                  self.matcher = mp
     580                  self.selection_only = True
     581  #----------------------------------------------------------------
     582  from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl
     583
     584 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
     585
     586 -        def __init__(self, *args, **kwargs):
     587                  wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl.__init__(self, *args, **kwargs)
     588                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     589          #----------------------------------------------------------------
     590          # generic Edit Area mixin API
     591          #----------------------------------------------------------------
     592 -        def _valid_for_save(self):
     593
     594                  valid = True
     595
     596                  if self._PRW_episode.GetValue().strip() == u'':
     597                          valid = False
     598                          self._PRW_episode.display_as_valid(False)
     599                          gmDispatcher.send(signal = 'statustext', msg = _('Must select an episode or enter a name for a new one. Cannot save hospitalization.'), beep = True)
     600                          self._PRW_episode.SetFocus()
     601
     602                  if not self._PRW_admission.is_valid_timestamp(allow_empty = False):
     603                          valid = False
     604                          gmDispatcher.send(signal = 'statustext', msg = _('Missing admission data. Cannot save hospitalization.'), beep = True)
     605                          self._PRW_admission.SetFocus()
     606
     607                  if self._PRW_discharge.is_valid_timestamp(allow_empty = True):
     608                          if self._PRW_discharge.date is not None:
     609                                  adm = self._PRW_admission.date
     610                                  discharge = self._PRW_discharge.date
     611                                  # normalize for comparison
     612                                  discharge = discharge.replace (
     613                                          hour = adm.hour,
     614                                          minute = adm.minute,
     615                                          second = adm.second,
     616                                          microsecond = adm.microsecond
     617                                  )
     618                                  if adm is not None:
     619                                          if discharge == adm:
     620                                                  self._PRW_discharge.SetData(discharge + pydt.timedelta(seconds = 1))
     621                                          elif not self._PRW_discharge.date > self._PRW_admission.date:
     622                                                  valid = False
     623                                                  self._PRW_discharge.display_as_valid(False)
     624                                                  gmDispatcher.send(signal = 'statustext', msg = _('Discharge date must be empty or later than admission. Cannot save hospitalization.'), beep = True)
     625                                                  self._PRW_discharge.SetFocus()
     626
     627                  return (valid is True)
     628          #----------------------------------------------------------------
     629 -        def _save_as_new(self):
     630
     631                  pat = gmPerson.gmCurrentPatient()
     632                  emr = pat.get_emr()
     633                  stay = emr.add_hospital_stay(episode = self._PRW_episode.GetData(can_create = True))
     634                  stay['hospital'] = gmTools.none_if(self._PRW_hospital.GetValue().strip(), u'')
     635                  stay['admission'] = self._PRW_admission.GetData()
     636                  stay['discharge'] = self._PRW_discharge.GetData()
     637                  stay.save_payload()
     638
     639                  self.data = stay
     640                  return True
     641          #----------------------------------------------------------------
     642 -        def _save_as_update(self):
     643
     644                  self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True)
     645                  self.data['hospital'] = gmTools.none_if(self._PRW_hospital.GetValue().strip(), u'')
     646                  self.data['admission'] = self._PRW_admission.GetData()
     647                  self.data['discharge'] = self._PRW_discharge.GetData()
     648                  self.data.save_payload()
     649
     650                  return True
     651          #----------------------------------------------------------------
     652 -        def _refresh_as_new(self):
     653                  self._PRW_hospital.SetText(value = u'')
     654                  self._PRW_episode.SetText(value = u'')
     655                  self._PRW_admission.SetText(data = gmDateTime.pydt_now_here())
     656                  self._PRW_discharge.SetText()
     657                  self._PRW_hospital.SetFocus()
     658          #----------------------------------------------------------------
     659 -        def _refresh_from_existing(self):
     660                  if self.data['hospital'] is not None:
     661                          self._PRW_hospital.SetText(value = self.data['hospital'])
     662
     663                  if self.data['pk_episode'] is not None:
     664                          self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
     665
     666                  self._PRW_admission.SetText(data = self.data['admission'])
     667                  self._PRW_discharge.SetText(data = self.data['discharge'])
     668
     669                  self._PRW_hospital.SetFocus()
     670          #----------------------------------------------------------------
     671 -        def _refresh_as_new_from_existing(self):
     672                  print "this was not expected to be used in this edit area"
     673
     674  #================================================================
     675  # encounter related widgets/functions
     676  #----------------------------------------------------------------
     677 -def start_new_encounter(emr=None):
     678          emr.start_new_encounter()
     679          gmDispatcher.send(signal = 'statustext', msg = _('Started a new encounter for the active patient.'), beep = True)
     680          time.sleep(0.5)
     681          gmGuiHelpers.gm_show_info (
     682                  _('\nA new encounter was started for the active patient.\n'),
     683                  _('Start of new encounter')
     684          )
     685  #----------------------------------------------------------------
     686  from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg
     687
     688 -def edit_encounter(parent=None, encounter=None, msg=None):
     689          if parent is None:
     690                  parent = wx.GetApp().GetTopWindow()
     691
     692          # FIXME: use generic dialog 2
     693          dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter, msg = msg)
     694          if dlg.ShowModal() == wx.ID_OK:
     695                  dlg.Destroy()
     696                  return True
     697          dlg.Destroy()
     698          return False
     699  #----------------------------------------------------------------
     700 -def manage_encounters(**kwargs):
     701          return select_encounters(**kwargs)
     702
     703 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False):
     704
     705          if patient is None:
     706                  patient = gmPerson.gmCurrentPatient()
     707
     708          if not patient.connected:
     709                  gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.'))
     710                  return False
     711
     712          if parent is None:
     713                  parent = wx.GetApp().GetTopWindow()
     714
     715          emr = patient.get_emr()
     716
     717          #--------------------
     718          def new():
     719                  cfg_db = gmCfg.cCfgSQL()
     720                  enc_type = cfg_db.get2 (
     721                          option = u'encounter.default_type',
     722                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     723                          bias = u'user'
     724                  )
     725                  if enc_type is None:
     726                          enc_type = gmEMRStructItems.get_most_commonly_used_encounter_type()
     727                  if enc_type is None:
     728                          enc_type = u'in surgery'
     729                  enc = gmEMRStructItems.create_encounter(fk_patient = patient.ID, enc_type = enc_type)
     730                  return edit_encounter(parent = parent, encounter = enc)
     731          #--------------------
     732          def edit(enc=None):
     733                  return edit_encounter(parent = parent, encounter = enc)
     734          #--------------------
     735          def edit_active(enc=None):
     736                  return edit_encounter(parent = parent, encounter = emr.active_encounter)
     737          #--------------------
     738          def start_new(enc=None):
     739                  start_new_encounter(emr = emr)
     740                  return True
     741          #--------------------
     742          def get_tooltip(data):
     743                  if data is None:
     744                          return None
     745                  return data.format (
     746                          patient = patient,
     747                          with_soap = False,
     748                          with_docs = False,
     749                          with_tests = False,
     750                          with_vaccinations = False,
     751                          with_rfe_aoe = True,
     752                          with_family_history = False,
     753                          by_episode=False,
     754                          fancy_header = True,
     755                  )
     756          #--------------------
     757          def refresh(lctrl):
     758                  if encounters is None:
     759                          encs = emr.get_encounters()
     760                  else:
     761                          encs = encounters
     762
     763                  items = [
     764                          [
     765                                  u'%s - %s' % (gmDateTime.pydt_strftime(e['started'], '%Y %b %d  %H:%M'), e['last_affirmed'].strftime('%H:%M')),
     766                                  e['l10n_type'],
     767                                  gmTools.coalesce(e['praxis_branch'], u''),
     768                                  gmTools.coalesce(e['reason_for_encounter'], u''),
     769                                  gmTools.coalesce(e['assessment_of_encounter'], u''),
     770                                  gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
     771                                  e['pk_encounter']
     772                          ] for e in encs
     773                  ]
     774                  lctrl.set_string_items(items = items)
     775                  lctrl.set_data(data = encs)
     776                  active_pk = emr.active_encounter['pk_encounter']
     777                  for idx in range(len(encs)):
     778                          e = encs[idx]
     779                          if e['pk_encounter'] == active_pk:
     780                                  lctrl.SetItemTextColour(idx, col=wx.NamedColour('RED'))
     781          #--------------------
     782          return gmListWidgets.get_choices_from_list (
     783                  parent = parent,
     784                  msg = _("The patient's encounters.\n"),
     785                  caption = _('Encounters ...'),
     786                  columns = [_('When'), _('Type'), _('Where'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
     787                  can_return_empty = False,
     788                  single_selection = single_selection,
     789                  refresh_callback = refresh,
     790                  edit_callback = edit,
     791                  new_callback = new,
     792                  list_tooltip_callback = get_tooltip,
     793                  ignore_OK_button = ignore_OK_button,
     794                  left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
     795                  middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
     796          )
     797
     798  #----------------------------------------------------------------
     799 -def ask_for_encounter_continuation(msg=None, caption=None, encounter=None, parent=None):
     800          """This is used as the callback when the EMR detects that the
     801             patient was here rather recently and wants to ask the
     802             provider whether to continue the recent encounter.
     803          """
     804          if parent is None:
     805                  parent = wx.GetApp().GetTopWindow()
     806
     807          dlg = gmGuiHelpers.c2ButtonQuestionDlg (
     808                  parent = None,
     809                  id = -1,
     810                  caption = caption,
     811                  question = msg,
     812                  button_defs = [
     813                          {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
     814                          {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
     815                  ],
     816                  show_checkbox = False
     817          )
     818
     819          result = dlg.ShowModal()
     820          dlg.Destroy()
     821
     822          if result == wx.ID_YES:
     823                  return True
     824
     825          return False
     826  #----------------------------------------------------------------
     827 -def manage_encounter_types(parent=None):
     828
     829          if parent is None:
     830                  parent = wx.GetApp().GetTopWindow()
     831
     832          #--------------------
     833          def edit(enc_type=None):
     834                  return edit_encounter_type(parent = parent, encounter_type = enc_type)
     835          #--------------------
     836          def delete(enc_type=None):
     837                  if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
     838                          return True
     839                  gmDispatcher.send (
     840                          signal = u'statustext',
     841                          msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
     842                          beep = True
     843                  )
     844                  return False
     845          #--------------------
     846          def refresh(lctrl):
     847                  enc_types = gmEMRStructItems.get_encounter_types()
     848                  lctrl.set_string_items(items = enc_types)
     849          #--------------------
     850          gmListWidgets.get_choices_from_list (
     851                  parent = parent,
     852                  msg = _('\nSelect the encounter type you want to edit !\n'),
     853                  caption = _('Managing encounter types ...'),
     854                  columns = [_('Local name'), _('Encounter type')],
     855                  single_selection = True,
     856                  edit_callback = edit,
     857                  new_callback = edit,
     858                  delete_callback = delete,
     859                  refresh_callback = refresh
     860          )
     861  #----------------------------------------------------------------
     862 -def edit_encounter_type(parent=None, encounter_type=None):
     863          ea = cEncounterTypeEditAreaPnl(parent = parent, id = -1)
     864          ea.data = encounter_type
     865          ea.mode = gmTools.coalesce(encounter_type, 'new', 'edit')
     866          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea)
     867          dlg.SetTitle(gmTools.coalesce(encounter_type, _('Adding new encounter type'), _('Editing local encounter type name')))
     868          if dlg.ShowModal() == wx.ID_OK:
     869                  return True
     870          return False
     871  #----------------------------------------------------------------
     872 -class cEncounterPhraseWheel(gmPhraseWheel.cPhraseWheel):
     873
     874 -        def __init__(self, *args, **kwargs):
     875                  gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
     876
     877                  cmd = u"""
     878                          SELECT DISTINCT ON (list_label)
     879                                  pk_encounter
     880                                          AS data,
     881                                  to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type || ' [#' || pk_encounter || ']'
     882                                          AS list_label,
     883                                  to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
     884                                          AS field_label
     885                          FROM
     886                                  clin.v_pat_encounters
     887                          WHERE
     888                                  (
     889                                          to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
     890                                                  OR
     891                                          l10n_type %(fragment_condition)s
     892                                                  OR
     893                                          type %(fragment_condition)s
     894                                  )       %(ctxt_patient)s
     895                          ORDER BY
     896                                  list_label
     897                          LIMIT
     898                                  30
     899                  """
     900                  context = {'ctxt_patient': {
     901                          'where_part': u'AND pk_patient = %(patient)s',
     902                          'placeholder': u'patient'
     903                  }}
     904
     905                  self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
     906                  self.matcher._SQL_data2match = u"""
     907                          SELECT
     908                                  pk_encounter
     909                                          AS data,
     910                                  to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
     911                                          AS list_label,
     912                                  to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
     913                                          AS field_label
     914                          FROM
     915                                  clin.v_pat_encounters
     916                          WHERE
     917                                  pk_encounter = %(pk)s
     918                  """
     919                  self.matcher.setThresholds(1, 3, 5)
     920                  #self.matcher.print_queries = True
     921                  self.selection_only = True
     922                  # outside code MUST bind this to a patient
     923                  self.set_context(context = 'patient', val = None)
     924          #--------------------------------------------------------
     925 -        def set_from_instance(self, instance):
     926                  val = u'%s: %s' % (
     927                          gmDateTime.pydt_strftime(instance['started'], '%Y %b %d'),
     928                          instance['l10n_type']
     929                  )
     930                  self.SetText(value = val, data = instance['pk_encounter'])
     931          #------------------------------------------------------------
     932 -        def _get_data_tooltip(self):
     933                  if self.GetData() is None:
     934                          return None
     935                  enc = gmEMRStructItems.cEncounter(aPK_obj = self._data.values()[0]['data'])
     936                  return enc.format (
     937                          with_docs = False,
     938                          with_tests = False,
     939                          with_vaccinations = False,
     940                          with_family_history = False
     941                  )
     942  #----------------------------------------------------------------
     943 -class cEncounterTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
     944          """Phrasewheel to allow selection of encounter type.
     945
     946          - user input interpreted as encounter type in English or local language
     947          - data returned is pk of corresponding encounter type or None
     948          """
     949 -        def __init__(self, *args, **kwargs):
     950
     951                  gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
     952
     953                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     954                          queries = [
     955  u"""
     956  SELECT
     957          data,
     958          field_label,
     959          list_label
     960  FROM (
     961          SELECT DISTINCT ON (data) *
     962          FROM (
     963                  SELECT
     964                          pk AS data,
     965                          _(description) AS field_label,
     966                          case
     967                                  when _(description) = description then _(description)
     968                                  else _(description) || ' (' || description || ')'
     969                          end AS list_label
     970                  FROM
     971                          clin.encounter_type
     972                  WHERE
     973                          _(description) %(fragment_condition)s
     974                                  OR
     975                          description %(fragment_condition)s
     976          ) AS q_distinct_pk
     977  ) AS q_ordered
     978  ORDER BY
     979          list_label
     980  """                     ]
     981                  )
     982                  mp.setThresholds(2, 4, 6)
     983
     984                  self.matcher = mp
     985                  self.selection_only = True
     986                  self.picklist_delay = 50
     987  #----------------------------------------------------------------
     988  from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
     989
     990 -class cEncounterTypeEditAreaPnl(wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
     991
     992 -        def __init__(self, *args, **kwargs):
     993
     994                  wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl.__init__(self, *args, **kwargs)
     995                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     996
     997  #               self.__register_interests()
     998          #-------------------------------------------------------
     999          # generic edit area API
    1000          #-------------------------------------------------------
    1001 -        def _valid_for_save(self):
    1002                  if self.mode == 'edit':
    1003                          if self._TCTRL_l10n_name.GetValue().strip() == u'':
    1004                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
    1005                                  return False
    1006                          self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
    1007                          return True
    1008
    1009                  no_errors = True
    1010
    1011                  if self._TCTRL_l10n_name.GetValue().strip() == u'':
    1012                          if self._TCTRL_name.GetValue().strip() == u'':
    1013                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
    1014                                  no_errors = False
    1015                          else:
    1016                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
    1017                  else:
    1018                          self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
    1019
    1020                  if self._TCTRL_name.GetValue().strip() == u'':
    1021                          if self._TCTRL_l10n_name.GetValue().strip() == u'':
    1022                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = False)
    1023                                  no_errors = False
    1024                          else:
    1025                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
    1026                  else:
    1027                          self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
    1028
    1029                  return no_errors
    1030          #-------------------------------------------------------
    1031 -        def _save_as_new(self):
    1032                  enc_type = gmEMRStructItems.create_encounter_type (
    1033                          description = gmTools.none_if(self._TCTRL_name.GetValue().strip(), u''),
    1034                          l10n_description = gmTools.coalesce (
    1035                                  gmTools.none_if(self._TCTRL_l10n_name.GetValue().strip(), u''),
    1036                                  self._TCTRL_name.GetValue().strip()
    1037                          )
    1038                  )
    1039                  if enc_type is None:
    1040                          return False
    1041                  self.data = enc_type
    1042                  return True
    1043          #-------------------------------------------------------
    1044 -        def _save_as_update(self):
    1045                  enc_type = gmEMRStructItems.update_encounter_type (
    1046                          description = self._TCTRL_name.GetValue().strip(),
    1047                          l10n_description = self._TCTRL_l10n_name.GetValue().strip()
    1048                  )
    1049                  if enc_type is None:
    1050                          return False
    1051                  self.data = enc_type
    1052                  return True
    1053          #-------------------------------------------------------
    1054 -        def _refresh_as_new(self):
    1055                  self._TCTRL_l10n_name.SetValue(u'')
    1056                  self._TCTRL_name.SetValue(u'')
    1057                  self._TCTRL_name.Enable(True)
    1058          #-------------------------------------------------------
    1059 -        def _refresh_from_existing(self):
    1060                  self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
    1061                  self._TCTRL_name.SetValue(self.data['description'])
    1062                  # disallow changing type on all encounters by editing system name
    1063                  self._TCTRL_name.Enable(False)
    1064          #-------------------------------------------------------
    1065 -        def _refresh_as_new_from_existing(self):
    1066                  self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
    1067                  self._TCTRL_name.SetValue(self.data['description'])
    1068                  self._TCTRL_name.Enable(True)
    1069          #-------------------------------------------------------
    1070          # internal API
    1071          #-------------------------------------------------------
    1072  #       def __register_interests(self):
    1073  #               return
    1074  #----------------------------------------------------------------
    1075  from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
    1076
    1077 -class cEncounterEditAreaPnl(wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl):
    1078
    1079 -        def __init__(self, *args, **kwargs):
    1080                  try:
    1081                          self.__encounter = kwargs['encounter']
    1082                          del kwargs['encounter']
    1083                  except KeyError:
    1084                          self.__encounter = None
    1085
    1086                  try:
    1087                          msg = kwargs['msg']
    1088                          del kwargs['msg']
    1089                  except KeyError:
    1090                          msg = None
    1091
    1092                  wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
    1093
    1094                  self.refresh(msg = msg)
    1095          #--------------------------------------------------------
    1096          # external API
    1097          #--------------------------------------------------------
    1098 -        def refresh(self, encounter=None, msg=None):
    1099
    1100                  if msg is not None:
    1101                          self._LBL_instructions.SetLabel(msg)
    1102
    1103                  if encounter is not None:
    1104                          self.__encounter = encounter
    1105
    1106                  if self.__encounter is None:
    1107                          return True
    1108
    1109                  # getting the patient via the encounter allows us to act
    1110                  # on any encounter regardless of the currently active patient
    1111                  pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
    1112                  self._LBL_patient.SetLabel(pat.get_description_gender().strip())
    1113                  curr_pat = gmPerson.gmCurrentPatient()
    1114                  if curr_pat.connected:
    1115                          if curr_pat.ID == self.__encounter['pk_patient']:
    1116                                  self._LBL_patient.SetForegroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT))
    1117                          else:
    1118                                  self._LBL_patient.SetForegroundColour('red')
    1119
    1120                  self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data = self.__encounter['pk_type'])
    1121                  self._PRW_location.Enable(True)
    1122                  self._PRW_location.display_as_disabled(False)
    1123                  branch = self.__encounter.praxis_branch
    1124                  if branch is None:              # None or old entry because praxis has been re-configured
    1125                          unit = self.__encounter.org_unit
    1126                          if unit is None:                                                        # None
    1127                                  self._PRW_location.SetText(u'', data = None)
    1128                          else:                                                                           # old entry
    1129                                  self._PRW_location.Enable(False)
    1130                                  self._PRW_location.display_as_disabled(True)
    1131                                  self._PRW_location.SetText(_('old praxis branch: %s (%s)') % (unit['unit'], unit['organization']), data = None)
    1132                  else:
    1133                          self._PRW_location.SetText(self.__encounter['praxis_branch'], data = branch['pk_praxis_branch'])
    1134
    1135                  fts = gmDateTime.cFuzzyTimestamp (
    1136                          timestamp = self.__encounter['started'],
    1137                          accuracy = gmDateTime.acc_minutes
    1138                  )
    1139                  self._PRW_start.SetText(fts.format_accurately(), data=fts)
    1140
    1141                  fts = gmDateTime.cFuzzyTimestamp (
    1142                          timestamp = self.__encounter['last_affirmed'],
    1143                          accuracy = gmDateTime.acc_minutes
    1144                  )
    1145                  self._PRW_end.SetText(fts.format_accurately(), data=fts)
    1146
    1147                  # RFE
    1148                  self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
    1149                  val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
    1150                  self._PRW_rfe_codes.SetText(val, data)
    1151
    1152                  # AOE
    1153                  self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
    1154                  val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
    1155                  self._PRW_aoe_codes.SetText(val, data)
    1156
    1157                  # last affirmed
    1158                  if self.__encounter['last_affirmed'] == self.__encounter['started']:
    1159                          self._PRW_end.SetFocus()
    1160                  else:
    1161                          self._TCTRL_aoe.SetFocus()
    1162
    1163                  return True
    1164          #--------------------------------------------------------
    1165 -        def __is_valid_for_save(self):
    1166
    1167                  if self._PRW_encounter_type.GetData() is None:
    1168                          self._PRW_encounter_type.SetBackgroundColour('pink')
    1169                          self._PRW_encounter_type.Refresh()
    1170                          self._PRW_encounter_type.SetFocus()
    1171                          return False
    1172                  self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
    1173                  self._PRW_encounter_type.Refresh()
    1174
    1175                  # start
    1176                  if self._PRW_start.GetValue().strip() == u'':
    1177                          self._PRW_start.SetBackgroundColour('pink')
    1178                          self._PRW_start.Refresh()
    1179                          self._PRW_start.SetFocus()
    1180                          return False
    1181                  if not self._PRW_start.is_valid_timestamp(empty_is_valid = False):
    1182                          self._PRW_start.SetBackgroundColour('pink')
    1183                          self._PRW_start.Refresh()
    1184                          self._PRW_start.SetFocus()
    1185                          return False
    1186                  self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
    1187                  self._PRW_start.Refresh()
    1188
    1189                  # last_affirmed
    1190  #               if self._PRW_end.GetValue().strip() == u'':
    1191  #                       self._PRW_end.SetBackgroundColour('pink')
    1192  #                       self._PRW_end.Refresh()
    1193  #                       self._PRW_end.SetFocus()
    1194  #                       return False
    1195                  if not self._PRW_end.is_valid_timestamp(empty_is_valid = False):
    1196                          self._PRW_end.SetBackgroundColour('pink')
    1197                          self._PRW_end.Refresh()
    1198                          self._PRW_end.SetFocus()
    1199                          return False
    1200                  self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
    1201                  self._PRW_end.Refresh()
    1202
    1203                  return True
    1204          #--------------------------------------------------------
    1205 -        def save(self):
    1206                  if not self.__is_valid_for_save():
    1207                          return False
    1208
    1209                  self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
    1210                  self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
    1211                  self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
    1212                  self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
    1213                  self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
    1214                  self.__encounter.save_payload()                 # FIXME: error checking
    1215
    1216                  self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
    1217                  self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
    1218
    1219                  return True
    1220  #----------------------------------------------------------------
    1221  # FIXME: use generic dialog 2
    1222 -class cEncounterEditAreaDlg(wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg):
    1223
    1224 -        def __init__(self, *args, **kwargs):
    1225                  encounter = kwargs['encounter']
    1226                  del kwargs['encounter']
    1227
    1228                  try:
    1229                          button_defs = kwargs['button_defs']
    1230                          del kwargs['button_defs']
    1231                  except KeyError:
    1232                          button_defs = None
    1233
    1234                  try:
    1235                          msg = kwargs['msg']
    1236                          del kwargs['msg']
    1237                  except KeyError:
    1238                          msg = None
    1239
    1240                  wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
    1241                  self.SetSize((450, 280))
    1242                  self.SetMinSize((450, 280))
    1243
    1244                  if button_defs is not None:
    1245                          self._BTN_save.SetLabel(button_defs[0][0])
    1246                          self._BTN_save.SetToolTipString(button_defs[0][1])
    1247                          self._BTN_close.SetLabel(button_defs[1][0])
    1248                          self._BTN_close.SetToolTipString(button_defs[1][1])
    1249                          self.Refresh()
    1250
    1251                  self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
    1252
    1253                  self.Fit()
    1254          #--------------------------------------------------------
    1255 -        def _on_save_button_pressed(self, evt):
    1256                  if self._PNL_edit_area.save():
    1257                          if self.IsModal():
    1258                                  self.EndModal(wx.ID_OK)
    1259                          else:
    1260                                  self.Close()
    1261          #--------------------------------------------------------
    1262 -        def _on_encounter_start_lost_focus(self):
    1263                  start = self._PRW_encounter_start.GetData()
    1264                  if start is None:
    1265                          return
    1266                  start = start.get_pydt()
    1267
    1268                  end = self._PRW_encounter_end.GetData()
    1269                  if end is None:
    1270                          fts = gmDateTime.cFuzzyTimestamp (
    1271                                  timestamp = start,
    1272                                  accuracy = gmDateTime.acc_minutes
    1273                          )
    1274                          self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
    1275                          return
    1276                  end = end.get_pydt()
    1277
    1278                  if start > end:
    1279                          end = end.replace (
    1280                                  year = start.year,
    1281                                  month = start.month,
    1282                                  day = start.day
    1283                          )
    1284                          fts = gmDateTime.cFuzzyTimestamp (
    1285                                  timestamp = end,
    1286                                  accuracy = gmDateTime.acc_minutes
    1287                          )
    1288                          self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
    1289                          return
    1290
    1291                  emr = self.__pat.get_emr()
    1292                  if start != emr.active_encounter['started']:
    1293                          end = end.replace (
    1294                                  year = start.year,
    1295                                  month = start.month,
    1296                                  day = start.day
    1297                          )
    1298                          fts = gmDateTime.cFuzzyTimestamp (
    1299                                  timestamp = end,
    1300                                  accuracy = gmDateTime.acc_minutes
    1301                          )
    1302                          self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
    1303                          return
    1304
    1305                  return
    1306
    1307  #----------------------------------------------------------------
    1308  from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
    1309
    1310 -class cActiveEncounterPnl(wxgActiveEncounterPnl.wxgActiveEncounterPnl):
    1311
    1312 -        def __init__(self, *args, **kwargs):
    1313                  wxgActiveEncounterPnl.wxgActiveEncounterPnl.__init__(self, *args, **kwargs)
    1314                  self.__register_events()
    1315                  self.refresh()
    1316          #------------------------------------------------------------
    1317 -        def clear(self):
    1318                  self._TCTRL_encounter.SetValue(u'')
    1319                  self._TCTRL_encounter.SetToolTipString(u'')
    1320                  self._BTN_new.Enable(False)
    1321                  self._BTN_list.Enable(False)
    1322          #------------------------------------------------------------
    1323 -        def refresh(self):
    1324                  pat = gmPerson.gmCurrentPatient()
    1325                  if not pat.connected:
    1326                          self.clear()
    1327                          return
    1328
    1329                  enc = pat.get_emr().active_encounter
    1330                  self._TCTRL_encounter.SetValue(enc.format(with_docs = False, with_tests = False, fancy_header = False, with_vaccinations = False, with_family_history = False).strip('\n'))
    1331                  self._TCTRL_encounter.SetToolTipString (
    1332                          _('The active encounter of the current patient:\n\n%s') %
    1333                                  enc.format(with_docs = False, with_tests = False, fancy_header = True, with_vaccinations = False, with_rfe_aoe = True, with_family_history = False).strip('\n')
    1334                  )
    1335                  self._BTN_new.Enable(True)
    1336                  self._BTN_list.Enable(True)
    1337          #------------------------------------------------------------
    1338 -        def __register_events(self):
    1339                  self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
    1340
    1341                  gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear)
    1342                  # this would throw an exception due to concurrency issues:
    1343                  #gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._schedule_refresh)
    1344                  gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._schedule_refresh)
    1345                  gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh)
    1346                  gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
    1347          #------------------------------------------------------------
    1348          # event handler
    1349          #------------------------------------------------------------
    1350 -        def _schedule_clear(self):
    1351                  wx.CallAfter(self.clear)
    1352          #------------------------------------------------------------
    1353 -        def _schedule_refresh(self, *args, **kwargs):
    1354                  wx.CallAfter(self.refresh)
    1355                  return True
    1356          #------------------------------------------------------------
    1357 -        def _on_ldclick(self, event):
    1358                  pat = gmPerson.gmCurrentPatient()
    1359                  if not pat.connected:
    1360                          return
    1361                  edit_encounter(encounter = pat.get_emr().active_encounter)
    1362          #------------------------------------------------------------
    1363 -        def _on_new_button_pressed(self, event):
    1364                  pat = gmPerson.gmCurrentPatient()
    1365                  if not pat.connected:
    1366                          return
    1367                  start_new_encounter(emr = pat.get_emr())
    1368          #------------------------------------------------------------
    1369 -        def _on_list_button_pressed(self, event):
    1370                  if not gmPerson.gmCurrentPatient().connected:
    1371                          return
    1372                  select_encounters()
    1373  #================================================================
    1374  # episode related widgets/functions
    1375  #----------------------------------------------------------------
    1376 -def edit_episode(parent=None, episode=None):
    1377          ea = cEpisodeEditAreaPnl(parent = parent, id = -1)
    1378          ea.data = episode
    1379          ea.mode = gmTools.coalesce(episode, 'new', 'edit')
    1380          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
    1381          dlg.SetTitle(gmTools.coalesce(episode, _('Adding a new episode'), _('Editing an episode')))
    1382          if dlg.ShowModal() == wx.ID_OK:
    1383                  return True
    1384          return False
    1385  #----------------------------------------------------------------
    1386 -def promote_episode_to_issue(parent=None, episode=None, emr=None):
    1387
    1388          created_new_issue = False
    1389
    1390          try:
    1391                  issue = gmEMRStructItems.cHealthIssue(name = episode['description'], patient = episode['pk_patient'])
    1392          except gmExceptions.NoSuchBusinessObjectError:
    1393                  issue = None
    1394
    1395          if issue is None:
    1396                  issue = emr.add_health_issue(issue_name = episode['description'])
    1397                  created_new_issue = True
    1398          else:
    1399                  # issue exists already, so ask user
    1400                  dlg = gmGuiHelpers.c3ButtonQuestionDlg (
    1401                          parent,
    1402                          -1,
    1403                          caption = _('Promoting episode to health issue'),
    1404                          question = _(
    1405                                  'There already is a health issue\n'
    1406                                  '\n'
    1407                                  ' %s\n'
    1408                                  '\n'
    1409                                  'What do you want to do ?'
    1410                          ) % issue['description'],
    1411                          button_defs = [
    1412                                  {'label': _('Use existing'), 'tooltip': _('Move episode into existing health issue'), 'default': False},
    1413                                  {'label': _('Create new'), 'tooltip': _('Create a new health issue with another name'), 'default': True}
    1414                          ]
    1415                  )
    1416                  use_existing = dlg.ShowModal()
    1417                  dlg.Destroy()
    1418
    1419                  if use_existing == wx.ID_CANCEL:
    1420                          return
    1421
    1422                  # user wants to create new issue with alternate name
    1423                  if use_existing == wx.ID_NO:
    1424                          # loop until name modified but non-empty or cancelled
    1425                          issue_name = episode['description']
    1426                          while issue_name == episode['description']:
    1427                                  dlg = wx.TextEntryDialog (
    1428                                          parent = parent,
    1429                                          message = _('Enter a short descriptive name for the new health issue:'),
    1430                                          caption = _('Creating a new health issue ...'),
    1431                                          defaultValue = issue_name,
    1432                                          style = wx.OK | wx.CANCEL | wx.CENTRE
    1433                                  )
    1434                                  decision = dlg.ShowModal()
    1435                                  if decision != wx.ID_OK:
    1436                                          dlg.Destroy()
    1437                                          return
    1438                                  issue_name = dlg.GetValue().strip()
    1439                                  dlg.Destroy()
    1440                                  if issue_name == u'':
    1441                                          issue_name = episode['description']
    1442
    1443                          issue = emr.add_health_issue(issue_name = issue_name)
    1444                          created_new_issue = True
    1445
    1446          # eventually move the episode to the issue
    1447          if not move_episode_to_issue(episode = episode, target_issue = issue, save_to_backend = True):
    1448                  # user cancelled the move so delete just-created issue
    1449                  if created_new_issue:
    1450                          # shouldn't fail as it is completely new
    1451                          gmEMRStructItems.delete_health_issue(health_issue = issue)
    1452                  return
    1453
    1454          return
    1455  #----------------------------------------------------------------
    1456 -def move_episode_to_issue(episode=None, target_issue=None, save_to_backend=False):
    1457          """Prepare changing health issue for an episode.
    1458
    1459          Checks for two-open-episodes conflict. When this
    1460          function succeeds, the pk_health_issue has been set
    1461          on the episode instance and the episode should - for
    1462          all practical purposes - be ready for save_payload().
    1463          """
    1464          # episode is closed: should always work
    1465          if not episode['episode_open']:
    1466                  episode['pk_health_issue'] = target_issue['pk_health_issue']
    1467                  if save_to_backend:
    1468                          episode.save_payload()
    1469                  return True
    1470
    1471          # un-associate: should always work, too
    1472          if target_issue is None:
    1473                  episode['pk_health_issue'] = None
    1474                  if save_to_backend:
    1475                          episode.save_payload()
    1476                  return True
    1477
    1478          # try closing possibly expired episode on target issue if any
    1479          db_cfg = gmCfg.cCfgSQL()
    1480          epi_ttl = int(db_cfg.get2 (
    1481                  option = u'episode.ttl',
    1482                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1483                  bias = 'user',
    1484                  default = 60                            # 2 months
    1485          ))
    1486          if target_issue.close_expired_episode(ttl=epi_ttl) is True:
    1487                  gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
    1488          existing_epi = target_issue.get_open_episode()
    1489
    1490          # no more open episode on target issue: should work now
    1491          if existing_epi is None:
    1492                  episode['pk_health_issue'] = target_issue['pk_health_issue']
    1493                  if save_to_backend:
    1494                          episode.save_payload()
    1495                  return True
    1496
    1497          # don't conflict on SELF ;-)
    1498          if existing_epi['pk_episode'] == episode['pk_episode']:
    1499                  episode['pk_health_issue'] = target_issue['pk_health_issue']
    1500                  if save_to_backend:
    1501                          episode.save_payload()
    1502                  return True
    1503
    1504          # we got two open episodes at once, ask user
    1505          move_range = episode.get_access_range()
    1506          exist_range = existing_epi.get_access_range()
    1507          question = _(
    1508                  'You want to associate the running episode:\n\n'
    1509                  ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
    1510                  'with the health issue:\n\n'
    1511                  ' "%(issue_name)s"\n\n'
    1512                  'There already is another episode running\n'
    1513                  'for this health issue:\n\n'
    1514                  ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
    1515                  'However, there can only be one running\n'
    1516                  'episode per health issue.\n\n'
    1517                  'Which episode do you want to close ?'
    1518          ) % {
    1519                  'new_epi_name': episode['description'],
    1520                  'new_epi_start': move_range[0].strftime('%m/%y'),
    1521                  'new_epi_end': move_range[1].strftime('%m/%y'),
    1522                  'issue_name': target_issue['description'],
    1523                  'old_epi_name': existing_epi['description'],
    1524                  'old_epi_start': exist_range[0].strftime('%m/%y'),
    1525                  'old_epi_end': exist_range[1].strftime('%m/%y')
    1526          }
    1527          dlg = gmGuiHelpers.c3ButtonQuestionDlg (
    1528                  parent = None,
    1529                  id = -1,
    1530                  caption = _('Resolving two-running-episodes conflict'),
    1531                  question = question,
    1532                  button_defs = [
    1533                          {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
    1534                          {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
    1535                  ]
    1536          )
    1537          decision = dlg.ShowModal()
    1538
    1539          if decision == wx.ID_CANCEL:
    1540                  # button 3: move cancelled by user
    1541                  return False
    1542
    1543          elif decision == wx.ID_YES:
    1544                  # button 1: close old episode
    1545                  existing_epi['episode_open'] = False
    1546                  existing_epi.save_payload()
    1547
    1548          elif decision == wx.ID_NO:
    1549                  # button 2: close new episode
    1550                  episode['episode_open'] = False
    1551
    1552          else:
    1553                  raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
    1554
    1555          episode['pk_health_issue'] = target_issue['pk_health_issue']
    1556          if save_to_backend:
    1557                  episode.save_payload()
    1558          return True
    1559  #----------------------------------------------------------------
    1560 -class cEpisodeListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
    1561
    1562          # FIXME: support pre-selection
    1563
    1564 -        def __init__(self, *args, **kwargs):
    1565
    1566                  episodes = kwargs['episodes']
    1567                  del kwargs['episodes']
    1568
    1569                  gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
    1570
    1571                  self.SetTitle(_('Select the episodes you are interested in ...'))
    1572                  self._LCTRL_items.set_columns([_('Episode'), _('Status'), _('Health Issue')])
    1573                  self._LCTRL_items.set_string_items (
    1574                          items = [
    1575                                  [       epi['description'],
    1576                                          gmTools.bool2str(epi['episode_open'], _('ongoing'), u''),
    1577                                          gmTools.coalesce(epi['health_issue'], u'')
    1578                                  ]
    1579                                  for epi in episodes ]
    1580                  )
    1581                  self._LCTRL_items.set_column_widths()
    1582                  self._LCTRL_items.set_data(data = episodes)
    1583  #----------------------------------------------------------------
    1584 -class cEpisodeDescriptionPhraseWheel(gmPhraseWheel.cPhraseWheel):
    1585          """Let user select an episode *description*.
    1586
    1587          The user can select an episode description from the previously
    1588          used descriptions across all episodes across all patients.
    1589
    1590          Selection is done with a phrasewheel so the user can
    1591          type the episode name and matches will be shown. Typing
    1592          "*" will show the entire list of episodes.
    1593
    1594          If the user types a description not existing yet a
    1595          new episode description will be returned.
    1596          """
    1597 -        def __init__(self, *args, **kwargs):
    1598
    1599                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    1600                          queries = [
    1601  u"""
    1602  SELECT DISTINCT ON (description)
    1603          description
    1604                  AS data,
    1605          description
    1606                  AS field_label,
    1607          description || ' ('
    1608          || CASE
    1609                  WHEN is_open IS TRUE THEN _('ongoing')
    1610                  ELSE _('closed')
    1611             END
    1612          || ')'
    1613                  AS list_label
    1614  FROM
    1615          clin.episode
    1616  WHERE
    1617          description %(fragment_condition)s
    1618  ORDER BY description
    1619  LIMIT 30
    1620  """
    1621                          ]
    1622                  )
    1623                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    1624                  self.matcher = mp
    1625  #----------------------------------------------------------------
    1626 -class cEpisodeSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
    1627          """Let user select an episode.
    1628
    1629          The user can select an episode from the existing episodes of a
    1630          patient. Selection is done with a phrasewheel so the user
    1631          can type the episode name and matches will be shown. Typing
    1632          "*" will show the entire list of episodes. Closed episodes
    1633          will be marked as such. If the user types an episode name not
    1634          in the list of existing episodes a new episode can be created
    1635          from it if the programmer activated that feature.
    1636
    1637          If keyword <patient_id> is set to None or left out the control
    1638          will listen to patient change signals and therefore act on
    1639          gmPerson.gmCurrentPatient() changes.
    1640          """
    1641 -        def __init__(self, *args, **kwargs):
    1642
    1643                  ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
    1644
    1645                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    1646                          queries = [
    1647  u"""(
    1648
    1649  select
    1650          pk_episode
    1651                  as data,
    1652          description
    1653                  as field_label,
    1654          coalesce (
    1655                  description || ' - ' || health_issue,
    1656                  description
    1657          ) as list_label,
    1658          1 as rank
    1659  from
    1660          clin.v_pat_episodes
    1661  where
    1662          episode_open is true and
    1663          description %(fragment_condition)s
    1664          %(ctxt_pat)s
    1665
    1666  ) union all (
    1667
    1668  select
    1669          pk_episode
    1670                  as data,
    1671          description
    1672                  as field_label,
    1673          coalesce (
    1674                  description || _(' (closed)') || ' - ' || health_issue,
    1675                  description || _(' (closed)')
    1676          ) as list_label,
    1677          2 as rank
    1678  from
    1679          clin.v_pat_episodes
    1680  where
    1681          description %(fragment_condition)s and
    1682          episode_open is false
    1683          %(ctxt_pat)s
    1684
    1685  )
    1686
    1687  order by rank, list_label
    1688  limit 30"""
    1689  ],
    1690                          context = ctxt
    1691                  )
    1692
    1693                  try:
    1694                          kwargs['patient_id']
    1695                  except KeyError:
    1696                          kwargs['patient_id'] = None
    1697
    1698                  if kwargs['patient_id'] is None:
    1699                          self.use_current_patient = True
    1700                          self.__register_patient_change_signals()
    1701                          pat = gmPerson.gmCurrentPatient()
    1702                          if pat.connected:
    1703                                  mp.set_context('pat', pat.ID)
    1704                  else:
    1705                          self.use_current_patient = False
    1706                          self.__patient_id = int(kwargs['patient_id'])
    1707                          mp.set_context('pat', self.__patient_id)
    1708
    1709                  del kwargs['patient_id']
    1710
    1711                  gmPhraseWheel.cPhraseWheel.__init__ (
    1712                          self,
    1713                          *args,
    1714                          **kwargs
    1715                  )
    1716                  self.matcher = mp
    1717          #--------------------------------------------------------
    1718          # external API
    1719          #--------------------------------------------------------
    1720 -        def set_patient(self, patient_id=None):
    1721                  if self.use_current_patient:
    1722                          return False
    1723                  self.__patient_id = int(patient_id)
    1724                  self.set_context('pat', self.__patient_id)
    1725                  return True
    1726          #--------------------------------------------------------
    1727 -        def GetData(self, can_create=False, as_instance=False, is_open=False):
    1728                  self.__is_open_for_create_data = is_open                # used (only) in _create_data()
    1729                  return gmPhraseWheel.cPhraseWheel.GetData(self, can_create = can_create, as_instance = as_instance)
    1730          #--------------------------------------------------------
    1731 -        def _create_data(self):
    1732
    1733                  epi_name = self.GetValue().strip()
    1734                  if epi_name == u'':
    1735                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
    1736                          _log.debug('cannot create episode without name')
    1737                          return
    1738
    1739                  if self.use_current_patient:
    1740                          pat = gmPerson.gmCurrentPatient()
    1741                  else:
    1742                          pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
    1743
    1744                  emr = pat.get_emr()
    1745                  epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
    1746                  if epi is None:
    1747                          self.data = {}
    1748                  else:
    1749                          self.SetText (
    1750                                  value = epi_name,
    1751                                  data = epi['pk_episode']
    1752                          )
    1753          #--------------------------------------------------------
    1754 -        def _data2instance(self):
    1755                  return gmEMRStructItems.cEpisode(aPK_obj = self.GetData())
    1756          #--------------------------------------------------------
    1757          # internal API
    1758          #--------------------------------------------------------
    1759 -        def __register_patient_change_signals(self):
    1760                  gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
    1761                  gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
    1762          #--------------------------------------------------------
    1763 -        def _pre_patient_selection(self):
    1764                  return True
    1765          #--------------------------------------------------------
    1766 -        def _post_patient_selection(self):
    1767                  if self.use_current_patient:
    1768                          patient = gmPerson.gmCurrentPatient()
    1769                          self.set_context('pat', patient.ID)
    1770                  return True
    1771  #----------------------------------------------------------------
    1772  from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
    1773
    1774 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
    1775
    1776 -        def __init__(self, *args, **kwargs):
    1777
    1778                  try:
    1779                          episode = kwargs['episode']
    1780                          del kwargs['episode']
    1781                  except KeyError:
    1782                          episode = None
    1783
    1784                  wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl.__init__(self, *args, **kwargs)
    1785                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    1786
    1787                  self.data = episode
    1788          #----------------------------------------------------------------
    1789          # generic Edit Area mixin API
    1790          #----------------------------------------------------------------
    1791 -        def _valid_for_save(self):
    1792
    1793                  errors = False
    1794
    1795                  if len(self._PRW_description.GetValue().strip()) == 0:
    1796                          errors = True
    1797                          self._PRW_description.display_as_valid(False)
    1798                          self._PRW_description.SetFocus()
    1799                  else:
    1800                          self._PRW_description.display_as_valid(True)
    1801                  self._PRW_description.Refresh()
    1802
    1803                  return not errors
    1804          #----------------------------------------------------------------
    1805 -        def _save_as_new(self):
    1806
    1807                  pat = gmPerson.gmCurrentPatient()
    1808                  emr = pat.get_emr()
    1809
    1810                  epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
    1811                  epi['summary'] = self._TCTRL_status.GetValue().strip()
    1812                  epi['episode_open'] = not self._CHBOX_closed.IsChecked()
    1813                  epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
    1814
    1815                  issue_name = self._PRW_issue.GetValue().strip()
    1816                  if len(issue_name) != 0:
    1817                          epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
    1818                          issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
    1819
    1820                          if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
    1821                                  gmDispatcher.send (
    1822                                          signal = 'statustext',
    1823                                          msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
    1824                                                  epi['description'],
    1825                                                  issue['description']
    1826                                          )
    1827                                  )
    1828                                  gmEMRStructItems.delete_episode(episode = epi)
    1829                                  return False
    1830
    1831                  epi.save()
    1832
    1833                  epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    1834
    1835                  self.data = epi
    1836                  return True
    1837          #----------------------------------------------------------------
    1838 -        def _save_as_update(self):
    1839
    1840                  self.data['description'] = self._PRW_description.GetValue().strip()
    1841                  self.data['summary'] = self._TCTRL_status.GetValue().strip()
    1842                  self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
    1843                  self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
    1844
    1845                  issue_name = self._PRW_issue.GetValue().strip()
    1846                  if len(issue_name) == 0:
    1847                          self.data['pk_health_issue'] = None
    1848                  else:
    1849                          self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
    1850                          issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
    1851
    1852                          if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
    1853                                  gmDispatcher.send (
    1854                                          signal = 'statustext',
    1855                                          msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
    1856                                                  self.data['description'],
    1857                                                  issue['description']
    1858                                          )
    1859                                  )
    1860                                  return False
    1861
    1862                  self.data.save()
    1863                  self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    1864
    1865                  return True
    1866          #----------------------------------------------------------------
    1867 -        def _refresh_as_new(self):
    1868                  if self.data is None:
    1869                          ident = gmPerson.gmCurrentPatient()
    1870                  else:
    1871                          ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
    1872                  self._TCTRL_patient.SetValue(ident.get_description_gender())
    1873                  self._PRW_issue.SetText()
    1874                  self._PRW_description.SetText()
    1875                  self._TCTRL_status.SetValue(u'')
    1876                  self._PRW_certainty.SetText()
    1877                  self._CHBOX_closed.SetValue(False)
    1878                  self._PRW_codes.SetText()
    1879          #----------------------------------------------------------------
    1880 -        def _refresh_from_existing(self):
    1881                  ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
    1882                  self._TCTRL_patient.SetValue(ident.get_description_gender())
    1883
    1884                  if self.data['pk_health_issue'] is not None:
    1885                          self._PRW_issue.SetText(self.data['health_issue'], data=self.data['pk_health_issue'])
    1886
    1887                  self._PRW_description.SetText(self.data['description'], data=self.data['description'])
    1888
    1889                  self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], u''))
    1890
    1891                  if self.data['diagnostic_certainty_classification'] is not None:
    1892                          self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
    1893
    1894                  self._CHBOX_closed.SetValue(not self.data['episode_open'])
    1895
    1896                  val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
    1897                  self._PRW_codes.SetText(val, data)
    1898          #----------------------------------------------------------------
    1899 -        def _refresh_as_new_from_existing(self):
    1900                  self._refresh_as_new()
    1901  #================================================================
    1902  # health issue related widgets/functions
    1903  #----------------------------------------------------------------
    1904 -def edit_health_issue(parent=None, issue=None):
    1905          ea = cHealthIssueEditAreaPnl(parent = parent, id = -1)
    1906          ea.data = issue
    1907          ea.mode = gmTools.coalesce(issue, 'new', 'edit')
    1908          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (issue is not None))
    1909          dlg.SetTitle(gmTools.coalesce(issue, _('Adding a new health issue'), _('Editing a health issue')))
    1910          if dlg.ShowModal() == wx.ID_OK:
    1911                  return True
    1912          return False
    1913  #----------------------------------------------------------------
    1914 -def select_health_issues(parent=None, emr=None):
    1915
    1916          if parent is None:
    1917                  parent = wx.GetApp().GetTopWindow()
    1918          #-----------------------------------------
    1919          def refresh(lctrl):
    1920                  issues = emr.get_health_issues()
    1921                  items = [
    1922                          [
    1923                                  gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), u'', u''),
    1924                                  i['description'],
    1925                                  gmTools.bool2subst(i['clinically_relevant'], _('relevant'), u'', u''),
    1926                                  gmTools.bool2subst(i['is_active'], _('active'), u'', u''),
    1927                                  gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), u'', u'')
    1928                          ] for i in issues
    1929                  ]
    1930                  lctrl.set_string_items(items = items)
    1931                  lctrl.set_data(data = issues)
    1932          #-----------------------------------------
    1933          return gmListWidgets.get_choices_from_list (
    1934                  parent = parent,
    1935                  msg = _('\nSelect the health issues !\n'),
    1936                  caption = _('Showing health issues ...'),
    1937                  columns = [u'', _('Health issue'), u'', u'', u''],
    1938                  single_selection = False,
    1939                  #edit_callback = edit,
    1940                  #new_callback = edit,
    1941                  #delete_callback = delete,
    1942                  refresh_callback = refresh
    1943          )
    1944  #----------------------------------------------------------------
    1945 -class cIssueListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
    1946
    1947          # FIXME: support pre-selection
    1948
    1949 -        def __init__(self, *args, **kwargs):
    1950
    1951                  issues = kwargs['issues']
    1952                  del kwargs['issues']
    1953
    1954                  gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
    1955
    1956                  self.SetTitle(_('Select the health issues you are interested in ...'))
    1957                  self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
    1958
    1959                  for issue in issues:
    1960                          if issue['is_confidential']:
    1961                                  row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
    1962                                  self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
    1963                          else:
    1964                                  row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
    1965
    1966                          self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
    1967                          if issue['clinically_relevant']:
    1968                                  self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
    1969                          if issue['is_active']:
    1970                                  self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
    1971                          if issue['is_cause_of_death']:
    1972                                  self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
    1973
    1974                  self._LCTRL_items.set_column_widths()
    1975                  self._LCTRL_items.set_data(data = issues)
    1976  #----------------------------------------------------------------
    1977 -class cIssueSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
    1978          """Let the user select a health issue.
    1979
    1980          The user can select a health issue from the existing issues
    1981          of a patient. Selection is done with a phrasewheel so the user
    1982          can type the issue name and matches will be shown. Typing
    1983          "*" will show the entire list of issues. Inactive issues
    1984          will be marked as such. If the user types an issue name not
    1985          in the list of existing issues a new issue can be created
    1986          from it if the programmer activated that feature.
    1987
    1988          If keyword <patient_id> is set to None or left out the control
    1989          will listen to patient change signals and therefore act on
    1990          gmPerson.gmCurrentPatient() changes.
    1991          """
    1992 -        def __init__(self, *args, **kwargs):
    1993
    1994                  ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
    1995
    1996                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    1997                          # FIXME: consider clin.health_issue.clinically_relevant
    1998                          queries = [
    1999  u"""
    2000  SELECT
    2001          data,
    2002          field_label,
    2003          list_label
    2004  FROM ((
    2005          SELECT
    2006                  pk_health_issue AS data,
    2007                  description AS field_label,
    2008                  description AS list_label
    2009          FROM clin.v_health_issues
    2010          WHERE
    2011                  is_active IS true
    2012                          AND
    2013                  description %(fragment_condition)s
    2014                          AND
    2015                  %(ctxt_pat)s
    2016
    2017          ) UNION (
    2018
    2019          SELECT
    2020                  pk_health_issue AS data,
    2021                  description AS field_label,
    2022                  description || _(' (inactive)') AS list_label
    2023          FROM clin.v_health_issues
    2024          WHERE
    2025                  is_active IS false
    2026                          AND
    2027                  description %(fragment_condition)s
    2028                          AND
    2029                  %(ctxt_pat)s
    2030  )) AS union_query
    2031  ORDER BY
    2032          list_label"""],
    2033                          context = ctxt
    2034                  )
    2035
    2036                  try: kwargs['patient_id']
    2037                  except KeyError: kwargs['patient_id'] = None
    2038
    2039                  if kwargs['patient_id'] is None:
    2040                          self.use_current_patient = True
    2041                          self.__register_patient_change_signals()
    2042                          pat = gmPerson.gmCurrentPatient()
    2043                          if pat.connected:
    2044                                  mp.set_context('pat', pat.ID)
    2045                  else:
    2046                          self.use_current_patient = False
    2047                          self.__patient_id = int(kwargs['patient_id'])
    2048                          mp.set_context('pat', self.__patient_id)
    2049
    2050                  del kwargs['patient_id']
    2051
    2052                  gmPhraseWheel.cPhraseWheel.__init__ (
    2053                          self,
    2054                          *args,
    2055                          **kwargs
    2056                  )
    2057                  self.matcher = mp
    2058          #--------------------------------------------------------
    2059          # external API
    2060          #--------------------------------------------------------
    2061 -        def set_patient(self, patient_id=None):
    2062                  if self.use_current_patient:
    2063                          return False
    2064                  self.__patient_id = int(patient_id)
    2065                  self.set_context('pat', self.__patient_id)
    2066                  return True
    2067          #--------------------------------------------------------
    2068 -        def _create_data(self):
    2069                  issue_name = self.GetValue().strip()
    2070                  if issue_name == u'':
    2071                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True)
    2072                          _log.debug('cannot create health issue without name')
    2073                          return
    2074
    2075                  if self.use_current_patient:
    2076                          pat = gmPerson.gmCurrentPatient()
    2077                  else:
    2078                          pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
    2079
    2080                  emr = pat.get_emr()
    2081                  issue = emr.add_health_issue(issue_name = issue_name)
    2082
    2083                  if issue is None:
    2084                          self.data = {}
    2085                  else:
    2086                          self.SetText (
    2087                                  value = issue_name,
    2088                                  data = issue['pk_health_issue']
    2089                          )
    2090          #--------------------------------------------------------
    2091 -        def _data2instance(self):
    2092                  return gmEMRStructItems.cHealthIssue(aPK_obj = self.GetData())
    2093          #--------------------------------------------------------
    2094          # internal API
    2095          #--------------------------------------------------------
    2096 -        def __register_patient_change_signals(self):
    2097                  gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
    2098                  gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
    2099          #--------------------------------------------------------
    2100 -        def _pre_patient_selection(self):
    2101                  return True
    2102          #--------------------------------------------------------
    2103 -        def _post_patient_selection(self):
    2104                  if self.use_current_patient:
    2105                          patient = gmPerson.gmCurrentPatient()
    2106                          self.set_context('pat', patient.ID)
    2107                  return True
    2108  #------------------------------------------------------------
    2109  from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
    2110
    2111 -class cIssueSelectionDlg(wxgIssueSelectionDlg.wxgIssueSelectionDlg):
    2112
    2113 -        def __init__(self, *args, **kwargs):
    2114                  try:
    2115                          msg = kwargs['message']
    2116                  except KeyError:
    2117                          msg = None
    2118                  del kwargs['message']
    2119                  wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
    2120                  if msg is not None:
    2121                          self._lbl_message.SetLabel(label=msg)
    2122          #--------------------------------------------------------
    2123 -        def _on_OK_button_pressed(self, event):
    2124                  event.Skip()
    2125                  pk_issue = self._PhWheel_issue.GetData(can_create=True)
    2126                  if pk_issue is None:
    2127                          gmGuiHelpers.gm_show_error (
    2128                                  _('Cannot create new health issue:\n [%(issue)s]') % {'issue': self._PhWheel_issue.GetValue().strip()},
    2129                                  _('Selecting health issue')
    2130                          )
    2131                          return False
    2132                  return True
    2133  #------------------------------------------------------------
    2134  from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
    2135
    2136 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
    2137          """Panel encapsulating health issue edit area functionality."""
    2138
    2139 -        def __init__(self, *args, **kwargs):
    2140
    2141                  try:
    2142                          issue = kwargs['issue']
    2143                  except KeyError:
    2144                           issue = None
    2145
    2146                  wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
    2147
    2148                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    2149
    2150                  # FIXME: include more sources: coding systems/other database columns
    2151                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    2152                          queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
    2153                  )
    2154                  mp.setThresholds(1, 3, 5)
    2155                  self._PRW_condition.matcher = mp
    2156
    2157                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    2158                          queries = [u"""
    2159  select distinct on (grouping) grouping, grouping from (
    2160
    2161          select rank, grouping from ((
    2162
    2163                  select
    2164                          grouping,
    2165                          1 as rank
    2166                  from
    2167                          clin.health_issue
    2168                  where
    2169                          grouping %%(fragment_condition)s
    2170                                  and
    2171                          (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
    2172
    2173          ) union (
    2174
    2175                  select
    2176                          grouping,
    2177                          2 as rank
    2178                  from
    2179                          clin.health_issue
    2180                  where
    2181                          grouping %%(fragment_condition)s
    2182
    2183          )) as union_result
    2184
    2185          order by rank
    2186
    2187  ) as order_result
    2188
    2189  limit 50""" % gmPerson.gmCurrentPatient().ID
    2190                          ]
    2191                  )
    2192                  mp.setThresholds(1, 3, 5)
    2193                  self._PRW_grouping.matcher = mp
    2194
    2195                  self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
    2196                  self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
    2197
    2198                  self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
    2199                  self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
    2200
    2201                  self._PRW_year_noted.Enable(True)
    2202
    2203                  self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
    2204
    2205                  self.data = issue
    2206          #----------------------------------------------------------------
    2207          # generic Edit Area mixin API
    2208          #----------------------------------------------------------------
    2209 -        def _valid_for_save(self):
    2210
    2211                  if self._PRW_condition.GetValue().strip() == '':
    2212                          self._PRW_condition.display_as_valid(False)
    2213                          self._PRW_condition.SetFocus()
    2214                          return False
    2215                  self._PRW_condition.display_as_valid(True)
    2216                  self._PRW_condition.Refresh()
    2217
    2218                  # FIXME: sanity check age/year diagnosed
    2219                  age_noted = self._PRW_age_noted.GetValue().strip()
    2220                  if age_noted != '':
    2221                          if gmDateTime.str2interval(str_interval = age_noted) is None:
    2222                                  self._PRW_age_noted.display_as_valid(False)
    2223                                  self._PRW_age_noted.SetFocus()
    2224                                  return False
    2225                  self._PRW_age_noted.display_as_valid(True)
    2226
    2227                  return True
    2228          #----------------------------------------------------------------
    2229 -        def _save_as_new(self):
    2230                  pat = gmPerson.gmCurrentPatient()
    2231                  emr = pat.get_emr()
    2232
    2233                  issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
    2234
    2235                  side = u''
    2236                  if self._ChBOX_left.GetValue():
    2237                          side += u's'
    2238                  if self._ChBOX_right.GetValue():
    2239                          side += u'd'
    2240                  issue['laterality'] = side
    2241
    2242                  issue['summary'] = self._TCTRL_status.GetValue().strip()
    2243                  issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
    2244                  issue['grouping'] = self._PRW_grouping.GetValue().strip()
    2245                  issue['is_active'] = self._ChBOX_active.GetValue()
    2246                  issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
    2247                  issue['is_confidential'] = self._ChBOX_confidential.GetValue()
    2248                  issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
    2249
    2250                  age_noted = self._PRW_age_noted.GetData()
    2251                  if age_noted is not None:
    2252                          issue['age_noted'] = age_noted
    2253
    2254                  issue.save()
    2255
    2256                  issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    2257
    2258                  self.data = issue
    2259                  return True
    2260          #----------------------------------------------------------------
    2261 -        def _save_as_update(self):
    2262
    2263                  self.data['description'] = self._PRW_condition.GetValue().strip()
    2264
    2265                  side = u''
    2266                  if self._ChBOX_left.GetValue():
    2267                          side += u's'
    2268                  if self._ChBOX_right.GetValue():
    2269                          side += u'd'
    2270                  self.data['laterality'] = side
    2271
    2272                  self.data['summary'] = self._TCTRL_status.GetValue().strip()
    2273                  self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
    2274                  self.data['grouping'] = self._PRW_grouping.GetValue().strip()
    2275                  self.data['is_active'] = bool(self._ChBOX_active.GetValue())
    2276                  self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
    2277                  self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
    2278                  self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
    2279
    2280                  age_noted = self._PRW_age_noted.GetData()
    2281                  if age_noted is not None:
    2282                          self.data['age_noted'] = age_noted
    2283
    2284                  self.data.save()
    2285                  self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    2286
    2287                  return True
    2288          #----------------------------------------------------------------
    2289 -        def _refresh_as_new(self):
    2290                  self._PRW_condition.SetText()
    2291                  self._ChBOX_left.SetValue(0)
    2292                  self._ChBOX_right.SetValue(0)
    2293                  self._PRW_codes.SetText()
    2294                  self._on_leave_codes()
    2295                  self._PRW_certainty.SetText()
    2296                  self._PRW_grouping.SetText()
    2297                  self._TCTRL_status.SetValue(u'')
    2298                  self._PRW_age_noted.SetText()
    2299                  self._PRW_year_noted.SetText()
    2300                  self._ChBOX_active.SetValue(1)
    2301                  self._ChBOX_relevant.SetValue(1)
    2302                  self._ChBOX_confidential.SetValue(0)
    2303                  self._ChBOX_caused_death.SetValue(0)
    2304
    2305                  return True
    2306          #----------------------------------------------------------------
    2307 -        def _refresh_from_existing(self):
    2308                  self._PRW_condition.SetText(self.data['description'])
    2309
    2310                  lat = gmTools.coalesce(self.data['laterality'], '')
    2311                  if lat.find('s') == -1:
    2312                          self._ChBOX_left.SetValue(0)
    2313                  else:
    2314                          self._ChBOX_left.SetValue(1)
    2315                  if lat.find('d') == -1:
    2316                          self._ChBOX_right.SetValue(0)
    2317                  else:
    2318                          self._ChBOX_right.SetValue(1)
    2319
    2320                  val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
    2321                  self._PRW_codes.SetText(val, data)
    2322                  self._on_leave_codes()
    2323
    2324                  if self.data['diagnostic_certainty_classification'] is not None:
    2325                          self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
    2326                  self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], u''))
    2327                  self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], u''))
    2328
    2329                  if self.data['age_noted'] is None:
    2330                          self._PRW_age_noted.SetText()
    2331                  else:
    2332                          self._PRW_age_noted.SetText (
    2333                                  value = '%sd' % self.data['age_noted'].days,
    2334                                  data = self.data['age_noted']
    2335                          )
    2336
    2337                  self._ChBOX_active.SetValue(self.data['is_active'])
    2338                  self._ChBOX_relevant.SetValue(self.data['clinically_relevant'])
    2339                  self._ChBOX_confidential.SetValue(self.data['is_confidential'])
    2340                  self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death'])
    2341
    2342                  # this dance should assure self._PRW_year_noted gets set -- but it doesn't ...
    2343  #               self._PRW_age_noted.SetFocus()
    2344  #               self._PRW_condition.SetFocus()
    2345
    2346                  return True
    2347          #----------------------------------------------------------------
    2348 -        def _refresh_as_new_from_existing(self):
    2349                  return self._refresh_as_new()
    2350          #--------------------------------------------------------
    2351          # internal helpers
    2352          #--------------------------------------------------------
    2353 -        def _on_leave_codes(self, *args, **kwargs):
    2354                  if not self._PRW_codes.IsModified():
    2355                          return True
    2356
    2357                  self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
    2358          #--------------------------------------------------------
    2359 -        def _on_leave_age_noted(self, *args, **kwargs):
    2360
    2361                  if not self._PRW_age_noted.IsModified():
    2362                          return True
    2363
    2364                  str_age = self._PRW_age_noted.GetValue().strip()
    2365
    2366                  if str_age == u'':
    2367                          wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
    2368                          return True
    2369
    2370                  age = gmDateTime.str2interval(str_interval = str_age)
    2371
    2372                  if age is None:
    2373                          gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
    2374                          self._PRW_age_noted.SetBackgroundColour('pink')
    2375                          self._PRW_age_noted.Refresh()
    2376                          wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
    2377                          return True
    2378
    2379                  pat = gmPerson.gmCurrentPatient()
    2380                  if pat['dob'] is not None:
    2381                          max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
    2382
    2383                          if age >= max_age:
    2384                                  gmDispatcher.send (
    2385                                          signal = 'statustext',
    2386                                          msg = _(
    2387                                                  'Health issue cannot have been noted at age %s. Patient is only %s old.'
    2388                                          ) % (age, pat.get_medical_age())
    2389                                  )
    2390                                  self._PRW_age_noted.SetBackgroundColour('pink')
    2391                                  self._PRW_age_noted.Refresh()
    2392                                  wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
    2393                                  return True
    2394
    2395                  self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
    2396                  self._PRW_age_noted.Refresh()
    2397                  self._PRW_age_noted.SetData(data=age)
    2398
    2399                  if pat['dob'] is not None:
    2400                          fts = gmDateTime.cFuzzyTimestamp (
    2401                                  timestamp = pat['dob'] + age,
    2402                                  accuracy = gmDateTime.acc_months
    2403                          )
    2404                          wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
    2405                          # if we do this we will *always* navigate there, regardless of TAB vs ALT-TAB
    2406                          #wx.CallAfter(self._ChBOX_active.SetFocus)
    2407                          # if we do the following instead it will take us to the save/update button ...
    2408                          #wx.CallAfter(self.Navigate)
    2409
    2410                  return True
    2411          #--------------------------------------------------------
    2412 -        def _on_leave_year_noted(self, *args, **kwargs):
    2413
    2414                  if not self._PRW_year_noted.IsModified():
    2415                          return True
    2416
    2417                  year_noted = self._PRW_year_noted.GetData()
    2418
    2419                  if year_noted is None:
    2420                          if self._PRW_year_noted.GetValue().strip() == u'':
    2421                                  wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
    2422                                  return True
    2423                          self._PRW_year_noted.SetBackgroundColour('pink')
    2424                          self._PRW_year_noted.Refresh()
    2425                          wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
    2426                          return True
    2427
    2428                  year_noted = year_noted.get_pydt()
    2429
    2430                  if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
    2431                          gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
    2432                          self._PRW_year_noted.SetBackgroundColour('pink')
    2433                          self._PRW_year_noted.Refresh()
    2434                          wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
    2435                          return True
    2436
    2437                  self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
    2438                  self._PRW_year_noted.Refresh()
    2439
    2440                  pat = gmPerson.gmCurrentPatient()
    2441                  if pat['dob'] is not None:
    2442                          issue_age = year_noted - pat['dob']
    2443                          str_age = gmDateTime.format_interval_medically(interval = issue_age)
    2444                          wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
    2445
    2446                  return True
    2447          #--------------------------------------------------------
    2448 -        def _on_modified_age_noted(self, *args, **kwargs):
    2449                  wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
    2450                  return True
    2451          #--------------------------------------------------------
    2452 -        def _on_modified_year_noted(self, *args, **kwargs):
    2453                  wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
    2454                  return True
    2455  #================================================================
    2456  # diagnostic certainty related widgets/functions
    2457  #----------------------------------------------------------------
    2458 -class cDiagnosticCertaintyClassificationPhraseWheel(gmPhraseWheel.cPhraseWheel):
    2459
    2460 -        def __init__(self, *args, **kwargs):
    2461
    2462                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    2463
    2464                  self.selection_only = False                     # can be NULL, too
    2465
    2466                  mp = gmMatchProvider.cMatchProvider_FixedList (
    2467                          aSeq = [
    2468                                  {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
    2469                                  {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
    2470                                  {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
    2471                                  {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
    2472                          ]
    2473                  )
    2474                  mp.setThresholds(1, 2, 4)
    2475                  self.matcher = mp
    2476
    2477                  self.SetToolTipString(_(
    2478                          "The diagnostic classification or grading of this assessment.\n"
    2479                          "\n"
    2480                          "This documents how certain one is about this being a true diagnosis."
    2481                  ))
    2482  #================================================================
    2483  # MAIN
    2484  #----------------------------------------------------------------
    2485  if __name__ == '__main__':
    2486
    2487          from Gnumed.business import gmPersonSearch
    2488          from Gnumed.wxpython import gmPatSearchWidgets
    2489
    2490          #================================================================
    2491 -        class testapp (wx.App):
    2492                          """
    2493                          Test application for testing EMR struct widgets
    2494                          """
    2495                          #--------------------------------------------------------
    2496 -                        def OnInit (self):
    2497                                  """
    2498                                  Create test application UI
    2499                                  """
    2500                                  frame = wx.Frame (
    2501                                                          None,
    2502                                                          -4,
    2503                                                          'Testing EMR struct widgets',
    2504                                                          size=wx.Size(600, 400),
    2505                                                          style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
    2506                                                  )
    2507                                  filemenu= wx.Menu()
    2508                                  filemenu.AppendSeparator()
    2509                                  filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
    2510
    2511                                  # Creating the menubar.
    2512                                  menuBar = wx.MenuBar()
    2513                                  menuBar.Append(filemenu,"&File")
    2514
    2515                                  frame.SetMenuBar(menuBar)
    2516
    2517                                  txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
    2518                                  wx.DefaultPosition, wx.DefaultSize, 0 )
    2519
    2520                                  # event handlers
    2521                                  wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
    2522
    2523                                  # patient EMR
    2524                                  self.__pat = gmPerson.gmCurrentPatient()
    2525
    2526                                  frame.Show(1)
    2527                                  return 1
    2528                          #--------------------------------------------------------
    2529 -                        def OnCloseWindow (self, e):
    2530                                  """
    2531                                  Close test aplication
    2532                                  """
    2533                                  self.ExitMainLoop ()
    2534          #----------------------------------------------------------------
    2535 -        def test_encounter_edit_area_panel():
    2536                  app = wx.PyWidgetTester(size = (200, 300))
    2537                  emr = pat.get_emr()
    2538                  enc = emr.active_encounter
    2539                  #enc = gmEMRStructItems.cEncounter(1)
    2540                  pnl = cEncounterEditAreaPnl(app.frame, -1, encounter=enc)
    2541                  app.frame.Show(True)
    2542                  app.MainLoop()
    2543                  return
    2544          #----------------------------------------------------------------
    2545 -        def test_encounter_edit_area_dialog():
    2546                  app = wx.PyWidgetTester(size = (200, 300))
    2547                  emr = pat.get_emr()
    2548                  enc = emr.active_encounter
    2549                  #enc = gmEMRStructItems.cEncounter(1)
    2550
    2551                  dlg = cEncounterEditAreaDlg(parent=app.frame, id=-1, size = (400,400), encounter=enc)
    2552                  dlg.ShowModal()
    2553
    2554  #               pnl = cEncounterEditAreaDlg(app.frame, -1, encounter=enc)
    2555  #               app.frame.Show(True)
    2556  #               app.MainLoop()
    2557          #----------------------------------------------------------------
    2558 -        def test_epsiode_edit_area_pnl():
    2559                  app = wx.PyWidgetTester(size = (200, 300))
    2560                  emr = pat.get_emr()
    2561                  epi = emr.get_episodes()[0]
    2562                  pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
    2563                  app.frame.Show(True)
    2564                  app.MainLoop()
    2565          #----------------------------------------------------------------
    2566 -        def test_episode_edit_area_dialog():
    2567                  app = wx.PyWidgetTester(size = (200, 300))
    2568                  emr = pat.get_emr()
    2569                  epi = emr.get_episodes()[0]
    2570                  edit_episode(parent=app.frame, episode=epi)
    2571          #----------------------------------------------------------------
    2572 -        def test_hospital_stay_prw():
    2573                  app = wx.PyWidgetTester(size = (400, 40))
    2574                  app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
    2575                  app.MainLoop()
    2576          #----------------------------------------------------------------
    2577 -        def test_episode_selection_prw():
    2578                  app = wx.PyWidgetTester(size = (400, 40))
    2579                  app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
    2580  #               app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(350,20), pos=(10,20), patient_id=pat.ID)
    2581                  app.MainLoop()
    2582          #----------------------------------------------------------------
    2583 -        def test_health_issue_edit_area_dlg():
    2584                  app = wx.PyWidgetTester(size = (200, 300))
    2585                  edit_health_issue(parent=app.frame, issue=None)
    2586          #----------------------------------------------------------------
    2587 -        def test_health_issue_edit_area_pnl():
    2588                  app = wx.PyWidgetTester(size = (200, 300))
    2589                  app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
    2590                  app.MainLoop()
    2591          #----------------------------------------------------------------
    2592 -        def test_edit_procedure():
    2593                  app = wx.PyWidgetTester(size = (200, 300))
    2594                  edit_procedure(parent=app.frame)
    2595          #================================================================
    2596
    2597          if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
    2598
    2599                  gmI18N.activate_locale()
    2600                  gmI18N.install_domain()
    2601                  gmDateTime.init()
    2602
    2603                  # obtain patient
    2604                  pat = gmPersonSearch.ask_for_patient()
    2605                  if pat is None:
    2606                          print "No patient. Exiting gracefully..."
    2607                          sys.exit(0)
    2608                  gmPatSearchWidgets.set_active_patient(patient=pat)
    2609
    2610  #       try:
    2611                  # lauch emr dialogs test application
    2612  #               app = testapp(0)
    2613  #               app.MainLoop()
    2614  #       except StandardError:
    2615  #               _log.exception("unhandled exception caught !")
    2616                  # but re-raise them
    2617  #               raise
    2618
    2619                  #test_encounter_edit_area_panel()
    2620                  #test_encounter_edit_area_dialog()
    2621                  #test_epsiode_edit_area_pnl()
    2622                  #test_episode_edit_area_dialog()
    2623                  #test_health_issue_edit_area_dlg()
    2624                  #test_episode_selection_prw()
    2625                  #test_hospital_stay_prw()
    2626                  test_edit_procedure()
    2627
    2628  #================================================================
    2629