Gnumed.wxpython.gmVaccWidgets

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

# Source Code for Module Gnumed.wxpython.gmVaccWidgets

       1  """GNUmed immunisation/vaccination widgets.
       2
       3  Modelled after Richard Terry's design document.
       4
       5  copyright: authors
       6  """
       7  #======================================================================
       8  __author__ = "R.Terry, S.J.Tan, K.Hilbert"
       9  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
      10
      11  import sys
      12  import logging
      13
      14
      15  import wx
      16
      17
      18  if __name__ == '__main__':
      19          sys.path.insert(0, '../../')
      20  from Gnumed.pycommon import gmDispatcher
      21  from Gnumed.pycommon import gmMatchProvider
      22  from Gnumed.pycommon import gmTools
      23  from Gnumed.pycommon import gmI18N
      24  from Gnumed.pycommon import gmCfg
      25  from Gnumed.pycommon import gmCfg2
      26  from Gnumed.pycommon import gmDateTime
      27  from Gnumed.pycommon import gmNetworkTools
      28  from Gnumed.pycommon import gmPrinting
      29  from Gnumed.pycommon import gmPG2
      30
      31  from Gnumed.business import gmPerson
      32  from Gnumed.business import gmVaccination
      33  from Gnumed.business import gmPraxis
      34  from Gnumed.business import gmProviderInbox
      35
      36  from Gnumed.wxpython import gmPhraseWheel
      37  from Gnumed.wxpython import gmTerryGuiParts
      38  from Gnumed.wxpython import gmRegetMixin
      39  from Gnumed.wxpython import gmGuiHelpers
      40  from Gnumed.wxpython import gmEditArea
      41  from Gnumed.wxpython import gmListWidgets
      42  from Gnumed.wxpython import gmFormWidgets
      43  from Gnumed.wxpython import gmMacro
      44  from Gnumed.wxpython import gmAuthWidgets
      45  from Gnumed.wxpython import gmSubstanceMgmtWidgets
      46
      47
      48  _log = logging.getLogger('gm.vacc')
      49
      50  #======================================================================
      51  # vaccine related widgets
      52  #----------------------------------------------------------------------
      53 -def regenerate_generic_vaccines():
      54
      55          dbo_conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Regenerating generic vaccines'))
      56          if dbo_conn is None:
      57                  return False
      58
      59          wx.BeginBusyCursor()
      60          _cfg = gmCfg2.gmCfgData()
      61          sql_script = gmVaccination.write_generic_vaccine_sql (
      62                  'client-%s' % _cfg.get(option = 'client_version'),
      63                  include_indications_mapping = False
      64          )
      65          _log.debug('regenerating generic vaccines, SQL script: %s', sql_script)
      66          if not gmPG2.run_sql_script(sql_script, conn = dbo_conn):
      67                  wx.EndBusyCursor()
      68                  gmGuiHelpers.gm_show_warning (
      69                          aMessage = _('Error regenerating generic vaccines.\n\nSee [%s]') % sql_script,
      70                          aTitle = _('Regenerating generic vaccines')
      71                  )
      72                  return False
      73
      74          gmDispatcher.send(signal = 'statustext', msg = _('Successfully regenerated generic vaccines ...'), beep = False)
      75          wx.EndBusyCursor()
      76          return True
      77
      78  #----------------------------------------------------------------------
      79 -def edit_vaccine(parent=None, vaccine=None, single_entry=True):
      80          ea = cVaccineEAPnl(parent, -1)
      81          ea.data = vaccine
      82          ea.mode = gmTools.coalesce(vaccine, 'new', 'edit')
      83          dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
      84          dlg.SetTitle(gmTools.coalesce(vaccine, _('Adding new vaccine'), _('Editing vaccine')))
      85          if dlg.ShowModal() == wx.ID_OK:
      86                  dlg.DestroyLater()
      87                  return True
      88          dlg.DestroyLater()
      89          return False
      90
      91  #----------------------------------------------------------------------
      92 -def manage_vaccines(parent=None):
      93
      94          if parent is None:
      95                  parent = wx.GetApp().GetTopWindow()
      96
      97  #       #------------------------------------------------------------
      98  #       def delete(vaccine=None):
      99  #               product = vaccine.product
     100  #               deleted = gmVaccination.delete_vaccine(vaccine = vaccine['pk_vaccine'])
     101  #               if not deleted:
     102  #                       gmGuiHelpers.gm_show_info (
     103  #                               _(      'Cannot delete vaccine\n'
     104  #                                       '\n'
     105  #                                       ' %s - %s (#%s)\n'
     106  #                                       '\n'
     107  #                                       'It is probably documented in a vaccination.'
     108  #                               ) % (
     109  #                                       vaccine['vaccine'],
     110  #                                       vaccine['l10n_preparation'],
     111  #                                       vaccine['pk_vaccine']
     112  #                               ),
     113  #                               _('Deleting vaccine')
     114  #                       )
     115  #                       return False
     116  #               delete_product = gmGuiHelpers.gm_show_question (
     117  #                       title = _('Deleting vaccine'),
     118  #                       question = _(
     119  #                               u'Fully delete the vaccine (including the associated drug product) ?\n'
     120  #                               u'\n'
     121  #                               u' "%s" (%s)'
     122  #                       ) % (product['product'], product['l10n_preparation'])
     123  #               )
     124  #               if delete_product:
     125  #                       pass
     126  #               return True
     127
     128          #------------------------------------------------------------
     129          def manage_drug_products(vaccine):
     130                  gmSubstanceMgmtWidgets.manage_drug_products(parent = parent)
     131                  return True
     132
     133          #------------------------------------------------------------
     134          def edit(vaccine=None):
     135                  return edit_vaccine(parent = parent, vaccine = vaccine, single_entry = True)
     136
     137          #------------------------------------------------------------
     138          def get_tooltip(vaccine):
     139                  if vaccine is None:
     140                          return None
     141                  return '\n'.join(vaccine.format())
     142
     143          #------------------------------------------------------------
     144          def refresh(lctrl):
     145                  vaccines = gmVaccination.get_vaccines(order_by = 'vaccine')
     146
     147                  items = [ [
     148                          '%s' % v['pk_drug_product'],
     149                          '%s%s' % (
     150                                  v['vaccine'],
     151                                  gmTools.bool2subst (
     152                                          v['is_fake_vaccine'],
     153                                          ' (%s)' % _('fake'),
     154                                          ''
     155                                  )
     156                          ),
     157                          v['l10n_preparation'],
     158                          gmTools.coalesce(v['atc_code'], ''),
     159                          '%s - %s' % (
     160                                  gmTools.coalesce(v['min_age'], '?'),
     161                                  gmTools.coalesce(v['max_age'], '?'),
     162                          ),
     163                          gmTools.coalesce(v['comment'], '')
     164                  ] for v in vaccines ]
     165                  lctrl.set_string_items(items)
     166                  lctrl.set_data(vaccines)
     167
     168          #------------------------------------------------------------
     169          gmListWidgets.get_choices_from_list (
     170                  parent = parent,
     171                  caption = _('Showing vaccine details'),
     172                  columns = [ '#', _('Vaccine'), _('Preparation'), _('ATC'), _('Age range'), _('Comment') ],
     173                  single_selection = True,
     174                  refresh_callback = refresh,
     175                  edit_callback = edit,
     176                  new_callback = edit,
     177                  #delete_callback = delete,
     178                  list_tooltip_callback = get_tooltip,
     179                  left_extra_button = (_('Products'), _('Manage drug products'), manage_drug_products)
     180          )
     181
     182  #----------------------------------------------------------------------
     183 -class cBatchNoPhraseWheel(gmPhraseWheel.cPhraseWheel):
     184
     185 -        def __init__(self, *args, **kwargs):
     186
     187                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     188
     189                  context = {
     190                          'ctxt_vaccine': {
     191                                  'where_part': 'AND pk_vaccine = %(pk_vaccine)s',
     192                                  'placeholder': 'pk_vaccine'
     193                          }
     194                  }
     195
     196                  query = """
     197  SELECT data, field_label, list_label FROM (
     198
     199          SELECT distinct on (field_label)
     200                  data,
     201                  field_label,
     202                  list_label,
     203                  rank
     204          FROM ((
     205                          -- batch_no by vaccine
     206                          SELECT
     207                                  batch_no AS data,
     208                                  batch_no AS field_label,
     209                                  batch_no || ' (' || vaccine || ')' AS list_label,
     210                                  1 as rank
     211                          FROM
     212                                  clin.v_vaccinations
     213                          WHERE
     214                                  batch_no %(fragment_condition)s
     215                                  %(ctxt_vaccine)s
     216                  ) UNION ALL (
     217                          -- batch_no for any vaccine
     218                          SELECT
     219                                  batch_no AS data,
     220                                  batch_no AS field_label,
     221                                  batch_no || ' (' || vaccine || ')' AS list_label,
     222                                  2 AS rank
     223                          FROM
     224                                  clin.v_vaccinations
     225                          WHERE
     226                                  batch_no %(fragment_condition)s
     227                  )
     228
     229          ) AS matching_batch_nos
     230
     231  ) as unique_matches
     232
     233  ORDER BY rank, list_label
     234  LIMIT 25
     235  """
     236                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = context)
     237                  mp.setThresholds(1, 2, 3)
     238                  self.matcher = mp
     239
     240                  self.unset_context(context = 'pk_vaccine')
     241                  self.SetToolTip(_('Enter or select the batch/lot number of the vaccine used.'))
     242                  self.selection_only = False
     243
     244  #----------------------------------------------------------------------
     245 -class cVaccinePhraseWheel(gmPhraseWheel.cPhraseWheel):
     246
     247 -        def __init__(self, *args, **kwargs):
     248
     249                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     250
     251                  # consider ATCs in ref.drug_product and ref.vacc_indication
     252                  query = """
     253  SELECT data, list_label, field_label FROM (
     254
     255          SELECT DISTINCT ON (data)
     256                  data,
     257                  list_label,
     258                  field_label
     259          FROM ((
     260                          -- fragment -> vaccine
     261                          SELECT
     262                                  r_v_v.pk_vaccine
     263                                          AS data,
     264                                  r_v_v.vaccine || ' ('
     265                                  ||      (SELECT string_agg(l10n_inds.ind_desc::text, ', ') FROM (
     266                                                  SELECT unnest(r_v_v.indications)->>'l10n_indication' AS ind_desc
     267                                          ) AS l10n_inds)
     268                                  || ')'
     269                                          AS list_label,
     270                                  r_v_v.vaccine
     271                                          AS field_label
     272                          FROM
     273                                  ref.v_vaccines r_v_v
     274                          WHERE
     275                                  r_v_v.vaccine %(fragment_condition)s
     276
     277                  ) union all (
     278
     279                          -- fragment -> localized indication -> vaccines
     280                          SELECT
     281                                   r_vi4v.pk_vaccine
     282                                          AS data,
     283                                  r_vi4v.vaccine || ' ('
     284                                  ||      (SELECT string_agg(l10n_inds.ind_desc::text, ', ') FROM (
     285                                                  SELECT unnest(r_vi4v.indications)->>'l10n_indication' AS ind_desc
     286                                          ) AS l10n_inds)
     287                                  || ')'
     288                                          AS list_label,
     289                                  r_vi4v.vaccine
     290                                          AS field_label
     291                          FROM
     292                                  ref.v_indications4vaccine r_vi4v
     293                          WHERE
     294                                   r_vi4v.l10n_indication %(fragment_condition)s
     295
     296                  ) union all (
     297
     298                          -- fragment -> indication -> vaccines
     299                          SELECT
     300                                  r_vi4v.pk_vaccine
     301                                          AS data,
     302                                  r_vi4v.vaccine || ' ('
     303                                  ||      (SELECT string_agg(l10n_inds.ind_desc::text, ', ') FROM (
     304                                                  SELECT unnest(r_vi4v.indications)->>'l10n_indication' AS ind_desc
     305                                          ) AS l10n_inds)
     306                                  || ')'
     307                                          AS list_label,
     308                                  r_vi4v.vaccine
     309                                          AS field_label
     310                          FROM
     311                                  ref.v_indications4vaccine r_vi4v
     312                          WHERE
     313                                  r_vi4v.indication %(fragment_condition)s
     314                  )
     315          ) AS distinct_total
     316
     317  ) AS total
     318
     319  ORDER by list_label
     320  LIMIT 25
     321  """
     322                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
     323                  mp.setThresholds(1, 2, 3)
     324                  self.matcher = mp
     325
     326                  self.selection_only = True
     327          #------------------------------------------------------------------
     328 -        def _data2instance(self):
     329                  return gmVaccination.cVaccine(aPK_obj = self.GetData())
     330
     331  #----------------------------------------------------------------------
     332  from Gnumed.wxGladeWidgets import wxgVaccineEAPnl
     333
     334 -class cVaccineEAPnl(wxgVaccineEAPnl.wxgVaccineEAPnl, gmEditArea.cGenericEditAreaMixin):
     335
     336 -        def __init__(self, *args, **kwargs):
     337                  try:
     338                          data = kwargs['vaccine']
     339                          del kwargs['vaccine']
     340                  except KeyError:
     341                          data = None
     342
     343                  wxgVaccineEAPnl.wxgVaccineEAPnl.__init__(self, *args, **kwargs)
     344                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     345
     346                  self.mode = 'new'
     347                  self.data = data
     348                  if data is not None:
     349                          self.mode = 'edit'
     350
     351                  self.__indications = None
     352
     353          #----------------------------------------------------------------
     354 -        def __refresh_indications(self):
     355                  self._TCTRL_indications.SetValue('')
     356                  if self.data is None:
     357                          return
     358                  self._TCTRL_indications.SetValue('- ' + '\n- '.join([ i['l10n_indication'] for i in self.data['indications'] ]))
     359
     360          #----------------------------------------------------------------
     361          # generic Edit Area mixin API
     362          #----------------------------------------------------------------
     363 -        def _valid_for_save(self):
     364
     365                  has_errors = False
     366
     367                  if self._PRW_drug_product.GetValue().strip() == '':
     368                          has_errors = True
     369                          self._PRW_drug_product.display_as_valid(False)
     370                  else:
     371                          self._PRW_drug_product.display_as_valid(True)
     372
     373                  atc = self._PRW_atc.GetValue().strip()
     374                  if (atc == '') or (atc.startswith('J07')):
     375                          self._PRW_atc.display_as_valid(True)
     376                  else:
     377                          if self._PRW_atc.GetData() is None:
     378                                  self._PRW_atc.display_as_valid(True)
     379                          else:
     380                                  has_errors = True
     381                                  self._PRW_atc.display_as_valid(False)
     382
     383                  val = self._PRW_age_min.GetValue().strip()
     384                  if val == '':
     385                          self._PRW_age_min.display_as_valid(True)
     386                  else:
     387                          if gmDateTime.str2interval(val) is None:
     388                                  has_errors = True
     389                                  self._PRW_age_min.display_as_valid(False)
     390                          else:
     391                                  self._PRW_age_min.display_as_valid(True)
     392
     393                  val = self._PRW_age_max.GetValue().strip()
     394                  if val == '':
     395                          self._PRW_age_max.display_as_valid(True)
     396                  else:
     397                          if gmDateTime.str2interval(val) is None:
     398                                  has_errors = True
     399                                  self._PRW_age_max.display_as_valid(False)
     400                          else:
     401                                  self._PRW_age_max.display_as_valid(True)
     402
     403                  # complex conditions
     404                  # are we editing ?
     405                  if self.mode == 'edit':
     406                          change_of_product = self.data['pk_drug_product'] != self._PRW_drug_product.GetData()
     407                          if change_of_product and self.data.is_in_use:
     408                                  do_it = gmGuiHelpers.gm_show_question (
     409                                          aTitle = _('Saving vaccine'),
     410                                          aMessage = _(
     411                                                  'This vaccine is already in use:\n'
     412                                                  '\n'
     413                                                  ' "%s"\n'
     414                                                  '\n'
     415                                                  'Are you absolutely positively sure that\n'
     416                                                  'you really want to edit this vaccine ?\n'
     417                                                  '\n'
     418                                                  'This will change the vaccine name and/or target\n'
     419                                                  'conditions in each patient this vaccine was\n'
     420                                                  'used in to document a vaccination with.\n'
     421                                          ) % self._PRW_drug_product.GetValue().strip()
     422                                  )
     423                                  if not do_it:
     424                                          has_errors = True
     425                  else:
     426                          if self._PRW_drug_product.GetData() is None:
     427                                  # need to ask for indications ?
     428                                  if self._PRW_drug_product.GetValue().strip() != '':
     429                                          self.__indications = gmSubstanceMgmtWidgets.manage_substance_doses(vaccine_indications_only = True)
     430                                          if self.__indications is None:
     431                                                  has_errors = True
     432                          else:
     433                                  # existing drug product selected
     434                                  pass
     435
     436                  return (has_errors is False)
     437
     438          #----------------------------------------------------------------
     439 -        def _save_as_new(self):
     440
     441                  # save the data as a new instance
     442                  vaccine = gmVaccination.create_vaccine (
     443                          pk_drug_product = self._PRW_drug_product.GetData(),
     444                          product_name = self._PRW_drug_product.GetValue().strip(),
     445                          indications = self.__indications,
     446                          is_live = self._CHBOX_live.GetValue()
     447                  )
     448                  val = self._PRW_age_min.GetValue().strip()
     449                  if val != '':
     450                          vaccine['min_age'] = gmDateTime.str2interval(val)
     451                  val = self._PRW_age_max.GetValue().strip()
     452                  if val != '':
     453                          vaccine['max_age'] = gmDateTime.str2interval(val)
     454                  val = self._TCTRL_comment.GetValue().strip()
     455                  if val != '':
     456                          vaccine['comment'] = val
     457                  vaccine.save()
     458
     459                  drug = vaccine.product
     460                  drug['is_fake_product'] = self._CHBOX_fake.GetValue()
     461                  val = self._PRW_atc.GetData()
     462                  if val is not None:
     463                          if val != 'J07':
     464                                  drug['atc'] = val.strip()
     465                  drug.save()
     466
     467                  # must be done very late or else the property access
     468                  # will refresh the display such that later field
     469                  # access will return empty values
     470                  self.data = vaccine
     471
     472                  return True
     473
     474          #----------------------------------------------------------------
     475 -        def _save_as_update(self):
     476
     477                  drug = self.data.product
     478                  drug['product'] = self._PRW_drug_product.GetValue().strip()
     479                  drug['is_fake_product'] = self._CHBOX_fake.GetValue()
     480                  val = self._PRW_atc.GetData()
     481                  if val is not None:
     482                          if val != 'J07':
     483                                  drug['atc'] = val.strip()
     484                  drug.save()
     485
     486                  self.data['is_live'] = self._CHBOX_live.GetValue()
     487                  val = self._PRW_age_min.GetValue().strip()
     488                  if val != '':
     489                          self.data['min_age'] = gmDateTime.str2interval(val)
     490                  if val != '':
     491                          self.data['max_age'] = gmDateTime.str2interval(val)
     492                  val = self._TCTRL_comment.GetValue().strip()
     493                  if val != '':
     494                          self.data['comment'] = val
     495                  self.data.save()
     496
     497                  return True
     498
     499          #----------------------------------------------------------------
     500 -        def _refresh_as_new(self):
     501                  self._PRW_drug_product.SetText(value = '', data = None, suppress_smarts = True)
     502                  self._CHBOX_live.SetValue(False)
     503                  self._CHBOX_fake.SetValue(False)
     504                  self._PRW_atc.SetText(value = '', data = None, suppress_smarts = True)
     505                  self._PRW_age_min.SetText(value = '', data = None, suppress_smarts = True)
     506                  self._PRW_age_max.SetText(value = '', data = None, suppress_smarts = True)
     507                  self._TCTRL_comment.SetValue('')
     508
     509                  self.__refresh_indications()
     510
     511                  self._PRW_drug_product.SetFocus()
     512
     513          #----------------------------------------------------------------
     514 -        def _refresh_from_existing(self):
     515                  self._PRW_drug_product.SetText(value = self.data['vaccine'], data = self.data['pk_drug_product'])
     516                  self._CHBOX_live.SetValue(self.data['is_live'])
     517                  self._CHBOX_fake.SetValue(self.data['is_fake_vaccine'])
     518                  self._PRW_atc.SetText(value = self.data['atc_code'], data = self.data['atc_code'])
     519                  if self.data['min_age'] is None:
     520                          self._PRW_age_min.SetText(value = '', data = None, suppress_smarts = True)
     521                  else:
     522                          self._PRW_age_min.SetText (
     523                                  value = gmDateTime.format_interval(self.data['min_age'], gmDateTime.acc_years),
     524                                  data = self.data['min_age']
     525                          )
     526                  if self.data['max_age'] is None:
     527                          self._PRW_age_max.SetText(value = '', data = None, suppress_smarts = True)
     528                  else:
     529                          self._PRW_age_max.SetText (
     530                                  value = gmDateTime.format_interval(self.data['max_age'], gmDateTime.acc_years),
     531                                  data = self.data['max_age']
     532                          )
     533                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
     534
     535                  self.__refresh_indications()
     536
     537                  self._PRW_drug_product.SetFocus()
     538
     539          #----------------------------------------------------------------
     540 -        def _refresh_as_new_from_existing(self):
     541                  self._refresh_as_new()
     542
     543  #======================================================================
     544  # vaccination related widgets
     545  #----------------------------------------------------------------------
     546 -def configure_adr_url():
     547
     548          def is_valid(value):
     549                  value = value.strip()
     550                  if value == '':
     551                          return True, gmVaccination.URL_vaccine_adr_german_default
     552                  try:
     553                          urllib.request.urlopen(value)
     554                          return True, value
     555                  except Exception:                       # FIXME: more specific
     556                          return True, value
     557
     558          gmCfgWidgets.configure_string_option (
     559                  message = _(
     560                          'GNUmed will use this URL to access a website which lets\n'
     561                          'you report an adverse vaccination reaction (vADR).\n'
     562                          '\n'
     563                          'If you set it to a specific address that URL must be\n'
     564                          'accessible now. If you leave it empty it will fall back\n'
     565                          'to the URL for reporting other adverse drug reactions.'
     566                  ),
     567                  option = 'external.urls.report_vaccine_ADR',
     568                  bias = 'user',
     569                  default_value = gmVaccination.URL_vaccine_adr_german_default,
     570                  validator = is_valid
     571          )
     572
     573  #----------------------------------------------------------------------
     574 -def configure_vaccination_plans_url():
     575
     576          def is_valid(value):
     577                  value = value.strip()
     578                  if value == '':
     579                          return True, gmVaccination.URL_vaccination_plan
     580                  try:
     581                          urllib.request.urlopen(value)
     582                          return True, value
     583                  except Exception:                       # FIXME: more specific
     584                          return True, value
     585
     586          gmCfgWidgets.configure_string_option (
     587                  message = _(
     588                          'GNUmed will use this URL to access a page showing\n'
     589                          'vaccination schedules.\n'
     590                          '\n'
     591                          'You can leave this empty but to set it to a specific\n'
     592                          'address the URL must be accessible now.'
     593                  ),
     594                  option = 'external.urls.vaccination_plans',
     595                  bias = 'user',
     596                  default_value = gmVaccination.URL_vaccination_plan,
     597                  validator = is_valid
     598          )
     599
     600  #----------------------------------------------------------------------
     601 -def print_vaccinations(parent=None):
     602
     603          if parent is None:
     604                  parent = wx.GetApp().GetTopWindow()
     605
     606          vaccs_printout = gmFormWidgets.generate_form_from_template (
     607                  parent = parent,
     608                  template_types = [
     609                          'Medical statement',
     610                          'vaccination report',
     611                          'vaccination record',
     612                          'reminder'
     613                  ],
     614                  edit = False
     615          )
     616
     617          if vaccs_printout is None:
     618                  return False
     619
     620          return gmFormWidgets.act_on_generated_forms (
     621                  parent = parent,
     622                  forms = [vaccs_printout],
     623                  jobtype = 'vaccinations',
     624                  episode_name = 'administrative',
     625                  review_copy_as_normal = True
     626          )
     627
     628  #----------------------------------------------------------------------
     629 -def edit_vaccination(parent=None, vaccination=None, single_entry=True):
     630          ea = cVaccinationEAPnl(parent, -1)
     631          ea.data = vaccination
     632          ea.mode = gmTools.coalesce(vaccination, 'new', 'edit')
     633          dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
     634          dlg.SetTitle(gmTools.coalesce(vaccination, _('Adding new vaccinations'), _('Editing vaccination')))
     635          if dlg.ShowModal() == wx.ID_OK:
     636                  dlg.DestroyLater()
     637                  return True
     638          dlg.DestroyLater()
     639          if not single_entry:
     640                  return True
     641          return False
     642
     643  #----------------------------------------------------------------------
     644 -def manage_vaccinations(parent=None, latest_only=False, expand_indications=False):
     645
     646          pat = gmPerson.gmCurrentPatient()
     647          emr = pat.emr
     648
     649          if parent is None:
     650                  parent = wx.GetApp().GetTopWindow()
     651
     652          #------------------------------------------------------------
     653          def browse2schedules(vaccination=None):
     654                  dbcfg = gmCfg.cCfgSQL()
     655                  url = dbcfg.get2 (
     656                          option = 'external.urls.vaccination_plans',
     657                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     658                          bias = 'user',
     659                          default = gmVaccination.URL_vaccination_plan
     660                  )
     661
     662                  gmNetworkTools.open_url_in_browser(url = url)
     663                  return False
     664
     665          #------------------------------------------------------------
     666          def print_vaccs(vaccination=None):
     667                  print_vaccinations(parent = parent)
     668                  return False
     669
     670          #------------------------------------------------------------
     671          def add_recall(vaccination=None):
     672                  if vaccination is None:
     673                          subject = _('vaccination recall')
     674                  else:
     675                          subject = _('vaccination recall (%s)') % vaccination['vaccine']
     676
     677                  recall = gmProviderInbox.create_inbox_message (
     678                          message_type = _('Vaccination'),
     679                          subject = subject,
     680                          patient = pat.ID,
     681                          staff = None
     682                  )
     683
     684                  if vaccination is not None:
     685                          recall['data'] = _('Existing vaccination:\n\n%s') % '\n'.join(vaccination.format(
     686                                  with_indications = True,
     687                                  with_comment = True,
     688                                  with_reaction = False,
     689                                  date_format = '%Y %b %d'
     690                          ))
     691                          recall.save()
     692
     693                  from Gnumed.wxpython import gmProviderInboxWidgets
     694                  gmProviderInboxWidgets.edit_inbox_message (
     695                          parent = parent,
     696                          message = recall,
     697                          single_entry = False
     698                  )
     699
     700                  return False
     701
     702          #------------------------------------------------------------
     703          def get_tooltip(vaccination):
     704                  if vaccination is None:
     705                          return None
     706                  return '\n'.join(vaccination.format (
     707                          with_indications = True,
     708                          with_comment = True,
     709                          with_reaction = True,
     710                          date_format = '%Y %b %d'
     711                  ))
     712
     713          #------------------------------------------------------------
     714          def edit(vaccination=None):
     715                  return edit_vaccination(parent = parent, vaccination = vaccination, single_entry = (vaccination is not None))
     716
     717          #------------------------------------------------------------
     718          def delete(vaccination=None):
     719                  gmVaccination.delete_vaccination(vaccination = vaccination['pk_vaccination'])
     720                  return True
     721
     722          #------------------------------------------------------------
     723          def refresh(lctrl):
     724
     725                  items = []
     726                  data = []
     727                  if latest_only:
     728                          latest_vaccs = emr.get_latest_vaccinations()
     729                          for indication in sorted(latest_vaccs):
     730                                  no_of_shots4ind, latest_vacc4ind = latest_vaccs[indication]
     731                                  items.append ([
     732                                          indication,
     733                                          _('%s (latest of %s: %s ago)') % (
     734                                                  gmDateTime.pydt_strftime(latest_vacc4ind['date_given'], format = '%Y %b'),
     735                                                  no_of_shots4ind,
     736                                                  gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - latest_vacc4ind['date_given'])
     737                                          ),
     738                                          latest_vacc4ind['vaccine'],
     739                                          latest_vacc4ind['batch_no'],
     740                                          gmTools.coalesce(latest_vacc4ind['site'], ''),
     741                                          gmTools.coalesce(latest_vacc4ind['reaction'], ''),
     742                                          gmTools.coalesce(latest_vacc4ind['comment'], '')
     743                                  ])
     744                                  data.append(latest_vacc4ind)
     745                  else:
     746                          shots = emr.get_vaccinations(order_by = 'date_given DESC, pk_vaccination')
     747                          if expand_indications:
     748                                  shots_by_ind = {}
     749                                  for shot in shots:
     750                                          for ind in shot['indications']:
     751                                                  try:
     752                                                          shots_by_ind[ind['l10n_indication']].append(shot)
     753                                                  except KeyError:
     754                                                          shots_by_ind[ind['l10n_indication']] = [shot]
     755                                  for ind in sorted(shots_by_ind):
     756                                          idx = len(shots_by_ind[ind])
     757                                          for shot in shots_by_ind[ind]:
     758                                                  items.append ([
     759                                                          '%s (#%s)' % (ind, idx),
     760                                                          _('%s (%s ago)') % (
     761                                                                  gmDateTime.pydt_strftime(shot['date_given'], '%Y %b %d'),
     762                                                                  gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - shot['date_given'])
     763                                                          ),
     764                                                          shot['vaccine'],
     765                                                          shot['batch_no'],
     766                                                          gmTools.coalesce(shot['site'], ''),
     767                                                          gmTools.coalesce(shot['reaction'], ''),
     768                                                          gmTools.coalesce(shot['comment'], '')
     769                                                  ])
     770                                                  idx -= 1
     771                                                  data.append(shot)
     772                          else:
     773                                  items = [ [
     774                                          gmDateTime.pydt_strftime(s['date_given'], '%Y %b %d'),
     775                                          s['vaccine'],
     776                                          ', '.join([ i['l10n_indication'] for i in s['indications'] ]),
     777                                          s['batch_no'],
     778                                          gmTools.coalesce(s['site'], ''),
     779                                          gmTools.coalesce(s['reaction'], ''),
     780                                          gmTools.coalesce(s['comment'], '')
     781                                  ] for s in shots ]
     782                                  data = shots
     783
     784                  lctrl.set_string_items(items)
     785                  lctrl.set_data(data)
     786
     787          #------------------------------------------------------------
     788          if latest_only:
     789                  msg = _('Most recent vaccination for each indication.\n')
     790                  cols = [ _('Indication'), _('Date'), _('Vaccine'), _('Batch'), _('Site'), _('Reaction'), _('Comment') ]
     791          else:
     792                  if expand_indications:
     793                          msg = _('Complete vaccination history (per indication).\n')
     794                          cols = [ _('Indication'), _('Date'), _('Vaccine'), _('Batch'), _('Site'), _('Reaction'), _('Comment') ]
     795                  else:
     796                          msg = _('Complete vaccination history (by shot).\n')
     797                          cols = [ _('Date'), _('Vaccine'), _('Intended to protect from'), _('Batch'), _('Site'), _('Reaction'), _('Comment') ]
     798
     799          gmListWidgets.get_choices_from_list (
     800                  parent = parent,
     801                  msg = msg,
     802                  caption = _('Showing vaccinations.'),
     803                  columns = cols,
     804                  single_selection = True,
     805                  refresh_callback = refresh,
     806                  new_callback = edit,
     807                  edit_callback = edit,
     808                  delete_callback = delete,
     809                  list_tooltip_callback = get_tooltip,
     810                  left_extra_button = (_('Print'), _('Print vaccinations or recalls.'), print_vaccs),
     811                  middle_extra_button = (_('Recall'), _('Add a recall for a vaccination'), add_recall),
     812                  right_extra_button = (_('Vx schedules'), _('Open a browser showing vaccination schedules.'), browse2schedules)
     813          )
     814
     815  #----------------------------------------------------------------------
     816  from Gnumed.wxGladeWidgets import wxgVaccinationEAPnl
     817
     818 -class cVaccinationEAPnl(wxgVaccinationEAPnl.wxgVaccinationEAPnl, gmEditArea.cGenericEditAreaMixin):
     819          """
     820          - warn on apparent duplicates
     821          - ask if "missing" (= previous, non-recorded) vaccinations
     822            should be estimated and saved (add note "auto-generated")
     823
     824          Batch No (http://www.fao.org/docrep/003/v9952E12.htm)
     825          """
     826 -        def __init__(self, *args, **kwargs):
     827
     828                  try:
     829                          data = kwargs['vaccination']
     830                          del kwargs['vaccination']
     831                  except KeyError:
     832                          data = None
     833
     834                  wxgVaccinationEAPnl.wxgVaccinationEAPnl.__init__(self, *args, **kwargs)
     835                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     836
     837                  self.mode = 'new'
     838                  self.data = data
     839                  if data is not None:
     840                          self.mode = 'edit'
     841
     842                  self.__init_ui()
     843
     844          #----------------------------------------------------------------
     845 -        def __init_ui(self):
     846                  # adjust phrasewheels etc
     847                  self._PRW_vaccine.add_callback_on_lose_focus(self._on_PRW_vaccine_lost_focus)
     848                  self._PRW_provider.selection_only = False
     849                  self._PRW_reaction.add_callback_on_lose_focus(self._on_PRW_reaction_lost_focus)
     850
     851          #----------------------------------------------------------------
     852 -        def _on_PRW_vaccine_lost_focus(self):
     853
     854                  vaccine = self._PRW_vaccine.GetData(as_instance=True)
     855
     856                  if self.mode == 'edit':
     857                          if vaccine is None:
     858                                  self._PRW_batch.unset_context(context = 'pk_vaccine')
     859                          else:
     860                                  self._PRW_batch.set_context(context = 'pk_vaccine', val = vaccine['pk_vaccine'])
     861                  # we are entering a new vaccination
     862                  else:
     863                          if vaccine is None:
     864                                  self._PRW_batch.unset_context(context = 'pk_vaccine')
     865                          else:
     866                                  self._PRW_batch.set_context(context = 'pk_vaccine', val = vaccine['pk_vaccine'])
     867
     868                  self.__refresh_indications()
     869
     870          #----------------------------------------------------------------
     871 -        def _on_PRW_reaction_lost_focus(self):
     872                  if self._PRW_reaction.GetValue().strip() == '':
     873                          self._BTN_report.Enable(False)
     874                  else:
     875                          self._BTN_report.Enable(True)
     876
     877          #----------------------------------------------------------------
     878 -        def __refresh_indications(self):
     879                  self._TCTRL_indications.SetValue('')
     880                  vaccine = self._PRW_vaccine.GetData(as_instance = True)
     881                  if vaccine is None:
     882                          return
     883                  lines = []
     884                  emr = gmPerson.gmCurrentPatient().emr
     885                  latest_vaccs = emr.get_latest_vaccinations (
     886                          atc_indications = [ i['atc_indication'] for i in vaccine['indications'] ]
     887                  )
     888                  for l10n_ind in [ i['l10n_indication'] for i in vaccine['indications'] ]:
     889                          try:
     890                                  no_of_shots4ind, latest_vacc4ind = latest_vaccs[l10n_ind]
     891                                  ago = gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - latest_vacc4ind['date_given'])
     892                                  lines.append(_('%s  (most recent shot of %s: %s ago)') % (l10n_ind, no_of_shots4ind, ago))
     893                          except KeyError:
     894                                  lines.append(_('%s  (no previous vaccination recorded)') % l10n_ind)
     895
     896                  self._TCTRL_indications.SetValue(_('Protects against:\n ') + '\n '.join(lines))
     897
     898          #----------------------------------------------------------------
     899          # generic Edit Area mixin API
     900          #----------------------------------------------------------------
     901 -        def _valid_for_save(self):
     902
     903                  has_errors = False
     904
     905                  if not self._PRW_date_given.is_valid_timestamp(empty_is_valid = False):
     906                          has_errors = True
     907
     908                  vaccine = self._PRW_vaccine.GetData(as_instance = True)
     909                  if vaccine is None:
     910                          has_errors = True
     911                          self._PRW_vaccine.display_as_valid(False)
     912                  else:
     913                          self._PRW_vaccine.display_as_valid(True)
     914
     915                  if self._PRW_batch.GetValue().strip() == '':
     916                          has_errors = True
     917                          self._PRW_batch.display_as_valid(False)
     918                  else:
     919                          self._PRW_batch.display_as_valid(True)
     920
     921                  if self._PRW_episode.GetValue().strip() == '':
     922                          self._PRW_episode.SetText(value = _('prevention'))
     923
     924                  return (has_errors is False)
     925
     926          #----------------------------------------------------------------
     927 -        def _save_as_new(self):
     928
     929                  vaccine = self._PRW_vaccine.GetData()
     930                  data = self.__save_new_from_vaccine(vaccine = vaccine)
     931
     932                  # must be done very late or else the property access
     933                  # will refresh the display such that later field
     934                  # access will return empty values
     935                  self.data = data
     936
     937                  return True
     938
     939          #----------------------------------------------------------------
     940 -        def __save_new_from_vaccine(self, vaccine=None):
     941
     942                  emr = gmPerson.gmCurrentPatient().emr
     943
     944                  data = emr.add_vaccination (
     945                          episode = self._PRW_episode.GetData(can_create = True, is_open = False),
     946                          vaccine = vaccine,
     947                          batch_no = self._PRW_batch.GetValue().strip()
     948                  )
     949
     950                  if self._CHBOX_anamnestic.GetValue() is True:
     951                          data['soap_cat'] = 's'
     952                  else:
     953                          data['soap_cat'] = 'p'
     954
     955                  data['date_given'] = self._PRW_date_given.GetData()
     956                  data['site'] = self._PRW_site.GetValue().strip()
     957                  data['pk_provider'] = self._PRW_provider.GetData()
     958                  data['reaction'] = self._PRW_reaction.GetValue().strip()
     959                  data['comment'] = self._TCTRL_comment.GetValue().strip()
     960
     961                  data.save()
     962
     963                  return data
     964
     965          #----------------------------------------------------------------
     966 -        def _save_as_update(self):
     967
     968                  if self._CHBOX_anamnestic.GetValue() is True:
     969                          self.data['soap_cat'] = 's'
     970                  else:
     971                          self.data['soap_cat'] = 'p'
     972
     973                  self.data['date_given'] = self._PRW_date_given.GetData()
     974                  self.data['pk_vaccine'] = self._PRW_vaccine.GetData()
     975                  self.data['batch_no'] = self._PRW_batch.GetValue().strip()
     976                  self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True, is_open = False)
     977                  self.data['site'] = self._PRW_site.GetValue().strip()
     978                  self.data['pk_provider'] = self._PRW_provider.GetData()
     979                  self.data['reaction'] = self._PRW_reaction.GetValue().strip()
     980                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
     981
     982                  self.data.save()
     983
     984                  return True
     985
     986          #----------------------------------------------------------------
     987 -        def _refresh_as_new(self):
     988                  self._PRW_date_given.SetText(data = gmDateTime.pydt_now_here())
     989                  self._CHBOX_anamnestic.SetValue(False)
     990                  self._PRW_vaccine.SetText(value = '', data = None, suppress_smarts = True)
     991                  self._PRW_batch.unset_context(context = 'pk_vaccine')
     992                  self._PRW_batch.SetValue('')
     993                  self._PRW_episode.SetText(value = '', data = None, suppress_smarts = True)
     994                  self._PRW_site.SetValue('')
     995                  self._PRW_provider.SetData(data = None)
     996                  self._PRW_reaction.SetText(value = '', data = None, suppress_smarts = True)
     997                  self._BTN_report.Enable(False)
     998                  self._TCTRL_comment.SetValue('')
     999
    1000                  self.__refresh_indications()
    1001
    1002                  self._PRW_date_given.SetFocus()
    1003
    1004          #----------------------------------------------------------------
    1005 -        def _refresh_from_existing(self):
    1006                  self._PRW_date_given.SetText(data = self.data['date_given'])
    1007                  if self.data['soap_cat'] == 's':
    1008                          self._CHBOX_anamnestic.SetValue(True)
    1009                  else:
    1010                          self._CHBOX_anamnestic.SetValue(False)
    1011                  self._PRW_vaccine.SetText(value = self.data['vaccine'], data = self.data['pk_vaccine'])
    1012
    1013                  self._PRW_batch.SetValue(self.data['batch_no'])
    1014                  self._PRW_episode.SetData(data = self.data['pk_episode'])
    1015                  self._PRW_site.SetValue(gmTools.coalesce(self.data['site'], ''))
    1016                  self._PRW_provider.SetData(self.data['pk_provider'])
    1017                  self._PRW_reaction.SetValue(gmTools.coalesce(self.data['reaction'], ''))
    1018                  if self.data['reaction'] is None:
    1019                          self._BTN_report.Enable(False)
    1020                  else:
    1021                          self._BTN_report.Enable(True)
    1022                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
    1023
    1024                  self.__refresh_indications()
    1025
    1026                  self._PRW_date_given.SetFocus()
    1027
    1028          #----------------------------------------------------------------
    1029 -        def _refresh_as_new_from_existing(self):
    1030                  self._PRW_date_given.SetText(data = self.data['date_given'])
    1031                  #self._CHBOX_anamnestic.SetValue(False)
    1032                  self._PRW_vaccine.SetText(value = self.data['vaccine'], data = self.data['pk_vaccine'])
    1033
    1034                  self._PRW_batch.set_context(context = 'pk_vaccine', val = self.data['pk_vaccine'])
    1035                  self._PRW_batch.SetValue('')
    1036
    1037                  self._PRW_episode.SetData(data = self.data['pk_episode'])
    1038                  self._PRW_site.SetValue(gmTools.coalesce(self.data['site'], ''))
    1039                  self._PRW_provider.SetData(self.data['pk_provider'])
    1040                  self._PRW_reaction.SetValue('')
    1041                  self._BTN_report.Enable(False)
    1042                  self._TCTRL_comment.SetValue('')
    1043
    1044                  self.__refresh_indications()
    1045
    1046                  self._PRW_date_given.SetFocus()
    1047
    1048          #----------------------------------------------------------------
    1049          # event handlers
    1050          #----------------------------------------------------------------
    1051 -        def _on_report_button_pressed(self, event):
    1052                  event.Skip()
    1053                  dbcfg = gmCfg.cCfgSQL()
    1054                  url = dbcfg.get2 (
    1055                          option = 'external.urls.report_vaccine_ADR',
    1056                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1057                          bias = 'user'
    1058                  )
    1059                  if url.strip() == '':
    1060                          url = dbcfg.get2 (
    1061                                  option = 'external.urls.report_ADR',
    1062                                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1063                                  bias = 'user'
    1064                          )
    1065                  gmNetworkTools.open_url_in_browser(url = url)
    1066
    1067          #----------------------------------------------------------------
    1068 -        def _on_add_vaccine_button_pressed(self, event):
    1069                  edit_vaccine(parent = self, vaccine = None, single_entry = False)
    1070                  # FIXME: could set newly generated vaccine here
    1071
    1072  #======================================================================
    1073  #======================================================================
    1074  #======================================================================
    1075  #======================================================================
    1076 -class cImmunisationsPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
    1077
    1078 -        def __init__(self, parent, id):
    1079                  wx.Panel.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize, wx.RAISED_BORDER)
    1080                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1081                  self.__pat = gmPerson.gmCurrentPatient()
    1082                  # do this here so "import cImmunisationsPanel from gmVaccWidgets" works
    1083                  self.ID_VaccinatedIndicationsList = wx.NewId()
    1084                  self.ID_VaccinationsPerRegimeList = wx.NewId()
    1085                  self.ID_MissingShots = wx.NewId()
    1086                  self.ID_ActiveSchedules = wx.NewId()
    1087                  self.__do_layout()
    1088                  self.__register_interests()
    1089                  self.__reset_ui_content()
    1090          #----------------------------------------------------
    1091 -        def __do_layout(self):
    1092                  #-----------------------------------------------
    1093                  # top part
    1094                  #-----------------------------------------------
    1095                  pnl_UpperCaption = gmTerryGuiParts.cHeadingCaption(self, -1, _("  IMMUNISATIONS  "))
    1096                  self.editarea = cVaccinationEditArea(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.NO_BORDER)
    1097
    1098                  #-----------------------------------------------
    1099                  # middle part
    1100                  #-----------------------------------------------
    1101                  # divider headings below editing area
    1102                  indications_heading = gmTerryGuiParts.cDividerCaption(self, -1, _("Indications"))
    1103                  vaccinations_heading = gmTerryGuiParts.cDividerCaption(self, -1, _("Vaccinations"))
    1104                  schedules_heading = gmTerryGuiParts.cDividerCaption(self, -1, _("Active Schedules"))
    1105                  szr_MiddleCap = wx.BoxSizer(wx.HORIZONTAL)
    1106                  szr_MiddleCap.Add(indications_heading, 4, wx.EXPAND)
    1107                  szr_MiddleCap.Add(vaccinations_heading, 6, wx.EXPAND)
    1108                  szr_MiddleCap.Add(schedules_heading, 10, wx.EXPAND)
    1109
    1110                  # left list: indications for which vaccinations have been given
    1111                  self.LBOX_vaccinated_indications = wx.ListBox(
    1112                          parent = self,
    1113                          id = self.ID_VaccinatedIndicationsList,
    1114                          choices = [],
    1115                          style = wx.LB_HSCROLL | wx.LB_NEEDED_SB | wx.SUNKEN_BORDER
    1116                  )
    1117                  self.LBOX_vaccinated_indications.SetFont(wx.Font(12,wx.SWISS, wx.NORMAL, wx.NORMAL, False, ''))
    1118
    1119                  # right list: when an indication has been selected on the left
    1120                  # display the corresponding vaccinations on the right
    1121                  self.LBOX_given_shots = wx.ListBox(
    1122                          parent = self,
    1123                          id = self.ID_VaccinationsPerRegimeList,
    1124                          choices = [],
    1125                          style = wx.LB_HSCROLL | wx.LB_NEEDED_SB | wx.SUNKEN_BORDER
    1126                  )
    1127                  self.LBOX_given_shots.SetFont(wx.Font(12,wx.SWISS, wx.NORMAL, wx.NORMAL, False, ''))
    1128
    1129                  self.LBOX_active_schedules = wx.ListBox (
    1130                          parent = self,
    1131                          id = self.ID_ActiveSchedules,
    1132                          choices = [],
    1133                          style = wx.LB_HSCROLL | wx.LB_NEEDED_SB | wx.SUNKEN_BORDER
    1134                  )
    1135                  self.LBOX_active_schedules.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, False, ''))
    1136
    1137                  szr_MiddleLists = wx.BoxSizer(wx.HORIZONTAL)
    1138                  szr_MiddleLists.Add(self.LBOX_vaccinated_indications, 4, wx.EXPAND)
    1139                  szr_MiddleLists.Add(self.LBOX_given_shots, 6, wx.EXPAND)
    1140                  szr_MiddleLists.Add(self.LBOX_active_schedules, 10, wx.EXPAND)
    1141
    1142                  #---------------------------------------------
    1143                  # bottom part
    1144                  #---------------------------------------------
    1145                  missing_heading = gmTerryGuiParts.cDividerCaption(self, -1, _("Missing Immunisations"))
    1146                  szr_BottomCap = wx.BoxSizer(wx.HORIZONTAL)
    1147                  szr_BottomCap.Add(missing_heading, 1, wx.EXPAND)
    1148
    1149                  self.LBOX_missing_shots = wx.ListBox (
    1150                          parent = self,
    1151                          id = self.ID_MissingShots,
    1152                          choices = [],
    1153                          style = wx.LB_HSCROLL | wx.LB_NEEDED_SB | wx.SUNKEN_BORDER
    1154                  )
    1155                  self.LBOX_missing_shots.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, False, ''))
    1156
    1157                  szr_BottomLists = wx.BoxSizer(wx.HORIZONTAL)
    1158                  szr_BottomLists.Add(self.LBOX_missing_shots, 1, wx.EXPAND)
    1159
    1160                  # alert caption
    1161                  pnl_AlertCaption = gmTerryGuiParts.cAlertCaption(self, -1, _('  Alerts  '))
    1162
    1163                  #---------------------------------------------
    1164                  # add all elements to the main background sizer
    1165                  #---------------------------------------------
    1166                  self.mainsizer = wx.BoxSizer(wx.VERTICAL)
    1167                  self.mainsizer.Add(pnl_UpperCaption, 0, wx.EXPAND)
    1168                  self.mainsizer.Add(self.editarea, 6, wx.EXPAND)
    1169                  self.mainsizer.Add(szr_MiddleCap, 0, wx.EXPAND)
    1170                  self.mainsizer.Add(szr_MiddleLists, 4, wx.EXPAND)
    1171                  self.mainsizer.Add(szr_BottomCap, 0, wx.EXPAND)
    1172                  self.mainsizer.Add(szr_BottomLists, 4, wx.EXPAND)
    1173                  self.mainsizer.Add(pnl_AlertCaption, 0, wx.EXPAND)
    1174
    1175                  self.SetAutoLayout(True)
    1176                  self.SetSizer(self.mainsizer)
    1177                  self.mainsizer.Fit(self)
    1178          #----------------------------------------------------
    1179 -        def __register_interests(self):
    1180                  # wxPython events
    1181                  wx.EVT_SIZE(self, self.OnSize)
    1182                  wx.EVT_LISTBOX(self, self.ID_VaccinatedIndicationsList, self._on_vaccinated_indication_selected)
    1183                  wx.EVT_LISTBOX_DCLICK(self, self.ID_VaccinationsPerRegimeList, self._on_given_shot_selected)
    1184                  wx.EVT_LISTBOX_DCLICK(self, self.ID_MissingShots, self._on_missing_shot_selected)
    1185  #               wx.EVT_RIGHT_UP(self.lb1, self.EvtRightButton)
    1186
    1187                  # client internal signals
    1188                  gmDispatcher.connect(signal= 'post_patient_selection', receiver=self._schedule_data_reget)
    1189                  gmDispatcher.connect(signal= 'vaccinations_updated', receiver=self._schedule_data_reget)
    1190          #----------------------------------------------------
    1191          # event handlers
    1192          #----------------------------------------------------
    1193 -        def OnSize (self, event):
    1194                  w, h = event.GetSize()
    1195                  self.mainsizer.SetDimension (0, 0, w, h)
    1196          #----------------------------------------------------
    1197 -        def _on_given_shot_selected(self, event):
    1198                  """Paste previously given shot into edit area.
    1199                  """
    1200                  self.editarea.set_data(aVacc=event.GetClientData())
    1201          #----------------------------------------------------
    1202 -        def _on_missing_shot_selected(self, event):
    1203                  self.editarea.set_data(aVacc = event.GetClientData())
    1204          #----------------------------------------------------
    1205 -        def _on_vaccinated_indication_selected(self, event):
    1206                  """Update right hand middle list to show vaccinations given for selected indication."""
    1207                  ind_list = event.GetEventObject()
    1208                  selected_item = ind_list.GetSelection()
    1209                  ind = ind_list.GetClientData(selected_item)
    1210                  # clear list
    1211                  self.LBOX_given_shots.Set([])
    1212                  emr = self.__pat.emr
    1213                  shots = emr.get_vaccinations(indications = [ind])
    1214                  # FIXME: use Set() for entire array (but problem with client_data)
    1215                  for shot in shots:
    1216                          if shot['is_booster']:
    1217                                  marker = 'B'
    1218                          else:
    1219                                  marker = '#%s' % shot['seq_no']
    1220                          label = '%s - %s: %s' % (marker, shot['date'].strftime('%m/%Y'), shot['vaccine'])
    1221                          self.LBOX_given_shots.Append(label, shot)
    1222          #----------------------------------------------------
    1223 -        def __reset_ui_content(self):
    1224                  # clear edit area
    1225                  self.editarea.set_data()
    1226                  # clear lists
    1227                  self.LBOX_vaccinated_indications.Clear()
    1228                  self.LBOX_given_shots.Clear()
    1229                  self.LBOX_active_schedules.Clear()
    1230                  self.LBOX_missing_shots.Clear()
    1231          #----------------------------------------------------
    1232 -        def _populate_with_data(self):
    1233                  # clear lists
    1234                  self.LBOX_vaccinated_indications.Clear()
    1235                  self.LBOX_given_shots.Clear()
    1236                  self.LBOX_active_schedules.Clear()
    1237                  self.LBOX_missing_shots.Clear()
    1238
    1239                  emr = self.__pat.emr
    1240
    1241                  t1 = time.time()
    1242                  # populate vaccinated-indications list
    1243                  # FIXME: consider adding virtual indication "most recent" to
    1244                  # FIXME: display most recent of all indications as suggested by Syan
    1245                  status, indications = emr.get_vaccinated_indications()
    1246                  # FIXME: would be faster to use Set() but can't
    1247                  # use Set(labels, client_data), and have to know
    1248                  # line position in SetClientData :-(
    1249                  for indication in indications:
    1250                          self.LBOX_vaccinated_indications.Append(indication[1], indication[0])
    1251  #               self.LBOX_vaccinated_indications.Set(lines)
    1252  #               self.LBOX_vaccinated_indications.SetClientData(data)
    1253                  print("vaccinated indications took", time.time()-t1, "seconds")
    1254
    1255                  t1 = time.time()
    1256                  # populate active schedules list
    1257                  scheds = emr.get_scheduled_vaccination_regimes()
    1258                  if scheds is None:
    1259                          label = _('ERROR: cannot retrieve active vaccination schedules')
    1260                          self.LBOX_active_schedules.Append(label)
    1261                  elif len(scheds) == 0:
    1262                          label = _('no active vaccination schedules')
    1263                          self.LBOX_active_schedules.Append(label)
    1264                  else:
    1265                          for sched in scheds:
    1266                                  label = _('%s for %s (%s shots): %s') % (sched['regime'], sched['l10n_indication'], sched['shots'], sched['comment'])
    1267                                  self.LBOX_active_schedules.Append(label)
    1268                  print("active schedules took", time.time()-t1, "seconds")
    1269
    1270                  t1 = time.time()
    1271                  # populate missing-shots list
    1272                  missing_shots = emr.get_missing_vaccinations()
    1273                  print("getting missing shots took", time.time()-t1, "seconds")
    1274                  if missing_shots is None:
    1275                          label = _('ERROR: cannot retrieve due/overdue vaccinations')
    1276                          self.LBOX_missing_shots.Append(label, None)
    1277                          return True
    1278                  # due
    1279                  due_template = _('%.0d weeks left: shot %s for %s in %s, due %s (%s)')
    1280                  overdue_template = _('overdue %.0dyrs %.0dwks: shot %s for %s in schedule "%s" (%s)')
    1281                  for shot in missing_shots['due']:
    1282                          if shot['overdue']:
    1283                                  years, days_left = divmod(shot['amount_overdue'].days, 364.25)
    1284                                  weeks = days_left / 7
    1285                                  # amount_overdue, seq_no, indication, regime, vacc_comment
    1286                                  label = overdue_template % (
    1287                                          years,
    1288                                          weeks,
    1289                                          shot['seq_no'],
    1290                                          shot['l10n_indication'],
    1291                                          shot['regime'],
    1292                                          shot['vacc_comment']
    1293                                  )
    1294                                  self.LBOX_missing_shots.Append(label, shot)
    1295                          else:
    1296                                  # time_left, seq_no, regime, latest_due, vacc_comment
    1297                                  label = due_template % (
    1298                                          shot['time_left'].days / 7,
    1299                                          shot['seq_no'],
    1300                                          shot['indication'],
    1301                                          shot['regime'],
    1302                                          shot['latest_due'].strftime('%m/%Y'),
    1303                                          shot['vacc_comment']
    1304                                  )
    1305                                  self.LBOX_missing_shots.Append(label, shot)
    1306                  # booster
    1307                  lbl_template = _('due now: booster for %s in schedule "%s" (%s)')
    1308                  for shot in missing_shots['boosters']:
    1309                          # indication, regime, vacc_comment
    1310                          label = lbl_template % (
    1311                                  shot['l10n_indication'],
    1312                                  shot['regime'],
    1313                                  shot['vacc_comment']
    1314                          )
    1315                          self.LBOX_missing_shots.Append(label, shot)
    1316                  print("displaying missing shots took", time.time()-t1, "seconds")
    1317
    1318                  return True
    1319          #----------------------------------------------------
    1320 -        def _on_post_patient_selection(self, **kwargs):
    1321                  return 1
    1322                  # FIXME:
    1323  #               if has_focus:
    1324  #                       wxCallAfter(self.__reset_ui_content)
    1325  #               else:
    1326  #                       return 1
    1327          #----------------------------------------------------
    1328 -        def _on_vaccinations_updated(self, **kwargs):
    1329                  return 1
    1330                  # FIXME:
    1331  #               if has_focus:
    1332  #                       wxCallAfter(self.__reset_ui_content)
    1333  #               else:
    1334  #                       is_stale == True
    1335  #                       return 1
    1336  #======================================================================
    1337  # main
    1338  #----------------------------------------------------------------------
    1339  if __name__ == "__main__":
    1340
    1341          if len(sys.argv) < 2:
    1342                  sys.exit()
    1343
    1344          if sys.argv[1] != 'test':
    1345                  sys.exit()
    1346
    1347          app = wx.PyWidgetTester(size = (600, 600))
    1348          app.SetWidget(cXxxPhraseWheel, -1)
    1349          app.MainLoop()
    1350