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

# Source Code for Module Gnumed.wxpython.gmEncounterWidgets

      1  """GNUmed encounter related widgets.
      2
      3  This module contains widgets to manage encounters."""
      4  #================================================================
      5  __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
      6  __license__ = "GPL v2 or later"
      7
      8  # stdlib
      9  import sys
     10  import time
     11  import logging
     12  import datetime as pydt
     13
     14
     15  # 3rd party
     16  import wx
     17
     18
     19  # GNUmed
     20  if __name__ == '__main__':
     21          sys.path.insert(0, '../../')
     22  from Gnumed.pycommon import gmCfg
     23  from Gnumed.pycommon import gmDateTime
     24  from Gnumed.pycommon import gmTools
     25  from Gnumed.pycommon import gmDispatcher
     26  from Gnumed.pycommon import gmMatchProvider
     27
     28  from Gnumed.business import gmEMRStructItems
     29  from Gnumed.business import gmPraxis
     30  from Gnumed.business import gmPerson
     31  from Gnumed.business import gmStaff
     32
     33  from Gnumed.wxpython import gmPhraseWheel
     34  from Gnumed.wxpython import gmGuiHelpers
     35  from Gnumed.wxpython import gmListWidgets
     36  from Gnumed.wxpython import gmEditArea
     37
     38
     39  _log = logging.getLogger('gm.ui')
     40
     41  #================================================================
     42  # encounter related widgets/functions
     43  #----------------------------------------------------------------
     44 -def _ask_for_encounter_continuation(new_encounter=None, fairly_recent_encounter=None):
     45          """This is used as the callback when the EMR detects that the
     46             patient was here rather recently and wants to ask the
     47             provider whether to continue the recent encounter.
     48          """
     49          # better safe than sorry
     50          if new_encounter['pk_patient'] != fairly_recent_encounter['pk_patient']:
     51                  raise ValueError('pk_patient values on new (enc=%s pat=%s) and fairly-recent (enc=%s pat=%s) encounter do not match' % (
     52                          new_encounter['pk_encounter'],
     53                          new_encounter['pk_patient'],
     54                          fairly_recent_encounter['pk_encounter'],
     55                          fairly_recent_encounter['pk_patient']
     56                  ))
     57
     58          # only pester user if current patient is concerned
     59          curr_pat = gmPerson.gmCurrentPatient()
     60          if new_encounter['pk_patient'] != curr_pat.ID:
     61                  return
     62
     63          # ask user
     64          msg = _(
     65                  '%s, %s  [#%s]\n'
     66                  '\n'
     67                  "This patient's chart was worked on only recently:\n"
     68                  '\n'
     69                  ' %s'
     70                  '\n'
     71                  'Do you want to continue that consultation ?\n'
     72                  ' (If not a new one will be used.)\n'
     73          ) % (
     74                  curr_pat.get_description_gender(with_nickname = False),
     75                  gmDateTime.pydt_strftime(curr_pat['dob'], '%Y %b %d'),
     76                  curr_pat.ID,
     77                  fairly_recent_encounter.format (
     78                          episodes = None,
     79                          with_soap = False,
     80                          left_margin = 1,
     81                          patient = None,
     82                          issues = None,
     83                          with_docs = False,
     84                          with_tests = False,
     85                          fancy_header = False,
     86                          with_vaccinations = False,
     87                          with_rfe_aoe = True,
     88                          with_family_history = False,
     89                          with_co_encountlet_hints = False,
     90                          by_episode = False
     91                  )
     92          )
     93          dlg = gmGuiHelpers.c2ButtonQuestionDlg (
     94                  parent = None,
     95                  id = -1,
     96                  caption = _('Pulling chart'),
     97                  question = msg,
     98                  button_defs = [
     99                          {'label': _('Continue recent'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
    100                          {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
    101                  ],
    102                  show_checkbox = False
    103          )
    104          result = dlg.ShowModal()
    105          dlg.DestroyLater()
    106
    107          # switch encounters
    108          if result == wx.ID_YES:
    109                  _log.info('user wants to continue fairly-recent encounter')
    110                  curr_pat.emr.active_encounter = fairly_recent_encounter
    111                  if new_encounter.transfer_all_data_to_another_encounter(pk_target_encounter = fairly_recent_encounter['pk_encounter']):
    112                          if not gmEMRStructItems.delete_encounter(pk_encounter = new_encounter['pk_encounter']):
    113                                  gmGuiHelpers.gm_show_info (
    114                                          _('Properly switched to fairly recent encounter but unable to delete newly-created encounter.'),
    115                                          _('Pulling chart')
    116                                  )
    117                  else:
    118                          gmGuiHelpers.gm_show_info (
    119                                  _('Unable to transfer the data from newly-created to fairly recent encounter.'),
    120                                  _('Pulling chart')
    121                          )
    122                  return
    123
    124          _log.debug('stayed with newly created encounter')
    125
    126  #----------------------------------------------------------------
    127 -def __ask_for_encounter_continuation(**kwargs):
    128          try:
    129                  del kwargs['signal']
    130                  del kwargs['sender']
    131          except KeyError:
    132                  pass
    133          wx.CallAfter(_ask_for_encounter_continuation, **kwargs)
    134
    135  #----------------------------------------------------------------
    136  # listen for encounter continuation inquiry requests
    137  gmDispatcher.connect(signal = 'ask_for_encounter_continuation', receiver = __ask_for_encounter_continuation)
    138
    139  #----------------------------------------------------------------
    140 -def start_new_encounter(emr=None):
    141          emr.start_new_encounter()
    142          gmDispatcher.send(signal = 'statustext', msg = _('Started a new encounter for the active patient.'), beep = True)
    143          time.sleep(0.5)
    144          gmGuiHelpers.gm_show_info (
    145                  _('\nA new encounter was started for the active patient.\n'),
    146                  _('Start of new encounter')
    147          )
    148
    149  #----------------------------------------------------------------
    150 -def sanity_check_encounter_of_active_patient(parent=None, msg=None):
    151
    152          # FIXME: should consult a centralized security provider
    153          # secretaries cannot edit encounters
    154          if gmStaff.gmCurrentProvider()['role'] == 'secretary':
    155                  return True
    156
    157          pat = gmPerson.gmCurrentPatient()
    158          if not pat.connected:
    159                  return True
    160
    161          dbcfg = gmCfg.cCfgSQL()
    162          check_enc = bool(dbcfg.get2 (
    163                  option = 'encounter.show_editor_before_patient_change',
    164                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    165                  bias = 'user',
    166                  default = True                                  # True: if needed, not always unconditionally
    167          ))
    168
    169          if not check_enc:
    170                  return True
    171
    172          emr = pat.emr
    173          enc = emr.active_encounter
    174
    175          # did we add anything to the EMR ?
    176          has_narr = enc.has_narrative()
    177          has_docs = enc.has_documents()
    178
    179          if (not has_narr) and (not has_docs):
    180                  return True
    181
    182          empty_aoe = (gmTools.coalesce(enc['assessment_of_encounter'], '').strip() == '')
    183          zero_duration = (enc['last_affirmed'] == enc['started'])
    184
    185          # all is well anyway
    186          if (not empty_aoe) and (not zero_duration):
    187                  return True
    188
    189          if zero_duration:
    190                  enc['last_affirmed'] = pydt.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone)
    191
    192          # no narrative, presumably only import of docs and done
    193          if not has_narr:
    194                  if empty_aoe:
    195                          enc['assessment_of_encounter'] = _('only documents added')
    196                  enc['pk_type'] = gmEMRStructItems.get_encounter_type(description = 'chart review')[0]['pk']
    197                  # "last_affirmed" should be latest modified_at of relevant docs but that's a lot more involved
    198                  enc.save_payload()
    199                  return True
    200
    201          # does have narrative
    202          if empty_aoe:
    203                  # - work out suitable default
    204                  epis = emr.get_episodes_by_encounter()
    205                  if len(epis) > 0:
    206                          enc_summary = ''
    207                          for epi in epis:
    208                                  enc_summary += '%s; ' % epi['description']
    209                          enc['assessment_of_encounter'] = enc_summary
    210
    211          if msg is None:
    212                  msg = _('Edit the encounter details of the active patient before moving on:')
    213          if parent is None:
    214                  parent = wx.GetApp().GetTopWindow()
    215          _log.debug('sanity-check editing encounter [%s] for patient [%s]', enc['pk_encounter'], enc['pk_patient'])
    216          edit_encounter(parent = parent, encounter = enc, msg = msg)
    217
    218          return True
    219
    220  #----------------------------------------------------------------
    221 -def edit_encounter(parent=None, encounter=None, msg=None, single_entry=False):
    222          if parent is None:
    223                  parent = wx.GetApp().GetTopWindow()
    224
    225          # FIXME: use generic dialog 2
    226          dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter, msg = msg)
    227          if dlg.ShowModal() == wx.ID_OK:
    228                  dlg.DestroyLater()
    229                  return True
    230          dlg.DestroyLater()
    231          return False
    232
    233  #----------------------------------------------------------------
    234 -def manage_encounters(**kwargs):
    235          return select_encounters(**kwargs)
    236
    237 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False):
    238
    239          if patient is None:
    240                  patient = gmPerson.gmCurrentPatient()
    241
    242          if not patient.connected:
    243                  gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.'))
    244                  return False
    245
    246          if parent is None:
    247                  parent = wx.GetApp().GetTopWindow()
    248
    249          emr = patient.emr
    250
    251          #--------------------
    252          def new():
    253                  cfg_db = gmCfg.cCfgSQL()
    254                  enc_type = cfg_db.get2 (
    255                          option = 'encounter.default_type',
    256                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    257                          bias = 'user'
    258                  )
    259                  if enc_type is None:
    260                          enc_type = gmEMRStructItems.get_most_commonly_used_encounter_type()
    261                  if enc_type is None:
    262                          enc_type = 'in surgery'
    263                  enc = gmEMRStructItems.create_encounter(fk_patient = patient.ID, enc_type = enc_type)
    264                  saved = edit_encounter(parent = parent, encounter = enc)
    265                  if saved:
    266                          return True
    267                  gmEMRStructItems.delete_encounter(pk_encounter = enc['pk_encounter'])
    268                  return False
    269          #--------------------
    270          def edit(enc=None):
    271                  if enc is None:
    272                          return False
    273                  return edit_encounter(parent = parent, encounter = enc)
    274          #--------------------
    275          def edit_active(enc=None):
    276                  return edit_encounter(parent = parent, encounter = emr.active_encounter)
    277          #--------------------
    278          def start_new(enc=None):
    279                  start_new_encounter(emr = emr)
    280                  return True
    281          #--------------------
    282          def delete(enc=None):
    283                  if enc is None:
    284                          return False
    285                  question = _(
    286                          'Really delete encounter [%s] ?\n'
    287                          '\n'
    288                          'Once deletion succeeds it cannot be undone.\n'
    289                          '\n'
    290                          'Note that it will only succeed if there\n'
    291                          'is no data attached to the encounter.'
    292                  ) % enc['pk_encounter']
    293                  delete_it = gmGuiHelpers.gm_show_question (
    294                          question = question,
    295                          title = _('Deleting encounter'),
    296                          cancel_button = False
    297                  )
    298                  if not delete_it:
    299                          return False
    300                  if gmEMRStructItems.delete_encounter(pk_encounter = enc['pk_encounter']):
    301                          return True
    302                  gmDispatcher.send (
    303                          signal = 'statustext',
    304                          msg = _('Cannot delete encounter [%s]. It is probably in use.') % enc['pk_encounter'],
    305                          beep = True
    306                  )
    307                  return False
    308          #--------------------
    309          def get_tooltip(data):
    310                  if data is None:
    311                          return None
    312                  return data.format (
    313                          patient = patient,
    314                          with_soap = False,
    315                          with_docs = False,
    316                          with_tests = False,
    317                          with_vaccinations = False,
    318                          with_rfe_aoe = True,
    319                          with_family_history = False,
    320                          by_episode=False,
    321                          fancy_header = True,
    322                  )
    323          #--------------------
    324          def refresh(lctrl):
    325                  if encounters is None:
    326                          encs = emr.get_encounters()
    327                  else:
    328                          encs = encounters
    329
    330                  items = [
    331                          [
    332                                  '%s - %s' % (gmDateTime.pydt_strftime(e['started'], '%Y %b %d  %H:%M'), e['last_affirmed'].strftime('%H:%M')),
    333                                  e['l10n_type'],
    334                                  gmTools.coalesce(e['praxis_branch'], ''),
    335                                  gmTools.coalesce(e['reason_for_encounter'], ''),
    336                                  gmTools.coalesce(e['assessment_of_encounter'], ''),
    337                                  gmTools.bool2subst(e.has_clinical_data(), '', gmTools.u_checkmark_thin),
    338                                  e['pk_encounter']
    339                          ] for e in encs
    340                  ]
    341                  lctrl.set_string_items(items = items)
    342                  lctrl.set_data(data = encs)
    343                  active_pk = emr.active_encounter['pk_encounter']
    344                  for idx in range(len(encs)):
    345                          e = encs[idx]
    346                          if e['pk_encounter'] == active_pk:
    347                                  lctrl.SetItemTextColour(idx, wx.Colour('RED'))
    348          #--------------------
    349          return gmListWidgets.get_choices_from_list (
    350                  parent = parent,
    351                  msg = _("The patient's encounters.\n"),
    352                  caption = _('Encounters ...'),
    353                  columns = [_('When'), _('Type'), _('Where'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
    354                  can_return_empty = False,
    355                  single_selection = single_selection,
    356                  refresh_callback = refresh,
    357                  edit_callback = edit,
    358                  new_callback = new,
    359                  delete_callback = delete,
    360                  list_tooltip_callback = get_tooltip,
    361                  ignore_OK_button = ignore_OK_button,
    362                  left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
    363                  middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
    364          )
    365
    366  #----------------------------------------------------------------
    367 -class cEncounterPhraseWheel(gmPhraseWheel.cPhraseWheel):
    368
    369 -        def __init__(self, *args, **kwargs):
    370                  gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
    371
    372                  cmd = """
    373                          SELECT DISTINCT ON (list_label)
    374                                  pk_encounter
    375                                          AS data,
    376                                  to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type || ' [#' || pk_encounter || ']'
    377                                          AS list_label,
    378                                  to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
    379                                          AS field_label
    380                          FROM
    381                                  clin.v_pat_encounters
    382                          WHERE
    383                                  (
    384                                          to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
    385                                                  OR
    386                                          l10n_type %(fragment_condition)s
    387                                                  OR
    388                                          type %(fragment_condition)s
    389                                  )       %(ctxt_patient)s
    390                          ORDER BY
    391                                  list_label
    392                          LIMIT
    393                                  30
    394                  """
    395                  context = {'ctxt_patient': {
    396                          'where_part': 'AND pk_patient = %(patient)s',
    397                          'placeholder': 'patient'
    398                  }}
    399
    400                  self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
    401                  self.matcher._SQL_data2match = """
    402                          SELECT
    403                                  pk_encounter
    404                                          AS data,
    405                                  to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
    406                                          AS list_label,
    407                                  to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
    408                                          AS field_label
    409                          FROM
    410                                  clin.v_pat_encounters
    411                          WHERE
    412                                  pk_encounter = %(pk)s
    413                  """
    414                  self.matcher.setThresholds(1, 3, 5)
    415                  #self.matcher.print_queries = True
    416                  self.selection_only = True
    417                  # outside code MUST bind this to a patient
    418                  self.set_context(context = 'patient', val = None)
    419          #--------------------------------------------------------
    420 -        def set_from_instance(self, instance):
    421                  val = '%s: %s' % (
    422                          gmDateTime.pydt_strftime(instance['started'], '%Y %b %d'),
    423                          instance['l10n_type']
    424                  )
    425                  self.SetText(value = val, data = instance['pk_encounter'])
    426          #------------------------------------------------------------
    427 -        def _get_data_tooltip(self):
    428                  if self.GetData() is None:
    429                          return None
    430                  enc = gmEMRStructItems.cEncounter(aPK_obj = list(self._data.values())[0]['data'])
    431                  return enc.format (
    432                          with_docs = False,
    433                          with_tests = False,
    434                          with_vaccinations = False,
    435                          with_family_history = False
    436                  )
    437
    438  #----------------------------------------------------------------
    439  from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
    440
    441 -class cEncounterEditAreaPnl(wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl):
    442
    443 -        def __init__(self, *args, **kwargs):
    444                  try:
    445                          self.__encounter = kwargs['encounter']
    446                          del kwargs['encounter']
    447                  except KeyError:
    448                          self.__encounter = None
    449
    450                  try:
    451                          msg = kwargs['msg']
    452                          del kwargs['msg']
    453                  except KeyError:
    454                          msg = None
    455
    456                  wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
    457
    458                  self.refresh(msg = msg)
    459          #--------------------------------------------------------
    460          # external API
    461          #--------------------------------------------------------
    462 -        def refresh(self, encounter=None, msg=None):
    463
    464                  if msg is not None:
    465                          self._LBL_instructions.SetLabel(msg)
    466
    467                  if encounter is not None:
    468                          self.__encounter = encounter
    469
    470                  if self.__encounter is None:
    471                          return True
    472
    473                  # getting the patient via the encounter allows us to act
    474                  # on any encounter regardless of the currently active patient
    475                  pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
    476                  self._LBL_patient.SetLabel(pat.get_description_gender().strip())
    477                  curr_pat = gmPerson.gmCurrentPatient()
    478                  if curr_pat.connected:
    479                          if curr_pat.ID == self.__encounter['pk_patient']:
    480                                  self._LBL_patient.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
    481                          else:
    482                                  self._LBL_patient.SetForegroundColour('red')
    483
    484                  self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data = self.__encounter['pk_type'])
    485                  self._PRW_location.Enable(True)
    486                  branch = self.__encounter.praxis_branch
    487                  if branch is None:              # None or old entry because praxis has been re-configured
    488                          unit = self.__encounter.org_unit
    489                          if unit is None:                                                        # None
    490                                  self._PRW_location.SetText('', data = None)
    491                          else:                                                                           # old entry
    492                                  self._PRW_location.Enable(False)
    493                                  self._PRW_location.SetText(_('old praxis branch: %s (%s)') % (unit['unit'], unit['organization']), data = None)
    494                  else:
    495                          self._PRW_location.SetText(self.__encounter['praxis_branch'], data = branch['pk_praxis_branch'])
    496
    497                  fts = gmDateTime.cFuzzyTimestamp (
    498                          timestamp = self.__encounter['started'],
    499                          accuracy = gmDateTime.acc_minutes
    500                  )
    501                  self._PRW_start.SetText(fts.format_accurately(), data=fts)
    502
    503                  fts = gmDateTime.cFuzzyTimestamp (
    504                          timestamp = self.__encounter['last_affirmed'],
    505                          accuracy = gmDateTime.acc_minutes
    506                  )
    507                  self._PRW_end.SetText(fts.format_accurately(), data=fts)
    508
    509                  # RFE
    510                  self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
    511                  val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
    512                  self._PRW_rfe_codes.SetText(val, data)
    513
    514                  # AOE
    515                  self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
    516                  val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
    517                  self._PRW_aoe_codes.SetText(val, data)
    518
    519                  # last affirmed
    520                  if self.__encounter['last_affirmed'] == self.__encounter['started']:
    521                          self._PRW_end.SetFocus()
    522                  else:
    523                          self._TCTRL_aoe.SetFocus()
    524
    525                  return True
    526          #--------------------------------------------------------
    527 -        def __is_valid_for_save(self):
    528
    529                  if self._PRW_encounter_type.GetData() is None:
    530                          self._PRW_encounter_type.SetBackgroundColour('pink')
    531                          self._PRW_encounter_type.Refresh()
    532                          self._PRW_encounter_type.SetFocus()
    533                          return False
    534                  self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
    535                  self._PRW_encounter_type.Refresh()
    536
    537                  # start
    538                  if self._PRW_start.GetValue().strip() == '':
    539                          self._PRW_start.SetBackgroundColour('pink')
    540                          self._PRW_start.Refresh()
    541                          self._PRW_start.SetFocus()
    542                          return False
    543                  if not self._PRW_start.is_valid_timestamp(empty_is_valid = False):
    544                          self._PRW_start.SetBackgroundColour('pink')
    545                          self._PRW_start.Refresh()
    546                          self._PRW_start.SetFocus()
    547                          return False
    548                  self._PRW_start.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
    549                  self._PRW_start.Refresh()
    550
    551                  # last_affirmed
    552  #               if self._PRW_end.GetValue().strip() == u'':
    553  #                       self._PRW_end.SetBackgroundColour('pink')
    554  #                       self._PRW_end.Refresh()
    555  #                       self._PRW_end.SetFocus()
    556  #                       return False
    557                  if not self._PRW_end.is_valid_timestamp(empty_is_valid = False):
    558                          self._PRW_end.SetBackgroundColour('pink')
    559                          self._PRW_end.Refresh()
    560                          self._PRW_end.SetFocus()
    561                          return False
    562                  self._PRW_end.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
    563                  self._PRW_end.Refresh()
    564
    565                  return True
    566          #--------------------------------------------------------
    567 -        def save(self):
    568                  if not self.__is_valid_for_save():
    569                          return False
    570
    571                  self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
    572                  self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
    573                  self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
    574                  self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), '')
    575                  self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), '')
    576                  self.__encounter.save_payload()                 # FIXME: error checking
    577
    578                  self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
    579                  self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
    580
    581                  return True
    582
    583  #----------------------------------------------------------------
    584  from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg
    585
    586  # FIXME: use generic dialog 2
    587 -class cEncounterEditAreaDlg(wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg):
    588
    589 -        def __init__(self, *args, **kwargs):
    590                  encounter = kwargs['encounter']
    591                  del kwargs['encounter']
    592
    593                  try:
    594                          button_defs = kwargs['button_defs']
    595                          del kwargs['button_defs']
    596                  except KeyError:
    597                          button_defs = None
    598
    599                  try:
    600                          msg = kwargs['msg']
    601                          del kwargs['msg']
    602                  except KeyError:
    603                          msg = None
    604
    605                  wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
    606                  self.SetSize((450, 280))
    607                  self.SetMinSize((450, 280))
    608
    609                  if button_defs is not None:
    610                          self._BTN_save.SetLabel(button_defs[0][0])
    611                          self._BTN_save.SetToolTip(button_defs[0][1])
    612                          self._BTN_close.SetLabel(button_defs[1][0])
    613                          self._BTN_close.SetToolTip(button_defs[1][1])
    614                          self.Refresh()
    615
    616                  self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
    617
    618                  self.Fit()
    619          #--------------------------------------------------------
    620 -        def _on_save_button_pressed(self, evt):
    621                  if self._PNL_edit_area.save():
    622                          if self.IsModal():
    623                                  self.EndModal(wx.ID_OK)
    624                          else:
    625                                  self.Close()
    626          #--------------------------------------------------------
    627 -        def _on_encounter_start_lost_focus(self):
    628                  start = self._PRW_encounter_start.GetData()
    629                  if start is None:
    630                          return
    631                  start = start.get_pydt()
    632
    633                  end = self._PRW_encounter_end.GetData()
    634                  if end is None:
    635                          fts = gmDateTime.cFuzzyTimestamp (
    636                                  timestamp = start,
    637                                  accuracy = gmDateTime.acc_minutes
    638                          )
    639                          self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
    640                          return
    641                  end = end.get_pydt()
    642
    643                  if start > end:
    644                          end = end.replace (
    645                                  year = start.year,
    646                                  month = start.month,
    647                                  day = start.day
    648                          )
    649                          fts = gmDateTime.cFuzzyTimestamp (
    650                                  timestamp = end,
    651                                  accuracy = gmDateTime.acc_minutes
    652                          )
    653                          self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
    654                          return
    655
    656                  emr = self.__pat.emr
    657                  if start != emr.active_encounter['started']:
    658                          end = end.replace (
    659                                  year = start.year,
    660                                  month = start.month,
    661                                  day = start.day
    662                          )
    663                          fts = gmDateTime.cFuzzyTimestamp (
    664                                  timestamp = end,
    665                                  accuracy = gmDateTime.acc_minutes
    666                          )
    667                          self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
    668                          return
    669
    670                  return
    671
    672  #----------------------------------------------------------------
    673  from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
    674
    675 -class cActiveEncounterPnl(wxgActiveEncounterPnl.wxgActiveEncounterPnl):
    676
    677 -        def __init__(self, *args, **kwargs):
    678                  wxgActiveEncounterPnl.wxgActiveEncounterPnl.__init__(self, *args, **kwargs)
    679                  self.__register_events()
    680                  self.refresh()
    681
    682          #------------------------------------------------------------
    683 -        def clear(self):
    684                  self._TCTRL_encounter.SetValue('')
    685                  self._TCTRL_encounter.SetToolTip('')
    686                  self._BTN_new.Enable(False)
    687                  self._BTN_list.Enable(False)
    688
    689          #------------------------------------------------------------
    690 -        def refresh(self):
    691                  self.clear()
    692
    693                  pat = gmPerson.gmCurrentPatient()
    694                  if not pat.connected:
    695                          return
    696
    697                  wx.CallAfter(self.__refresh)
    698
    699          #------------------------------------------------------------
    700 -        def __refresh(self):
    701                  enc = gmPerson.gmCurrentPatient().emr.active_encounter
    702                  self._TCTRL_encounter.SetValue(enc.format (
    703                          with_docs = False,
    704                          with_tests = False,
    705                          fancy_header = False,
    706                          with_vaccinations = False,
    707                          with_family_history = False).strip('\n')
    708                  )
    709                  self._TCTRL_encounter.SetToolTip (
    710                          _('The active encounter of the current patient:\n\n%s') % enc.format(
    711                                  with_docs = False,
    712                                  with_tests = False,
    713                                  fancy_header = True,
    714                                  with_vaccinations = False,
    715                                  with_rfe_aoe = True,
    716                                  with_family_history = False).strip('\n')
    717                  )
    718                  self._BTN_new.Enable(True)
    719                  self._BTN_list.Enable(True)
    720
    721          #------------------------------------------------------------
    722 -        def __register_events(self):
    723                  self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
    724
    725                  gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
    726                  # this would throw an exception due to concurrency issues:
    727                  #gmDispatcher.connect(signal = u'post_patient_selection', receiver = self.refresh)
    728                  gmDispatcher.connect(signal = 'clin.episode_mod_db', receiver = self.refresh)
    729                  gmDispatcher.connect(signal = 'current_encounter_modified', receiver = self.refresh)
    730                  gmDispatcher.connect(signal = 'current_encounter_switched', receiver = self.refresh)
    731
    732          #------------------------------------------------------------
    733          # event handler
    734          #------------------------------------------------------------
    735 -        def _on_pre_patient_unselection(self):
    736                  self.clear()
    737
    738          #------------------------------------------------------------
    739 -        def _on_ldclick(self, event):
    740                  pat = gmPerson.gmCurrentPatient()
    741                  if not pat.connected:
    742                          return
    743                  edit_encounter(encounter = pat.emr.active_encounter)
    744
    745          #------------------------------------------------------------
    746 -        def _on_new_button_pressed(self, event):
    747                  pat = gmPerson.gmCurrentPatient()
    748                  if not pat.connected:
    749                          return
    750                  start_new_encounter(emr = pat.emr)
    751
    752          #------------------------------------------------------------
    753 -        def _on_list_button_pressed(self, event):
    754                  if not gmPerson.gmCurrentPatient().connected:
    755                          return
    756                  select_encounters()
    757
    758  #================================================================
    759  # encounter TYPE related widgets
    760  #----------------------------------------------------------------
    761 -def edit_encounter_type(parent=None, encounter_type=None):
    762          ea = cEncounterTypeEditAreaPnl(parent, -1)
    763          ea.data = encounter_type
    764          ea.mode = gmTools.coalesce(encounter_type, 'new', 'edit')
    765          dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea)
    766          dlg.SetTitle(gmTools.coalesce(encounter_type, _('Adding new encounter type'), _('Editing local encounter type name')))
    767          if dlg.ShowModal() == wx.ID_OK:
    768                  return True
    769          return False
    770
    771  #----------------------------------------------------------------
    772 -def manage_encounter_types(parent=None):
    773
    774          if parent is None:
    775                  parent = wx.GetApp().GetTopWindow()
    776
    777          #--------------------
    778          def edit(enc_type=None):
    779                  return edit_encounter_type(parent = parent, encounter_type = enc_type)
    780          #--------------------
    781          def delete(enc_type=None):
    782                  if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
    783                          return True
    784                  gmDispatcher.send (
    785                          signal = 'statustext',
    786                          msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
    787                          beep = True
    788                  )
    789                  return False
    790          #--------------------
    791          def refresh(lctrl):
    792                  enc_types = gmEMRStructItems.get_encounter_types()
    793                  lctrl.set_string_items(items = enc_types)
    794          #--------------------
    795          gmListWidgets.get_choices_from_list (
    796                  parent = parent,
    797                  msg = _('\nSelect the encounter type you want to edit !\n'),
    798                  caption = _('Managing encounter types ...'),
    799                  columns = [_('Local name'), _('Encounter type')],
    800                  single_selection = True,
    801                  edit_callback = edit,
    802                  new_callback = edit,
    803                  delete_callback = delete,
    804                  refresh_callback = refresh
    805          )
    806
    807  #----------------------------------------------------------------
    808  from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
    809
    810 -class cEncounterTypeEditAreaPnl(wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
    811
    812 -        def __init__(self, *args, **kwargs):
    813
    814                  wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl.__init__(self, *args, **kwargs)
    815                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    816
    817  #               self.__register_interests()
    818          #-------------------------------------------------------
    819          # generic edit area API
    820          #-------------------------------------------------------
    821 -        def _valid_for_save(self):
    822                  if self.mode == 'edit':
    823                          if self._TCTRL_l10n_name.GetValue().strip() == '':
    824                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
    825                                  return False
    826                          self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
    827                          return True
    828
    829                  no_errors = True
    830
    831                  if self._TCTRL_l10n_name.GetValue().strip() == '':
    832                          if self._TCTRL_name.GetValue().strip() == '':
    833                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
    834                                  no_errors = False
    835                          else:
    836                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
    837                  else:
    838                          self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
    839
    840                  if self._TCTRL_name.GetValue().strip() == '':
    841                          if self._TCTRL_l10n_name.GetValue().strip() == '':
    842                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = False)
    843                                  no_errors = False
    844                          else:
    845                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
    846                  else:
    847                          self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
    848
    849                  return no_errors
    850          #-------------------------------------------------------
    851 -        def _save_as_new(self):
    852                  enc_type = gmEMRStructItems.create_encounter_type (
    853                          description = gmTools.none_if(self._TCTRL_name.GetValue().strip(), ''),
    854                          l10n_description = gmTools.coalesce (
    855                                  gmTools.none_if(self._TCTRL_l10n_name.GetValue().strip(), ''),
    856                                  self._TCTRL_name.GetValue().strip()
    857                          )
    858                  )
    859                  if enc_type is None:
    860                          return False
    861                  self.data = enc_type
    862                  return True
    863          #-------------------------------------------------------
    864 -        def _save_as_update(self):
    865                  enc_type = gmEMRStructItems.update_encounter_type (
    866                          description = self._TCTRL_name.GetValue().strip(),
    867                          l10n_description = self._TCTRL_l10n_name.GetValue().strip()
    868                  )
    869                  if enc_type is None:
    870                          return False
    871                  self.data = enc_type
    872                  return True
    873          #-------------------------------------------------------
    874 -        def _refresh_as_new(self):
    875                  self._TCTRL_l10n_name.SetValue('')
    876                  self._TCTRL_name.SetValue('')
    877                  self._TCTRL_name.Enable(True)
    878          #-------------------------------------------------------
    879 -        def _refresh_from_existing(self):
    880                  self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
    881                  self._TCTRL_name.SetValue(self.data['description'])
    882                  # disallow changing type on all encounters by editing system name
    883                  self._TCTRL_name.Enable(False)
    884          #-------------------------------------------------------
    885 -        def _refresh_as_new_from_existing(self):
    886                  self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
    887                  self._TCTRL_name.SetValue(self.data['description'])
    888                  self._TCTRL_name.Enable(True)
    889          #-------------------------------------------------------
    890          # internal API
    891          #-------------------------------------------------------
    892  #       def __register_interests(self):
    893  #               return
    894
    895  #----------------------------------------------------------------
    896 -class cEncounterTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
    897          """Phrasewheel to allow selection of encounter type.
    898
    899          - user input interpreted as encounter type in English or local language
    900          - data returned is pk of corresponding encounter type or None
    901          """
    902 -        def __init__(self, *args, **kwargs):
    903
    904                  gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
    905
    906                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    907                          queries = [
    908  """
    909  SELECT
    910          data,
    911          field_label,
    912          list_label
    913  FROM (
    914          SELECT DISTINCT ON (data) *
    915          FROM (
    916                  SELECT
    917                          pk AS data,
    918                          _(description) AS field_label,
    919                          case
    920                                  when _(description) = description then _(description)
    921                                  else _(description) || ' (' || description || ')'
    922                          end AS list_label
    923                  FROM
    924                          clin.encounter_type
    925                  WHERE
    926                          _(description) %(fragment_condition)s
    927                                  OR
    928                          description %(fragment_condition)s
    929          ) AS q_distinct_pk
    930  ) AS q_ordered
    931  ORDER BY
    932          list_label
    933  """                     ]
    934                  )
    935                  mp.setThresholds(2, 4, 6)
    936
    937                  self.matcher = mp
    938                  self.selection_only = True
    939                  self.picklist_delay = 50
    940
    941  #================================================================
    942  # main
    943  #----------------------------------------------------------------
    944  if __name__ == '__main__':
    945
    946          if len(sys.argv) < 2:
    947                  sys.exit()
    948
    949          if sys.argv[1] != 'test':
    950                  sys.exit()
    951
    952          from Gnumed.pycommon import gmI18N
    953          gmI18N.activate_locale()
    954          gmI18N.install_domain()
    955
    956          #----------------------------------------------------------------
    957 -        def test_encounter_edit_area_panel():
    958                  app = wx.PyWidgetTester(size = (200, 300))
    959                  emr = pat.emr
    960                  enc = emr.active_encounter
    961                  #enc = gmEMRStructItems.cEncounter(1)
    962                  pnl = cEncounterEditAreaPnl(app.frame, -1, encounter=enc)
    963                  app.frame.Show(True)
    964                  app.MainLoop()
    965                  return
    966          #----------------------------------------------------------------
    967 -        def test_encounter_edit_area_dialog():
    968                  app = wx.PyWidgetTester(size = (200, 300))
    969                  emr = pat.emr
    970                  enc = emr.active_encounter
    971                  #enc = gmEMRStructItems.cEncounter(1)
    972
    973                  dlg = cEncounterEditAreaDlg(parent=app.frame, id=-1, size = (400,400), encounter=enc)
    974                  dlg.ShowModal()
    975
    976  #               pnl = cEncounterEditAreaDlg(app.frame, -1, encounter=enc)
    977  #               app.frame.Show(True)
    978  #               app.MainLoop()
    979
    980          #----------------------------------------------------------------
    981          #test_encounter_edit_area_panel()
    982          #test_encounter_edit_area_dialog()
    983