Gnumed.wxpython.gmMedicationWidgets

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

# Source Code for Module Gnumed.wxpython.gmMedicationWidgets

       1  """GNUmed medication/substances handling widgets."""
       2
       3  #================================================================
       4  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
       5  __license__ = "GPL v2 or later"
       6
       7  import logging
       8  import sys
       9  import os.path
      10  import decimal
      11  import datetime as pydt
      12
      13
      14  import wx
      15  import wx.grid
      16
      17
      18  if __name__ == '__main__':
      19          sys.path.insert(0, '../../')
      20          from Gnumed.pycommon import gmI18N
      21          gmI18N.activate_locale()
      22          gmI18N.install_domain(domain = 'gnumed')
      23
      24  from Gnumed.pycommon import gmDispatcher
      25  from Gnumed.pycommon import gmCfg
      26  from Gnumed.pycommon import gmTools
      27  from Gnumed.pycommon import gmDateTime
      28  from Gnumed.pycommon import gmMatchProvider
      29  from Gnumed.pycommon import gmI18N
      30  from Gnumed.pycommon import gmPrinting
      31  from Gnumed.pycommon import gmCfg2
      32  from Gnumed.pycommon import gmNetworkTools
      33
      34  from Gnumed.business import gmPerson
      35  from Gnumed.business import gmATC
      36  from Gnumed.business import gmPraxis
      37  from Gnumed.business import gmMedication
      38  from Gnumed.business import gmForms
      39  from Gnumed.business import gmStaff
      40  from Gnumed.business import gmDocuments
      41  from Gnumed.business import gmLOINC
      42  from Gnumed.business import gmClinicalRecord
      43  from Gnumed.business import gmClinicalCalculator
      44  from Gnumed.business import gmPathLab
      45
      46  from Gnumed.wxpython import gmGuiHelpers
      47  from Gnumed.wxpython import gmRegetMixin
      48  from Gnumed.wxpython import gmAuthWidgets
      49  from Gnumed.wxpython import gmEditArea
      50  from Gnumed.wxpython import gmMacro
      51  from Gnumed.wxpython import gmCfgWidgets
      52  from Gnumed.wxpython import gmListWidgets
      53  from Gnumed.wxpython import gmPhraseWheel
      54  from Gnumed.wxpython import gmFormWidgets
      55  from Gnumed.wxpython import gmAllergyWidgets
      56  from Gnumed.wxpython import gmDocumentWidgets
      57
      58
      59  _log = logging.getLogger('gm.ui')
      60
      61  #============================================================
      62  # generic drug database access
      63  #============================================================
      64 -def configure_drug_data_source(parent=None):
      65          gmCfgWidgets.configure_string_from_list_option (
      66                  parent = parent,
      67                  message = _(
      68                          '\n'
      69                          'Please select the default drug data source from the list below.\n'
      70                          '\n'
      71                          'Note that to actually use it you need to have the database installed, too.'
      72                  ),
      73                  option = 'external.drug_data.default_source',
      74                  bias = 'user',
      75                  default_value = None,
      76                  choices = gmMedication.drug_data_source_interfaces.keys(),
      77                  columns = [_('Drug data source')],
      78                  data = gmMedication.drug_data_source_interfaces.keys(),
      79                  caption = _('Configuring default drug data source')
      80          )
      81  #============================================================
      82 -def get_drug_database(parent = None):
      83          dbcfg = gmCfg.cCfgSQL()
      84
      85          # load from option
      86          default_db = dbcfg.get2 (
      87                  option = 'external.drug_data.default_source',
      88                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
      89                  bias = 'workplace'
      90          )
      91
      92          # not configured -> try to configure
      93          if default_db is None:
      94                  gmDispatcher.send('statustext', msg = _('No default drug database configured.'), beep = True)
      95                  configure_drug_data_source(parent = parent)
      96                  default_db = dbcfg.get2 (
      97                          option = 'external.drug_data.default_source',
      98                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
      99                          bias = 'workplace'
     100                  )
     101                  # still not configured -> return
     102                  if default_db is None:
     103                          gmGuiHelpers.gm_show_error (
     104                                  aMessage = _('There is no default drug database configured.'),
     105                                  aTitle = _('Jumping to drug database')
     106                          )
     107                          return None
     108
     109          # now it MUST be configured (either newly or previously)
     110          # but also *validly* ?
     111          try:
     112                  drug_db = gmMedication.drug_data_source_interfaces[default_db]()
     113          except KeyError:
     114                  # not valid
     115                  _log.error('faulty default drug data source configuration: %s', default_db)
     116                  # try to configure
     117                  configure_drug_data_source(parent = parent)
     118                  default_db = dbcfg.get2 (
     119                          option = 'external.drug_data.default_source',
     120                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     121                          bias = 'workplace'
     122                  )
     123                  # deconfigured or aborted (and thusly still misconfigured) ?
     124                  try:
     125                          drug_db = gmMedication.drug_data_source_interfaces[default_db]()
     126                  except KeyError:
     127                          _log.error('still faulty default drug data source configuration: %s', default_db)
     128                          return None
     129
     130          pat = gmPerson.gmCurrentPatient()
     131          if pat.connected:
     132                  drug_db.patient = pat
     133
     134          return drug_db
     135  #============================================================
     136 -def jump_to_drug_database():
     137          dbcfg = gmCfg.cCfgSQL()
     138          drug_db = get_drug_database()
     139          if drug_db is None:
     140                  return
     141          drug_db.switch_to_frontend(blocking = False)
     142
     143  #============================================================
     144 -def jump_to_ifap(import_drugs=False):
     145
     146          dbcfg = gmCfg.cCfgSQL()
     147
     148          ifap_cmd = dbcfg.get2 (
     149                  option = 'external.ifap-win.shell_command',
     150                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     151                  bias = 'workplace',
     152                  default = 'wine "C:\Ifapwin\WIAMDB.EXE"'
     153          )
     154          found, binary = gmShellAPI.detect_external_binary(ifap_cmd)
     155          if not found:
     156                  gmDispatcher.send('statustext', msg = _('Cannot call IFAP via [%s].') % ifap_cmd)
     157                  return False
     158          ifap_cmd = binary
     159
     160          if import_drugs:
     161                  transfer_file = os.path.expanduser(dbcfg.get2 (
     162                          option = 'external.ifap-win.transfer_file',
     163                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     164                          bias = 'workplace',
     165                          default = '~/.wine/drive_c/Ifapwin/ifap2gnumed.csv'
     166                  ))
     167                  # file must exist for Ifap to write into it
     168                  try:
     169                          f = open(transfer_file, 'w+b').close()
     170                  except IOError:
     171                          _log.exception('Cannot create IFAP <-> GNUmed transfer file [%s]', transfer_file)
     172                          gmDispatcher.send('statustext', msg = _('Cannot create IFAP <-> GNUmed transfer file [%s].') % transfer_file)
     173                          return False
     174
     175          wx.BeginBusyCursor()
     176          gmShellAPI.run_command_in_shell(command = ifap_cmd, blocking = import_drugs)
     177          wx.EndBusyCursor()
     178
     179          if import_drugs:
     180                  # COMMENT: this file must exist PRIOR to invoking IFAP
     181                  # COMMENT: or else IFAP will not write data into it ...
     182                  try:
     183                          csv_file = open(transfer_file, 'rb')                                            # FIXME: encoding
     184                  except:
     185                          _log.exception('cannot access [%s]', fname)
     186                          csv_file = None
     187
     188                  if csv_file is not None:
     189                          import csv
     190                          csv_lines = csv.DictReader (
     191                                  csv_file,
     192                                  fieldnames = u'PZN Handelsname Form Abpackungsmenge Einheit Preis1 Hersteller Preis2 rezeptpflichtig Festbetrag Packungszahl Packungsgr\xf6\xdfe'.split(),
     193                                  delimiter = ';'
     194                          )
     195                          pat = gmPerson.gmCurrentPatient()
     196                          emr = pat.get_emr()
     197                          # dummy episode for now
     198                          epi = emr.add_episode(episode_name = _('Current medication'))
     199                          for line in csv_lines:
     200                                  narr = u'%sx %s %s %s (\u2258 %s %s) von %s (%s)' % (
     201                                          line['Packungszahl'].strip(),
     202                                          line['Handelsname'].strip(),
     203                                          line['Form'].strip(),
     204                                          line[u'Packungsgr\xf6\xdfe'].strip(),
     205                                          line['Abpackungsmenge'].strip(),
     206                                          line['Einheit'].strip(),
     207                                          line['Hersteller'].strip(),
     208                                          line['PZN'].strip()
     209                                  )
     210                                  emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi)
     211                          csv_file.close()
     212
     213          return True
     214
     215  #============================================================
     216  # ATC related widgets
     217  #============================================================
     218
     219 -def browse_atc_reference(parent=None):
     220
     221          if parent is None:
     222                  parent = wx.GetApp().GetTopWindow()
     223          #------------------------------------------------------------
     224          def refresh(lctrl):
     225                  atcs = gmATC.get_reference_atcs()
     226
     227                  items = [ [
     228                          a['atc'],
     229                          a['term'],
     230                          gmTools.coalesce(a['unit'], u''),
     231                          gmTools.coalesce(a['administrative_route'], u''),
     232                          gmTools.coalesce(a['comment'], u''),
     233                          a['version'],
     234                          a['lang']
     235                  ] for a in atcs ]
     236                  lctrl.set_string_items(items)
     237                  lctrl.set_data(atcs)
     238          #------------------------------------------------------------
     239          gmListWidgets.get_choices_from_list (
     240                  parent = parent,
     241                  msg = _('\nThe ATC codes as known to GNUmed.\n'),
     242                  caption = _('Showing ATC codes.'),
     243                  columns = [ u'ATC', _('Term'), _('Unit'), _(u'Route'), _('Comment'), _('Version'), _('Language') ],
     244                  single_selection = True,
     245                  refresh_callback = refresh
     246          )
     247
     248  #============================================================
     249 -def update_atc_reference_data():
     250
     251          dlg = wx.FileDialog (
     252                  parent = None,
     253                  message = _('Choose an ATC import config file'),
     254                  defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')),
     255                  defaultFile = '',
     256                  wildcard = "%s (*.conf)|*.conf|%s (*)|*" % (_('config files'), _('all files')),
     257                  style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST
     258          )
     259
     260          result = dlg.ShowModal()
     261          if result == wx.ID_CANCEL:
     262                  return
     263
     264          cfg_file = dlg.GetPath()
     265          dlg.Destroy()
     266
     267          conn = gmAuthWidgets.get_dbowner_connection(procedure = _('importing ATC reference data'))
     268          if conn is None:
     269                  return False
     270
     271          wx.BeginBusyCursor()
     272
     273          if gmATC.atc_import(cfg_fname = cfg_file, conn = conn):
     274                  gmDispatcher.send(signal = 'statustext', msg = _('Successfully imported ATC reference data.'))
     275          else:
     276                  gmDispatcher.send(signal = 'statustext', msg = _('Importing ATC reference data failed.'), beep = True)
     277
     278          wx.EndBusyCursor()
     279          return True
     280
     281  #============================================================
     282
     283 -class cATCPhraseWheel(gmPhraseWheel.cPhraseWheel):
     284
     285 -        def __init__(self, *args, **kwargs):
     286
     287                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     288
     289                  query = u"""
     290
     291                          SELECT DISTINCT ON (label)
     292                                  atc_code,
     293                                  label
     294                          FROM (
     295
     296                                  SELECT
     297                                          code as atc_code,
     298                                          (code || ': ' || term)
     299                                                  AS label
     300                                  FROM ref.atc
     301                                  WHERE
     302                                          term %(fragment_condition)s
     303                                                  OR
     304                                          code %(fragment_condition)s
     305
     306                                  UNION ALL
     307
     308                                  SELECT
     309                                          atc_code,
     310                                          (atc_code || ': ' || description)
     311                                                  AS label
     312                                  FROM ref.consumable_substance
     313                                  WHERE
     314                                          description %(fragment_condition)s
     315                                                  OR
     316                                          atc_code %(fragment_condition)s
     317
     318                                  UNION ALL
     319
     320                                  SELECT
     321                                          atc_code,
     322                                          (atc_code || ': ' || description || ' (' || preparation || ')')
     323                                                  AS label
     324                                  FROM ref.branded_drug
     325                                  WHERE
     326                                          description %(fragment_condition)s
     327                                                  OR
     328                                          atc_code %(fragment_condition)s
     329
     330                                  -- it would be nice to be able to include clin.vacc_indication but that's hard to do in SQL
     331
     332                          ) AS candidates
     333                          WHERE atc_code IS NOT NULL
     334                          ORDER BY label
     335                          LIMIT 50"""
     336
     337                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
     338                  mp.setThresholds(1, 2, 4)
     339  #               mp.word_separators = '[ \t=+&:@]+'
     340                  self.SetToolTipString(_('Select an ATC (Anatomical-Therapeutic-Chemical) code.'))
     341                  self.matcher = mp
     342                  self.selection_only = True
     343
     344  #============================================================
     345  # consumable substances widgets
     346  #------------------------------------------------------------
     347 -def manage_consumable_substances(parent=None):
     348
     349          if parent is None:
     350                  parent = wx.GetApp().GetTopWindow()
     351          #------------------------------------------------------------
     352          def add_from_db(substance):
     353                  drug_db = get_drug_database(parent = parent)
     354                  if drug_db is None:
     355                          return False
     356                  drug_db.import_drugs()
     357                  return True
     358          #------------------------------------------------------------
     359          def edit(substance=None):
     360                  return edit_consumable_substance(parent = parent, substance = substance, single_entry = (substance is not None))
     361          #------------------------------------------------------------
     362          def delete(substance):
     363                  if substance.is_in_use_by_patients:
     364                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this substance. It is in use.'), beep = True)
     365                          return False
     366
     367                  return gmMedication.delete_consumable_substance(substance = substance['pk'])
     368          #------------------------------------------------------------
     369          def refresh(lctrl):
     370                  substs = gmMedication.get_consumable_substances(order_by = 'description')
     371                  items = [ [
     372                          s['description'],
     373                          s['amount'],
     374                          s['unit'],
     375                          gmTools.coalesce(s['atc_code'], u''),
     376                          s['pk']
     377                  ] for s in substs ]
     378                  lctrl.set_string_items(items)
     379                  lctrl.set_data(substs)
     380          #------------------------------------------------------------
     381          msg = _('\nThese are the consumable substances registered with GNUmed.\n')
     382
     383          gmListWidgets.get_choices_from_list (
     384                  parent = parent,
     385                  msg = msg,
     386                  caption = _('Showing consumable substances.'),
     387                  columns = [_('Substance'), _('Amount'), _('Unit'), 'ATC', u'#'],
     388                  single_selection = True,
     389                  new_callback = edit,
     390                  edit_callback = edit,
     391                  delete_callback = delete,
     392                  refresh_callback = refresh,
     393                  left_extra_button = (_('Import'), _('Import consumable substances from a drug database.'), add_from_db)
     394          )
     395
     396  #------------------------------------------------------------
     397 -def edit_consumable_substance(parent=None, substance=None, single_entry=False):
     398
     399          if substance is not None:
     400                  if substance.is_in_use_by_patients:
     401                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit this substance. It is in use.'), beep = True)
     402                          return False
     403
     404          ea = cConsumableSubstanceEAPnl(parent = parent, id = -1)
     405          ea.data = substance
     406          ea.mode = gmTools.coalesce(substance, 'new', 'edit')
     407          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
     408          dlg.SetTitle(gmTools.coalesce(substance, _('Adding new consumable substance'), _('Editing consumable substance')))
     409          if dlg.ShowModal() == wx.ID_OK:
     410                  dlg.Destroy()
     411                  return True
     412          dlg.Destroy()
     413          return False
     414
     415  #============================================================
     416  from Gnumed.wxGladeWidgets import wxgConsumableSubstanceEAPnl
     417
     418 -class cConsumableSubstanceEAPnl(wxgConsumableSubstanceEAPnl.wxgConsumableSubstanceEAPnl, gmEditArea.cGenericEditAreaMixin):
     419
     420 -        def __init__(self, *args, **kwargs):
     421
     422                  try:
     423                          data = kwargs['substance']
     424                          del kwargs['substance']
     425                  except KeyError:
     426                          data = None
     427
     428                  wxgConsumableSubstanceEAPnl.wxgConsumableSubstanceEAPnl.__init__(self, *args, **kwargs)
     429                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     430
     431                  # Code using this mixin should set mode and data
     432                  # after instantiating the class:
     433                  self.mode = 'new'
     434                  self.data = data
     435                  if data is not None:
     436                          self.mode = 'edit'
     437
     438  #               self.__init_ui()
     439          #----------------------------------------------------------------
     440  #       def __init_ui(self):
     441  #               self._PRW_atc.selection_only = False
     442          #----------------------------------------------------------------
     443          # generic Edit Area mixin API
     444          #----------------------------------------------------------------
     445 -        def _valid_for_save(self):
     446
     447                  validity = True
     448
     449                  if self._TCTRL_substance.GetValue().strip() == u'':
     450                          validity = False
     451                          self.display_tctrl_as_valid(tctrl = self._TCTRL_substance, valid = False)
     452                          self._TCTRL_substance.SetFocus()
     453                  else:
     454                          self.display_tctrl_as_valid(tctrl = self._TCTRL_substance, valid = True)
     455
     456                  try:
     457                          decimal.Decimal(self._TCTRL_amount.GetValue().strip().replace(',', '.'))
     458                          self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
     459                  except (TypeError, decimal.InvalidOperation):
     460                          validity = False
     461                          self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
     462                          self._TCTRL_amount.SetFocus()
     463
     464                  if self._PRW_unit.GetValue().strip() == u'':
     465                          validity = False
     466                          self._PRW_unit.display_as_valid(valid = False)
     467                          self._TCTRL_substance.SetFocus()
     468                  else:
     469                          self._PRW_unit.display_as_valid(valid = True)
     470
     471                  if validity is False:
     472                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save consumable substance. Missing essential input.'))
     473
     474                  return validity
     475          #----------------------------------------------------------------
     476 -        def _save_as_new(self):
     477                  subst = gmMedication.create_consumable_substance (
     478                          substance = self._TCTRL_substance.GetValue().strip(),
     479                          atc = self._PRW_atc.GetData(),
     480                          amount = decimal.Decimal(self._TCTRL_amount.GetValue().strip().replace(',', '.')),
     481                          unit = gmTools.coalesce(self._PRW_unit.GetData(), self._PRW_unit.GetValue().strip(), function_initial = ('strip', None))
     482                  )
     483                  success, data = subst.save()
     484                  if not success:
     485                          err, msg = data
     486                          _log.error(err)
     487                          _log.error(msg)
     488                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save consumable substance. %s') % msg, beep = True)
     489                          return False
     490
     491                  self.data = subst
     492                  return True
     493          #----------------------------------------------------------------
     494 -        def _save_as_update(self):
     495                  self.data['description'] = self._TCTRL_substance.GetValue().strip()
     496                  self.data['atc_code'] = self._PRW_atc.GetData()
     497                  self.data['amount'] = decimal.Decimal(self._TCTRL_amount.GetValue().strip().replace(',', '.'))
     498                  self.data['unit'] = gmTools.coalesce(self._PRW_unit.GetData(), self._PRW_unit.GetValue().strip(), function_initial = ('strip', None))
     499                  success, data = self.data.save()
     500
     501                  if not success:
     502                          err, msg = data
     503                          _log.error(err)
     504                          _log.error(msg)
     505                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save consumable substance. %s') % msg, beep = True)
     506                          return False
     507
     508                  return True
     509          #----------------------------------------------------------------
     510 -        def _refresh_as_new(self):
     511                  self._TCTRL_substance.SetValue(u'')
     512                  self._TCTRL_amount.SetValue(u'')
     513                  self._PRW_unit.SetText(u'', None)
     514                  self._PRW_atc.SetText(u'', None)
     515
     516                  self._TCTRL_substance.SetFocus()
     517          #----------------------------------------------------------------
     518 -        def _refresh_from_existing(self):
     519                  self._TCTRL_substance.SetValue(self.data['description'])
     520                  self._TCTRL_amount.SetValue(u'%s' % self.data['amount'])
     521                  self._PRW_unit.SetText(self.data['unit'], self.data['unit'])
     522                  self._PRW_atc.SetText(gmTools.coalesce(self.data['atc_code'], u''), self.data['atc_code'])
     523
     524                  self._TCTRL_substance.SetFocus()
     525          #----------------------------------------------------------------
     526 -        def _refresh_as_new_from_existing(self):
     527                  self._refresh_as_new()
     528
     529  #============================================================
     530  # drug component widgets
     531  #------------------------------------------------------------
     532 -def manage_drug_components(parent=None):
     533
     534          if parent is None:
     535                  parent = wx.GetApp().GetTopWindow()
     536
     537          #------------------------------------------------------------
     538          def edit(component=None):
     539                  substance = gmMedication.cConsumableSubstance(aPK_obj = component['pk_consumable_substance'])
     540                  return edit_consumable_substance(parent = parent, substance = substance, single_entry = True)
     541          #------------------------------------------------------------
     542          def delete(component):
     543                  if component.is_in_use_by_patients:
     544                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot remove this component from the drug. It is in use.'), beep = True)
     545                          return False
     546
     547                  return component.containing_drug.remove_component(substance = component['pk_component'])
     548          #------------------------------------------------------------
     549          def refresh(lctrl):
     550                  comps = gmMedication.get_drug_components()
     551                  items = [ [
     552                          u'%s%s' % (c['brand'], gmTools.coalesce(c['atc_brand'], u'', u' [%s]')),
     553                          u'%s%s' % (c['substance'], gmTools.coalesce(c['atc_substance'], u'', u' [%s]')),
     554                          u'%s %s' % (c['amount'], c['unit']),
     555                          c['preparation'],
     556                          gmTools.coalesce(c['external_code_brand'], u'', u'%%s [%s]' % c['external_code_type_brand']),
     557                          c['pk_component']
     558                  ] for c in comps ]
     559                  lctrl.set_string_items(items)
     560                  lctrl.set_data(comps)
     561          #------------------------------------------------------------
     562          msg = _('\nThese are the components in the drug brands known to GNUmed.\n')
     563
     564          gmListWidgets.get_choices_from_list (
     565                  parent = parent,
     566                  msg = msg,
     567                  caption = _('Showing drug brand components.'),
     568                  columns = [_('Brand'), _('Substance'), _('Strength'), _('Preparation'), _('Code'), u'#'],
     569                  single_selection = True,
     570                  #new_callback = edit,
     571                  edit_callback = edit,
     572                  delete_callback = delete,
     573                  refresh_callback = refresh
     574          )
     575
     576  #------------------------------------------------------------
     577 -def edit_drug_component(parent=None, drug_component=None, single_entry=False):
     578          ea = cDrugComponentEAPnl(parent = parent, id = -1)
     579          ea.data = drug_component
     580          ea.mode = gmTools.coalesce(drug_component, 'new', 'edit')
     581          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
     582          dlg.SetTitle(gmTools.coalesce(drug_component, _('Adding new drug component'), _('Editing drug component')))
     583          if dlg.ShowModal() == wx.ID_OK:
     584                  dlg.Destroy()
     585                  return True
     586          dlg.Destroy()
     587          return False
     588
     589  #============================================================
     590  from Gnumed.wxGladeWidgets import wxgDrugComponentEAPnl
     591
     592 -class cDrugComponentEAPnl(wxgDrugComponentEAPnl.wxgDrugComponentEAPnl, gmEditArea.cGenericEditAreaMixin):
     593
     594 -        def __init__(self, *args, **kwargs):
     595
     596                  try:
     597                          data = kwargs['component']
     598                          del kwargs['component']
     599                  except KeyError:
     600                          data = None
     601
     602                  wxgDrugComponentEAPnl.wxgDrugComponentEAPnl.__init__(self, *args, **kwargs)
     603                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     604
     605                  # Code using this mixin should set mode and data
     606                  # after instantiating the class:
     607                  self.mode = 'new'
     608                  self.data = data
     609                  if data is not None:
     610                          self.mode = 'edit'
     611
     612                  #self.__init_ui()
     613          #----------------------------------------------------------------
     614  #       def __init_ui(self):
     615  #               # adjust phrasewheels etc
     616          #----------------------------------------------------------------
     617          # generic Edit Area mixin API
     618          #----------------------------------------------------------------
     619 -        def _valid_for_save(self):
     620                  if self.data is not None:
     621                          if self.data['is_in_use']:
     622                                  gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit drug component. It is in use.'), beep = True)
     623                                  return False
     624
     625                  validity = True
     626
     627                  if self._PRW_substance.GetData() is None:
     628                          validity = False
     629                          self._PRW_substance.display_as_valid(False)
     630                  else:
     631                          self._PRW_substance.display_as_valid(True)
     632
     633                  val = self._TCTRL_amount.GetValue().strip().replace(',', u'.', 1)
     634                  try:
     635                          decimal.Decimal(val)
     636                          self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
     637                  except:
     638                          validity = False
     639                          self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
     640
     641                  if self._PRW_unit.GetValue().strip() == u'':
     642                          validity = False
     643                          self._PRW_unit.display_as_valid(False)
     644                  else:
     645                          self._PRW_unit.display_as_valid(True)
     646
     647                  if validity is False:
     648                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save drug component. Invalid or missing essential input.'))
     649
     650                  return validity
     651          #----------------------------------------------------------------
     652 -        def _save_as_new(self):
     653                  # save the data as a new instance
     654                  data = 1
     655                  data[''] = 1
     656                  data[''] = 1
     657  #               data.save()
     658
     659                  # must be done very late or else the property access
     660                  # will refresh the display such that later field
     661                  # access will return empty values
     662  #               self.data = data
     663                  return False
     664                  return True
     665          #----------------------------------------------------------------
     666 -        def _save_as_update(self):
     667                  self.data['pk_consumable_substance'] = self._PRW_substance.GetData()
     668                  self.data['amount'] = decimal.Decimal(self._TCTRL_amount.GetValue().strip().replace(',', u'.', 1))
     669                  self.data['unit'] = self._PRW_unit.GetValue().strip()
     670                  return self.data.save()
     671          #----------------------------------------------------------------
     672 -        def _refresh_as_new(self):
     673                  self._TCTRL_brand.SetValue(u'')
     674                  self._TCTRL_components.SetValue(u'')
     675                  self._TCTRL_codes.SetValue(u'')
     676                  self._PRW_substance.SetText(u'', None)
     677                  self._TCTRL_amount.SetValue(u'')
     678                  self._PRW_unit.SetText(u'', None)
     679
     680                  self._PRW_substance.SetFocus()
     681          #----------------------------------------------------------------
     682 -        def _refresh_from_existing(self):
     683                  self._TCTRL_brand.SetValue(u'%s (%s)' % (self.data['brand'], self.data['preparation']))
     684                  self._TCTRL_components.SetValue(u' / '.join(self.data.containing_drug['components']))
     685                  details = []
     686                  if self.data['atc_brand'] is not None:
     687                          details.append(u'ATC: %s' % self.data['atc_brand'])
     688                  if self.data['external_code_brand'] is not None:
     689                          details.append(u'%s: %s' % (self.data['external_code_type_brand'], self.data['external_code_brand']))
     690                  self._TCTRL_codes.SetValue(u'; '.join(details))
     691
     692                  self._PRW_substance.SetText(self.data['substance'], self.data['pk_consumable_substance'])
     693                  self._TCTRL_amount.SetValue(u'%s' % self.data['amount'])
     694                  self._PRW_unit.SetText(self.data['unit'], self.data['unit'])
     695
     696                  self._PRW_substance.SetFocus()
     697          #----------------------------------------------------------------
     698 -        def _refresh_as_new_from_existing(self):
     699                  #self._PRW_brand.SetText(u'', None)
     700                  #self._TCTRL_prep.SetValue(u'')
     701                  #self._TCTRL_brand_details.SetValue(u'')
     702                  self._PRW_substance.SetText(u'', None)
     703                  self._TCTRL_amount.SetValue(u'')
     704                  self._PRW_unit.SetText(u'', None)
     705
     706                  self._PRW_substance.SetFocus()
     707
     708  #============================================================
     709 -class cDrugComponentPhraseWheel(gmPhraseWheel.cPhraseWheel):
     710
     711 -        def __init__(self, *args, **kwargs):
     712
     713                  mp = gmMedication.cDrugComponentMatchProvider()
     714                  mp.setThresholds(2, 3, 4)
     715                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     716                  self.SetToolTipString(_('A drug component with optional strength.'))
     717                  self.matcher = mp
     718                  self.selection_only = False
     719          #--------------------------------------------------------
     720 -        def _data2instance(self):
     721                  return gmMedication.cDrugComponent(aPK_obj = self.GetData(as_instance = False, can_create = False))
     722
     723  #============================================================
     724  #============================================================
     725 -class cSubstancePreparationPhraseWheel(gmPhraseWheel.cPhraseWheel):
     726
     727 -        def __init__(self, *args, **kwargs):
     728
     729                  query = u"""
     730  (
     731          SELECT DISTINCT ON (list_label)
     732                  preparation AS data,
     733                  preparation AS list_label,
     734                  preparation AS field_label
     735          FROM ref.branded_drug
     736          WHERE preparation %(fragment_condition)s
     737  ) UNION (
     738          SELECT DISTINCT ON (list_label)
     739                  preparation AS data,
     740                  preparation AS list_label,
     741                  preparation AS field_label
     742          FROM clin.substance_intake
     743          WHERE preparation %(fragment_condition)s
     744  )
     745  ORDER BY list_label
     746  LIMIT 30"""
     747
     748                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
     749                  mp.setThresholds(1, 2, 4)
     750                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     751                  self.SetToolTipString(_('The preparation (form) of the substance or brand.'))
     752                  self.matcher = mp
     753                  self.selection_only = False
     754
     755  #============================================================
     756 -class cSubstancePhraseWheel(gmPhraseWheel.cPhraseWheel):
     757
     758 -        def __init__(self, *args, **kwargs):
     759
     760                  mp = gmMedication.cSubstanceMatchProvider()
     761                  mp.setThresholds(1, 2, 4)
     762                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     763                  self.SetToolTipString(_('The substance with optional strength.'))
     764                  self.matcher = mp
     765                  self.selection_only = False
     766                  self.phrase_separators = None
     767
     768          #--------------------------------------------------------
     769 -        def _data2instance(self):
     770                  return gmMedication.cConsumableSubstance(aPK_obj = self.GetData(as_instance = False, can_create = False))
     771
     772  #============================================================
     773  # branded drugs widgets
     774  #------------------------------------------------------------
     775 -def manage_components_of_branded_drug(parent=None, brand=None):
     776
     777          if brand is not None:
     778                  if brand.is_in_use_by_patients:
     779                          gmGuiHelpers.gm_show_info (
     780                                  aTitle = _('Managing components of a drug'),
     781                                  aMessage = _(
     782                                          'Cannot manage the components of the branded drug product\n'
     783                                          '\n'
     784                                          ' "%s" (%s)\n'
     785                                          '\n'
     786                                          'because it is currently taken by patients.\n'
     787                                  ) % (brand['brand'], brand['preparation'])
     788                          )
     789                          return False
     790          #--------------------------------------------------------
     791          if parent is None:
     792                  parent = wx.GetApp().GetTopWindow()
     793          #--------------------------------------------------------
     794  #       def manage_substances():
     795  #               pass
     796          #--------------------------------------------------------
     797          if brand is None:
     798                  msg = _('Pick the substances which are components of this drug.')
     799                  right_col = _('Components of drug')
     800                  comp_substs = []
     801          else:
     802                  right_col = u'%s (%s)' % (brand['brand'], brand['preparation'])
     803                  msg = _(
     804                          'Adjust the components of "%s"\n'
     805                          '\n'
     806                          'The drug must contain at least one component. Any given\n'
     807                          'substance can only be included once per drug.'
     808                  ) % right_col
     809                  comp_substs = [ c.substance for c in brand.components ]
     810
     811          substs = gmMedication.get_consumable_substances(order_by = 'description')
     812          choices = [ u'%s %s %s' % (s['description'], s['amount'], s['unit']) for s in substs ]
     813          picks = [ u'%s %s %s' % (c['description'], c['amount'], c['unit']) for c in comp_substs ]
     814
     815          picker = gmListWidgets.cItemPickerDlg (
     816                  parent,
     817                  -1,
     818                  title = _('Managing components of a drug ...'),
     819                  msg = msg
     820          )
     821          picker.set_columns(['Substances'], [right_col])
     822          picker.set_choices(choices = choices, data = substs)
     823          picker.set_picks(picks = picks, data = comp_substs)
     824  #       picker.extra_button = (
     825  #               _('Substances'),
     826  #               _('Manage list of consumable substances'),
     827  #               manage_substances
     828  #       )
     829
     830          btn_pressed = picker.ShowModal()
     831          substs = picker.get_picks()
     832          picker.Destroy()
     833
     834          if btn_pressed != wx.ID_OK:
     835                  return (False, None)
     836
     837          if brand is not None:
     838                  brand.set_substances_as_components(substances = substs)
     839
     840          return (True, substs)
     841  #------------------------------------------------------------
     842 -def manage_branded_drugs(parent=None, ignore_OK_button=False):
     843
     844          if parent is None:
     845                  parent = wx.GetApp().GetTopWindow()
     846          #------------------------------------------------------------
     847          def add_from_db(brand):
     848                  drug_db = get_drug_database(parent = parent)
     849                  if drug_db is None:
     850                          return False
     851                  drug_db.import_drugs()
     852                  return True
     853          #------------------------------------------------------------
     854          def get_tooltip(brand=None):
     855                  tt = u'%s %s\n' % (brand['brand'], brand['preparation'])
     856                  tt += u'\n'
     857                  tt += u'%s%s%s\n' % (
     858                          gmTools.bool2subst(brand.is_vaccine, u'%s, ' % _('Vaccine'), u''),
     859                          u'%s, ' % gmTools.bool2subst(brand.is_in_use_by_patients, _('in use'), _('not in use')),
     860                          gmTools.bool2subst(brand['is_fake_brand'], _('fake'), u'')
     861                  )
     862                  tt += gmTools.coalesce(brand['atc'], u'', _('ATC: %s\n'))
     863                  tt += gmTools.coalesce(brand['external_code'], u'', u'%s: %%s\n' % brand['external_code_type'])
     864                  if brand['components'] is not None:
     865                          tt += u'- %s' % u'\n- '.join(brand['components'])
     866                  return tt
     867          #------------------------------------------------------------
     868          def edit(brand):
     869                  if brand is not None:
     870                          if brand.is_vaccine:
     871                                  gmGuiHelpers.gm_show_info (
     872                                          aTitle = _('Editing medication'),
     873                                          aMessage = _(
     874                                                  'Cannot edit the medication\n'
     875                                                  '\n'
     876                                                  ' "%s" (%s)\n'
     877                                                  '\n'
     878                                                  'because it is a vaccine. Please edit it\n'
     879                                                  'from the vaccine management section !\n'
     880                                          ) % (brand['brand'], brand['preparation'])
     881                                  )
     882                                  return False
     883
     884                  return edit_branded_drug(parent = parent, branded_drug = brand, single_entry = True)
     885          #------------------------------------------------------------
     886          def delete(brand):
     887                  if brand.is_vaccine:
     888                          gmGuiHelpers.gm_show_info (
     889                                  aTitle = _('Deleting medication'),
     890                                  aMessage = _(
     891                                          'Cannot delete the medication\n'
     892                                          '\n'
     893                                          ' "%s" (%s)\n'
     894                                          '\n'
     895                                          'because it is a vaccine. Please delete it\n'
     896                                          'from the vaccine management section !\n'
     897                                  ) % (brand['brand'], brand['preparation'])
     898                          )
     899                          return False
     900                  gmMedication.delete_branded_drug(brand = brand['pk_brand'])
     901                  return True
     902          #------------------------------------------------------------
     903          def new():
     904                  return edit_branded_drug(parent = parent, branded_drug = None, single_entry = False)
     905          #------------------------------------------------------------
     906          def refresh(lctrl):
     907                  drugs = gmMedication.get_branded_drugs()
     908                  items = [ [
     909                          u'%s%s' % (
     910                                  d['brand'],
     911                                  gmTools.bool2subst(d['is_fake_brand'], ' (%s)' % _('fake'), u'')
     912                          ),
     913                          d['preparation'],
     914                          gmTools.coalesce(d['atc'], u''),
     915                          gmTools.coalesce(d['components'], u''),
     916                          gmTools.coalesce(d['external_code'], u'', u'%%s [%s]' % d['external_code_type']),
     917                          d['pk_brand']
     918                  ] for d in drugs ]
     919                  lctrl.set_string_items(items)
     920                  lctrl.set_data(drugs)
     921          #------------------------------------------------------------
     922          msg = _('\nThese are the drug brands known to GNUmed.\n')
     923
     924          gmListWidgets.get_choices_from_list (
     925                  parent = parent,
     926                  msg = msg,
     927                  caption = _('Showing branded drugs.'),
     928                  columns = [_('Name'), _('Preparation'), _('ATC'), _('Components'), _('Code'), u'#'],
     929                  single_selection = True,
     930                  ignore_OK_button = ignore_OK_button,
     931                  refresh_callback = refresh,
     932                  new_callback = new,
     933                  edit_callback = edit,
     934                  delete_callback = delete,
     935                  list_tooltip_callback = get_tooltip,
     936                  left_extra_button = (_('Import'), _('Import substances and brands from a drug database.'), add_from_db)
     937                  #, middle_extra_button = (_('Clone'), _('Clone selected drug into a new entry for editing.'), clone_from_existing)
     938                  #, right_extra_button = (_('Reassign'), _('Reassign all patients taking the selected drug to another drug.'), reassign_patients)
     939          )
     940
     941  #------------------------------------------------------------
     942 -def edit_branded_drug(parent=None, branded_drug=None, single_entry=False):
     943
     944          if branded_drug is not None:
     945                  if branded_drug.is_in_use_by_patients:
     946                          gmGuiHelpers.gm_show_info (
     947                                  aTitle = _('Editing drug'),
     948                                  aMessage = _(
     949                                          'Cannot edit the branded drug product\n'
     950                                          '\n'
     951                                          ' "%s" (%s)\n'
     952                                          '\n'
     953                                          'because it is currently taken by patients.\n'
     954                                  ) % (branded_drug['brand'], branded_drug['preparation'])
     955                          )
     956                          return False
     957
     958          if parent is None:
     959                  parent = wx.GetApp().GetTopWindow()
     960          #--------------------------------------------
     961          def manage_substances(drug):
     962                  manage_consumable_substances(parent = parent)
     963          #--------------------------------------------
     964          ea = cBrandedDrugEAPnl(parent = parent, id = -1)
     965          ea.data = branded_drug
     966          ea.mode = gmTools.coalesce(branded_drug, 'new', 'edit')
     967          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
     968          dlg.SetTitle(gmTools.coalesce(branded_drug, _('Adding new drug brand'), _('Editing drug brand')))
     969          dlg.left_extra_button = (
     970                  _('Substances'),
     971                  _('Manage consumable substances'),
     972                  manage_substances
     973          )
     974          if dlg.ShowModal() == wx.ID_OK:
     975                  dlg.Destroy()
     976                  return True
     977          dlg.Destroy()
     978          return False
     979
     980  #============================================================
     981  from Gnumed.wxGladeWidgets import wxgBrandedDrugEAPnl
     982
     983 -class cBrandedDrugEAPnl(wxgBrandedDrugEAPnl.wxgBrandedDrugEAPnl, gmEditArea.cGenericEditAreaMixin):
     984
     985 -        def __init__(self, *args, **kwargs):
     986
     987                  try:
     988                          data = kwargs['drug']
     989                          del kwargs['drug']
     990                  except KeyError:
     991                          data = None
     992
     993                  wxgBrandedDrugEAPnl.wxgBrandedDrugEAPnl.__init__(self, *args, **kwargs)
     994                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     995
     996                  self.mode = 'new'
     997                  self.data = data
     998                  if data is not None:
     999                          self.mode = 'edit'
    1000                          self.__component_substances = data.components_as_substances
    1001
    1002                  #self.__init_ui()
    1003          #----------------------------------------------------------------
    1004  #       def __init_ui(self):
    1005                  # adjust external type PRW
    1006          #----------------------------------------------------------------
    1007          # generic Edit Area mixin API
    1008          #----------------------------------------------------------------
    1009 -        def _valid_for_save(self):
    1010
    1011                  if self.data is not None:
    1012                          if self.data.is_in_use_by_patients:
    1013                                  gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit drug brand. It is in use.'), beep = True)
    1014                                  return False
    1015
    1016                  validity = True
    1017
    1018                  brand_name = self._PRW_brand.GetValue().strip()
    1019                  if brand_name == u'':
    1020                          validity = False
    1021                          self._PRW_brand.display_as_valid(False)
    1022                  else:
    1023                          self._PRW_brand.display_as_valid(True)
    1024
    1025                  preparation = self._PRW_preparation.GetValue().strip()
    1026                  if preparation == u'':
    1027                          validity = False
    1028                          self._PRW_preparation.display_as_valid(False)
    1029                  else:
    1030                          self._PRW_preparation.display_as_valid(True)
    1031
    1032                  if validity is True:
    1033                          # dupe ?
    1034                          drug = gmMedication.get_drug_by_brand(brand_name = brand_name, preparation = preparation)
    1035                          if drug is not None:
    1036                                  validity = False
    1037                                  self._PRW_brand.display_as_valid(False)
    1038                                  self._PRW_preparation.display_as_valid(False)
    1039                                  gmGuiHelpers.gm_show_error (
    1040                                          title = _('Checking brand data'),
    1041                                          error = _(
    1042                                                  'The brand information you entered:\n'
    1043                                                  '\n'
    1044                                                  ' [%s %s]\n'
    1045                                                  '\n'
    1046                                                  'already exists as a drug product.'
    1047                                          ) % (brand_name, preparation)
    1048                                  )
    1049
    1050                          else:
    1051                                  # lacking components ?
    1052                                  self._TCTRL_components.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BACKGROUND))
    1053                                  if len(self.__component_substances) == 0:
    1054                                          wants_empty = gmGuiHelpers.gm_show_question (
    1055                                                  title = _('Checking brand data'),
    1056                                                  question = _(
    1057                                                          'You have not selected any substances\n'
    1058                                                          'as drug components.\n'
    1059                                                          '\n'
    1060                                                          'Without components you will not be able to\n'
    1061                                                          'use this drug for documenting patient care.\n'
    1062                                                          '\n'
    1063                                                          'Are you sure you want to save\n'
    1064                                                          'it without components ?'
    1065                                                  )
    1066                                          )
    1067                                          if not wants_empty:
    1068                                                  validity = False
    1069                                                  self.display_ctrl_as_valid(ctrl = self._TCTRL_components, valid = False)
    1070
    1071                  if validity is False:
    1072                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save branded drug. Invalid or missing essential input.'))
    1073
    1074                  return validity
    1075          #----------------------------------------------------------------
    1076 -        def _save_as_new(self):
    1077
    1078                  drug = gmMedication.create_branded_drug (
    1079                          brand_name = self._PRW_brand.GetValue().strip(),
    1080                          preparation = gmTools.coalesce (
    1081                                  self._PRW_preparation.GetData(),
    1082                                  self._PRW_preparation.GetValue()
    1083                          ).strip(),
    1084                          return_existing = True
    1085                  )
    1086                  drug['is_fake_brand'] = self._CHBOX_is_fake.GetValue()
    1087                  drug['atc'] = self._PRW_atc.GetData()
    1088                  code = self._TCTRL_external_code.GetValue().strip()
    1089                  if code != u'':
    1090                          drug['external_code'] = code
    1091                          drug['external_code_type'] = self._PRW_external_code_type.GetData().strip()
    1092
    1093                  drug.save()
    1094
    1095                  if len(self.__component_substances) > 0:
    1096                          drug.set_substances_as_components(substances = self.__component_substances)
    1097
    1098                  self.data = drug
    1099
    1100                  return True
    1101          #----------------------------------------------------------------
    1102 -        def _save_as_update(self):
    1103                  self.data['brand'] = self._PRW_brand.GetValue().strip()
    1104                  self.data['preparation'] = gmTools.coalesce (
    1105                          self._PRW_preparation.GetData(),
    1106                          self._PRW_preparation.GetValue()
    1107                  ).strip()
    1108                  self.data['is_fake_brand'] = self._CHBOX_is_fake.GetValue()
    1109                  self.data['atc'] = self._PRW_atc.GetData()
    1110                  code = self._TCTRL_external_code.GetValue().strip()
    1111                  if code != u'':
    1112                          self.data['external_code'] = code
    1113                          self.data['external_code_type'] = self._PRW_external_code_type.GetData().strip()
    1114                  success, data = self.data.save()
    1115                  if not success:
    1116                          err, msg = data
    1117                          _log.error('problem saving')
    1118                          _log.error('%s', err)
    1119                          _log.error('%s', msg)
    1120                  return (success is True)
    1121          #----------------------------------------------------------------
    1122 -        def _refresh_as_new(self):
    1123                  self._PRW_brand.SetText(u'', None)
    1124                  self._PRW_preparation.SetText(u'', None)
    1125                  self._CHBOX_is_fake.SetValue(False)
    1126                  self._TCTRL_components.SetValue(u'')
    1127                  self._PRW_atc.SetText(u'', None)
    1128                  self._TCTRL_external_code.SetValue(u'')
    1129                  self._PRW_external_code_type.SetText(u'', None)
    1130
    1131                  self._PRW_brand.SetFocus()
    1132
    1133                  self.__component_substances = []
    1134          #----------------------------------------------------------------
    1135 -        def _refresh_as_new_from_existing(self):
    1136                  self._refresh_as_new()
    1137          #----------------------------------------------------------------
    1138 -        def _refresh_from_existing(self):
    1139                  self._PRW_brand.SetText(self.data['brand'], self.data['pk_brand'])
    1140                  self._PRW_preparation.SetText(self.data['preparation'], self.data['preparation'])
    1141                  self._CHBOX_is_fake.SetValue(self.data['is_fake_brand'])
    1142                  comps = u''
    1143                  if self.data['components'] is not None:
    1144                          comps = u'- %s' % u'\n- '.join(self.data['components'])
    1145                  self._TCTRL_components.SetValue(comps)
    1146                  self._PRW_atc.SetText(gmTools.coalesce(self.data['atc'], u''), self.data['atc'])
    1147                  self._TCTRL_external_code.SetValue(gmTools.coalesce(self.data['external_code'], u''))
    1148                  t = gmTools.coalesce(self.data['external_code_type'], u'')
    1149                  self._PRW_external_code_type.SetText(t, t)
    1150
    1151                  self._PRW_brand.SetFocus()
    1152
    1153                  self.__component_substances = self.data.components_as_substances
    1154          #----------------------------------------------------------------
    1155          # event handler
    1156          #----------------------------------------------------------------
    1157 -        def _on_manage_components_button_pressed(self, event):
    1158                  event.Skip()
    1159                  if self.mode == 'new_from_existing':
    1160                          brand = None
    1161                  else:
    1162                          brand = self.data
    1163                  OKed, substs = manage_components_of_branded_drug(parent = self, brand = brand)
    1164                  if OKed is True:
    1165                          self.__component_substances = substs
    1166                          comps = u''
    1167                          if len(substs) > 0:
    1168                                  comps = u'- %s' % u'\n- '.join([ u'%s %s %s' % (s['description'], s['amount'], s['unit']) for s in substs ])
    1169                          self._TCTRL_components.SetValue(comps)
    1170  #============================================================
    1171 -class cBrandedDrugPhraseWheel(gmPhraseWheel.cPhraseWheel):
    1172
    1173 -        def __init__(self, *args, **kwargs):
    1174
    1175                  query = u"""
    1176                          SELECT
    1177                                  pk
    1178                                          AS data,
    1179                                  (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', ''))
    1180                                          AS list_label,
    1181                                  (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', ''))
    1182                                          AS field_label
    1183                          FROM ref.branded_drug
    1184                          WHERE description %(fragment_condition)s
    1185                          ORDER BY list_label
    1186                          LIMIT 50"""
    1187
    1188                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
    1189                  mp.setThresholds(2, 3, 4)
    1190                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    1191                  self.SetToolTipString(_(
    1192                          'The brand name of the drug.\n'
    1193                          '\n'
    1194                          'Note: a brand name will need to be linked to\n'
    1195                          'one or more components before it can be used,\n'
    1196                          'except in the case of fake (generic) vaccines.'
    1197                  ))
    1198                  self.matcher = mp
    1199                  self.selection_only = False
    1200
    1201  #============================================================
    1202  # current substance intake widgets
    1203  #------------------------------------------------------------
    1204 -class cSubstanceSchedulePhraseWheel(gmPhraseWheel.cPhraseWheel):
    1205
    1206 -        def __init__(self, *args, **kwargs):
    1207
    1208                  query = u"""
    1209  SELECT DISTINCT ON (sched)
    1210          schedule as sched,
    1211          schedule
    1212  FROM clin.substance_intake
    1213  WHERE schedule %(fragment_condition)s
    1214  ORDER BY sched
    1215  LIMIT 50"""
    1216
    1217                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
    1218                  mp.setThresholds(1, 2, 4)
    1219                  mp.word_separators = '[ \t=+&:@]+'
    1220                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    1221                  self.SetToolTipString(_('The schedule for taking this substance.'))
    1222                  self.matcher = mp
    1223                  self.selection_only = False
    1224
    1225  #============================================================
    1226 -class cSubstanceAimPhraseWheel(gmPhraseWheel.cPhraseWheel):
    1227
    1228 -        def __init__(self, *args, **kwargs):
    1229
    1230                  query = u"""
    1231  (
    1232          SELECT DISTINCT ON (field_label)
    1233                  aim
    1234                          AS data,
    1235                  aim || ' (' || substance || ' ' || amount || ' ' || unit || ')'
    1236                          AS list_label,
    1237                  aim
    1238                          AS field_label
    1239          FROM clin.v_substance_intakes
    1240          WHERE
    1241                  aim %(fragment_condition)s
    1242                  %(ctxt_substance)s
    1243  ) UNION (
    1244          SELECT DISTINCT ON (field_label)
    1245                  aim
    1246                          AS data,
    1247                  aim || ' (' || substance || ' ' || amount || ' ' || unit || ')'
    1248                          AS list_label,
    1249                  aim
    1250                          AS field_label
    1251          FROM clin.v_substance_intakes
    1252          WHERE
    1253                  aim %(fragment_condition)s
    1254  )
    1255  ORDER BY list_label
    1256  LIMIT 30"""
    1257
    1258                  context = {'ctxt_substance': {
    1259                          'where_part': u'AND substance = %(substance)s',
    1260                          'placeholder': u'substance'
    1261                  }}
    1262
    1263                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = context)
    1264                  mp.setThresholds(1, 2, 4)
    1265                  #mp.word_separators = '[ \t=+&:@]+'
    1266                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    1267                  self.SetToolTipString(_('The medical aim for consuming this substance.'))
    1268                  self.matcher = mp
    1269                  self.selection_only = False
    1270
    1271  #============================================================
    1272 -def turn_substance_intake_into_allergy(parent=None, intake=None, emr=None):
    1273
    1274          if intake['is_currently_active']:
    1275                  intake['discontinued'] = gmDateTime.pydt_now_here()
    1276          if intake['discontinue_reason'] is None:
    1277                  intake['discontinue_reason'] = u'%s %s' % (_('not tolerated:'), _('discontinued due to allergy or intolerance'))
    1278          else:
    1279                  if not intake['discontinue_reason'].startswith(_('not tolerated:')):
    1280                          intake['discontinue_reason'] = u'%s %s' % (_('not tolerated:'), intake['discontinue_reason'])
    1281          if not intake.save():
    1282                  return False
    1283
    1284          allg = intake.turn_into_allergy(encounter_id = emr.active_encounter['pk_encounter'])
    1285
    1286          brand = intake.containing_drug
    1287          if brand is not None:
    1288                  comps = [ c['substance'] for c in brand.components ]
    1289                  if len(comps) > 1:
    1290                          gmGuiHelpers.gm_show_info (
    1291                                  aTitle = _(u'Documented an allergy'),
    1292                                  aMessage = _(
    1293                                          u'An allergy was documented against the substance:\n'
    1294                                          u'\n'
    1295                                          u'  [%s]\n'
    1296                                          u'\n'
    1297                                          u'This substance was taken with the multi-component brand:\n'
    1298                                          u'\n'
    1299                                          u'  [%s (%s)]\n'
    1300                                          u'\n'
    1301                                          u'Note that ALL components of this brand were discontinued.'
    1302                                  ) % (
    1303                                          intake['substance'],
    1304                                          intake['brand'],
    1305                                          u' & '.join(comps)
    1306                                  )
    1307                          )
    1308
    1309          if parent is None:
    1310                  parent = wx.GetApp().GetTopWindow()
    1311
    1312          dlg = gmAllergyWidgets.cAllergyManagerDlg(parent = parent, id = -1)
    1313          dlg.ShowModal()
    1314
    1315          return True
    1316
    1317  #============================================================
    1318 -def manage_substance_intakes(parent=None, emr=None):
    1319
    1320          if parent is None:
    1321                  parent = wx.GetApp().GetTopWindow()
    1322
    1323          if emr is None:
    1324                  emr = gmPerson.gmCurrentPatient().emr
    1325  #       #------------------------------------------------------------
    1326  #       def add_from_db(substance):
    1327  #               drug_db = get_drug_database(parent = parent)
    1328  #               if drug_db is None:
    1329  #                       return False
    1330  #               drug_db.import_drugs()
    1331  #               return True
    1332  #       #------------------------------------------------------------
    1333  #       def edit(substance=None):
    1334  #               return edit_consumable_substance(parent = parent, substance = substance, single_entry = (substance is not None))
    1335  #       #------------------------------------------------------------
    1336  #       def delete(substance):
    1337  #               if substance.is_in_use_by_patients:
    1338  #                       gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this substance. It is in use.'), beep = True)
    1339  #                       return False
    1340  #
    1341  #               return gmMedication.delete_consumable_substance(substance = substance['pk'])
    1342          #------------------------------------------------------------
    1343          def get_tooltip(intake=None):
    1344                  return intake.format(one_line = False, show_all_brand_components = True)
    1345          #------------------------------------------------------------
    1346          def refresh(lctrl):
    1347                  intakes = emr.get_current_substance_intakes (
    1348                          include_inactive = False,
    1349                          include_unapproved = True,
    1350                          order_by = u'substance, brand, started'
    1351                  )
    1352                  items = []
    1353                  for i in intakes:
    1354                          started = i.medically_formatted_start
    1355                          items.append ([
    1356                                  u'%s%s %s %s %s%s' % (
    1357                                          i['substance'],
    1358                                          gmTools.coalesce(i['brand'], u'', u' (%s)'),
    1359                                          i['amount'],
    1360                                          i['unit'],
    1361                                          i['preparation'],
    1362                                          gmTools.coalesce(i['external_code_brand'], u'', u' [%s::%s]' % (i['external_code_type_brand'], i['external_code_brand']))
    1363                                  ),
    1364                                  u'%s%s%s' % (
    1365                                          started,
    1366                                          gmTools.coalesce(i['schedule'], u'', u' %%s %s' % gmTools.u_right_arrow),
    1367                                          gmTools.coalesce(i['duration'], u'', u' %s')
    1368                                  ),
    1369                                  u'%s' % (
    1370                                          gmTools.bool2subst (
    1371                                                  i['intake_is_approved_of'],
    1372                                                  u'',
    1373                                                  _('disapproved')
    1374                                          )
    1375                                  )
    1376                          ])
    1377                  lctrl.set_string_items(items)
    1378                  lctrl.set_data(intakes)
    1379          #------------------------------------------------------------
    1380          msg = _('Substances consumed by the patient:')
    1381
    1382          return gmListWidgets.get_choices_from_list (
    1383                  parent = parent,
    1384                  msg = msg,
    1385                  caption = _('Showing consumable substances.'),
    1386                  columns = [ _('Intake'), _('Application'), _('Status') ],
    1387                  single_selection = False,
    1388  #               new_callback = edit,
    1389  #               edit_callback = edit,
    1390  #               delete_callback = delete,
    1391                  refresh_callback = refresh,
    1392                  list_tooltip_callback = get_tooltip
    1393  #               ,left_extra_button = (_('Import'), _('Import consumable substances from a drug database.'), add_from_db)
    1394          )
    1395
    1396  #============================================================
    1397  from Gnumed.wxGladeWidgets import wxgCurrentMedicationEAPnl
    1398
    1399 -class cSubstanceIntakeEAPnl(wxgCurrentMedicationEAPnl.wxgCurrentMedicationEAPnl, gmEditArea.cGenericEditAreaMixin):
    1400
    1401 -        def __init__(self, *args, **kwargs):
    1402
    1403                  try:
    1404                          data = kwargs['substance']
    1405                          del kwargs['substance']
    1406                  except KeyError:
    1407                          data = None
    1408
    1409                  self.calc = gmClinicalCalculator.cClinicalCalculator()
    1410
    1411                  wxgCurrentMedicationEAPnl.wxgCurrentMedicationEAPnl.__init__(self, *args, **kwargs)
    1412                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    1413
    1414                  self.mode = 'new'
    1415                  self.data = data
    1416                  if data is not None:
    1417                          self.mode = 'edit'
    1418
    1419                  self.__init_ui()
    1420          #----------------------------------------------------------------
    1421 -        def __init_ui(self):
    1422
    1423                  self._PRW_component.add_callback_on_lose_focus(callback = self._on_leave_component)
    1424                  self._PRW_component.selection_only = True
    1425
    1426                  self._PRW_substance.add_callback_on_lose_focus(callback = self._on_leave_substance)
    1427                  self._PRW_substance.selection_only = True
    1428
    1429                  self._PRW_duration.display_accuracy = gmDateTime.acc_days
    1430
    1431                  self._PRW_aim.add_callback_on_set_focus(callback = self._on_enter_aim)
    1432          #----------------------------------------------------------------
    1433 -        def __refresh_allergies(self):
    1434                  curr_pat = gmPerson.gmCurrentPatient()
    1435                  emr = curr_pat.emr
    1436
    1437                  state = emr.allergy_state
    1438                  if state['last_confirmed'] is None:
    1439                          confirmed = _('never')
    1440                  else:
    1441                          confirmed = gmDateTime.pydt_strftime(state['last_confirmed'], '%Y %b %d')
    1442                  msg = _(u'%s, last confirmed %s\n') % (state.state_string, confirmed)
    1443                  msg += gmTools.coalesce(state['comment'], u'', _('Comment (%s): %%s\n') % state['modified_by'])
    1444
    1445                  tt = u''
    1446
    1447                  allgs = emr.get_allergies()
    1448                  if len(allgs) > 0:
    1449                          msg += u'\n'
    1450                  for allergy in allgs:
    1451                          msg += u'%s: %s (%s)\n' % (
    1452                                  allergy['descriptor'],
    1453                                  allergy['l10n_type'],
    1454                                  gmTools.bool2subst(allergy['definite'], _('definite'), _('suspected'), u'?')
    1455                          )
    1456                          tt += u'%s: %s\n' % (
    1457                                  allergy['descriptor'],
    1458                                  gmTools.coalesce(allergy['reaction'], _('reaction not recorded'))
    1459                          )
    1460
    1461                  if len(allgs) > 0:
    1462                          msg += u'\n'
    1463                          tt += u'\n'
    1464
    1465                  gfr = emr.get_most_recent_results(loinc = gmLOINC.LOINC_gfr_quantity, no_of_results = 1)
    1466                  if gfr is None:
    1467                          self.calc.patient = curr_pat
    1468                          gfr = self.calc.eGFR
    1469                          if gfr.numeric_value is None:
    1470                                  msg += _('GFR: unknown')
    1471                          else:
    1472                                  msg += gfr.message
    1473                                  tt += gfr.format (
    1474                                          left_margin = 0,
    1475                                          width = 50,
    1476                                          eol = u'\n',
    1477                                          with_formula = True,
    1478                                          with_warnings = True,
    1479                                          with_variables = False,
    1480                                          with_sub_results = True,
    1481                                          return_list = False
    1482                                  )
    1483                  else:
    1484                          msg += u'%s: %s %s (%s)\n' % (
    1485                                  gfr['unified_abbrev'],
    1486                                  gfr['unified_val'],
    1487                                  gmTools.coalesce(gfr['abnormality_indicator'], u'', u' (%s)'),
    1488                                  gmDateTime.pydt_strftime (
    1489                                          gfr['clin_when'],
    1490                                          format = '%Y %b %d'
    1491                                  )
    1492                          )
    1493                          tt += _('GFR reported by path lab')
    1494
    1495                  self._LBL_allergies.SetLabel(msg)
    1496                  self._LBL_allergies.SetToolTipString(tt)
    1497          #----------------------------------------------------------------
    1498          # generic Edit Area mixin API
    1499          #----------------------------------------------------------------
    1500 -        def _valid_for_save(self):
    1501
    1502                  validity = True
    1503
    1504                  has_component = (self._PRW_component.GetData() is not None)
    1505                  has_substance = (self._PRW_substance.GetValue().strip() != u'')
    1506
    1507                  self._PRW_component.display_as_valid(True)
    1508
    1509                  # cannot add duplicate components
    1510                  if self.mode == 'new':
    1511                          msg = _(
    1512                                  'The patient is already taking\n'
    1513                                  '\n'
    1514                                  ' %s\n'
    1515                                  '\n'
    1516                                  'You will want to adjust the schedule\n'
    1517                                  'rather than document the intake twice.'
    1518                          )
    1519                          title = _('Adding substance intake entry')
    1520                          if has_component:
    1521                                  emr = gmPerson.gmCurrentPatient().get_emr()
    1522                                  if emr.substance_intake_exists(pk_component = self._PRW_component.GetData()):
    1523                                          gmGuiHelpers.gm_show_warning (
    1524                                                  aTitle = title,
    1525                                                  aMessage = msg % self._PRW_component.GetValue().strip()
    1526                                          )
    1527                                          self._PRW_component.display_as_valid(False)
    1528                                          validity = False
    1529                          pk_substance = self._PRW_substance.GetData()
    1530                          if pk_substance is not None:
    1531                                  emr = gmPerson.gmCurrentPatient().get_emr()
    1532                                  if emr.substance_intake_exists(pk_substance = pk_substance):
    1533                                          gmGuiHelpers.gm_show_warning (
    1534                                                  aTitle = title,
    1535                                                  aMessage = msg % self._PRW_substance.GetValue().strip()
    1536                                          )
    1537                                          self._PRW_substance.display_as_valid(False)
    1538                                          validity = False
    1539
    1540                  # must have either brand or substance
    1541                  if (has_component is False) and (has_substance is False):
    1542                          self._PRW_substance.display_as_valid(False)
    1543                          self._PRW_component.display_as_valid(False)
    1544                          validity = False
    1545                  else:
    1546                          self._PRW_substance.display_as_valid(True)
    1547
    1548                  # brands already have a preparation, so only required for substances
    1549                  if not has_component:
    1550                          if self._PRW_preparation.GetValue().strip() == u'':
    1551                                  self._PRW_preparation.display_as_valid(False)
    1552                                  validity = False
    1553                          else:
    1554                                  self._PRW_preparation.display_as_valid(True)
    1555
    1556                  # episode must be set if intake is to be approved of
    1557                  if self._CHBOX_approved.IsChecked():
    1558                          if self._PRW_episode.GetValue().strip() == u'':
    1559                                  self._PRW_episode.display_as_valid(False)
    1560                                  validity = False
    1561                          else:
    1562                                  self._PRW_episode.display_as_valid(True)
    1563
    1564                  if self._PRW_duration.GetValue().strip() in [u'', gmTools.u_infinity]:
    1565                          self._PRW_duration.display_as_valid(True)
    1566                  else:
    1567                          if self._PRW_duration.GetData() is None:
    1568                                  # no data ...
    1569                                  if gmDateTime.str2interval(self._PRW_duration.GetValue()) is None:
    1570                                          self._PRW_duration.display_as_valid(False)
    1571                                          validity = False
    1572                                  # ... but valid string
    1573                                  else:
    1574                                          self._PRW_duration.display_as_valid(True)
    1575                          # has data
    1576                          else:
    1577                                  self._PRW_duration.display_as_valid(True)
    1578
    1579                  # started must exist
    1580                  started = self._DP_started.GetData()
    1581                  if started is None:
    1582                          self._DP_started.display_as_valid(False)
    1583                          validity = False
    1584                  else:
    1585                          self._DP_started.display_as_valid(True)
    1586
    1587                  if validity is False:
    1588                          gmDispatcher.send(signal = 'statustext', msg = _('Input incomplete/invalid for saving as substance intake.'))
    1589
    1590                  # discontinued must be "< now()" AND "> started" if at all
    1591                  discontinued = self._DP_discontinued.GetData()
    1592                  if discontinued is not None:
    1593                          now = gmDateTime.pydt_now_here().replace (
    1594                                  hour = 23,
    1595                                  minute = 59,
    1596                                  second = 59,
    1597                                  microsecond = 111111
    1598                          )
    1599                          # not in the future
    1600                          if discontinued > now:
    1601                                  self._DP_discontinued.display_as_valid(False)
    1602                                  validity = False
    1603                                  gmDispatcher.send(signal = 'statustext', msg = _('Discontinued (%s) in the future (now: %s)!') % (discontinued, now))
    1604                          else:
    1605                                  started = started.replace (
    1606                                          hour = 0,
    1607                                          minute = 0,
    1608                                          second = 0,
    1609                                          microsecond = 1
    1610                                  )
    1611                                  # and not before it was started
    1612                                  if started > discontinued:
    1613                                          self._DP_started.display_as_valid(False)
    1614                                          self._DP_discontinued.display_as_valid(False)
    1615                                          validity = False
    1616                                          gmDispatcher.send(signal = 'statustext', msg = _('Discontinued (%s) before started (%s) !') % (discontinued, started))
    1617                                  else:
    1618                                          self._DP_started.display_as_valid(True)
    1619                                          self._DP_discontinued.display_as_valid(True)
    1620
    1621                  return validity
    1622          #----------------------------------------------------------------
    1623 -        def _save_as_new(self):
    1624
    1625                  epi = self._PRW_episode.GetData()
    1626                  if epi is None:
    1627                          # create new episode, Jim wants it to auto-open
    1628                          epi = self._PRW_episode.GetData(can_create = True, is_open = True)
    1629
    1630                  emr = gmPerson.gmCurrentPatient().get_emr()
    1631                  if self._PRW_substance.GetData() is None:
    1632                          # auto-creates all components as intakes
    1633                          intake = emr.add_substance_intake (
    1634                                  pk_component = self._PRW_component.GetData(),
    1635                                  episode = epi
    1636                          )
    1637                  else:
    1638                          intake = emr.add_substance_intake (
    1639                                  pk_substance = self._PRW_substance.GetData(),
    1640                                  episode = epi,
    1641                                  preparation = self._PRW_preparation.GetValue().strip()
    1642                          )
    1643
    1644                  if intake is None:
    1645                          gmDispatcher.send('statustext', msg = _('Cannot add duplicate of (maybe inactive) substance intake.'), beep = True)
    1646                          return False
    1647
    1648                  intake['started'] = self._DP_started.GetData()
    1649                  intake['discontinued'] = self._DP_discontinued.GetData()
    1650                  if intake['discontinued'] is None:
    1651                          intake['discontinue_reason'] = None
    1652                  else:
    1653                          intake['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip()
    1654                  intake['schedule'] = self._PRW_schedule.GetValue().strip()
    1655                  intake['aim'] = self._PRW_aim.GetValue().strip()
    1656                  intake['notes'] = self._PRW_notes.GetValue().strip()
    1657                  intake['is_long_term'] = self._CHBOX_long_term.IsChecked()
    1658                  intake['intake_is_approved_of'] = self._CHBOX_approved.IsChecked()
    1659                  if self._PRW_duration.GetValue().strip() in [u'', gmTools.u_infinity]:
    1660                          intake['duration'] = None
    1661                  else:
    1662                          if self._PRW_duration.GetData() is None:
    1663                                  intake['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue())
    1664                          else:
    1665                                  intake['duration'] = self._PRW_duration.GetData()
    1666                  intake.save()
    1667
    1668                  self.data = intake
    1669
    1670                  return True
    1671          #----------------------------------------------------------------
    1672 -        def _save_as_update(self):
    1673
    1674                  # auto-applies to all components of a multi-component drug if any:
    1675                  self.data['started'] = self._DP_started.GetData()
    1676                  self.data['discontinued'] = self._DP_discontinued.GetData()
    1677                  if self.data['discontinued'] is None:
    1678                          self.data['discontinue_reason'] = None
    1679                  else:
    1680                          self.data['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip()
    1681                  self.data['schedule'] = self._PRW_schedule.GetValue()
    1682                  self.data['is_long_term'] = self._CHBOX_long_term.IsChecked()
    1683                  self.data['intake_is_approved_of'] = self._CHBOX_approved.IsChecked()
    1684                  if self._PRW_duration.GetValue().strip() in [u'', gmTools.u_infinity]:
    1685                          self.data['duration'] = None
    1686                  else:
    1687                          if self._PRW_duration.GetData() is None:
    1688                                  self.data['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue())
    1689                          else:
    1690                                  self.data['duration'] = self._PRW_duration.GetData()
    1691
    1692                  # applies to non-component substances only
    1693                  self.data['preparation'] = self._PRW_preparation.GetValue()
    1694
    1695                  # per-component
    1696                  self.data['aim'] = self._PRW_aim.GetValue()
    1697                  self.data['notes'] = self._PRW_notes.GetValue()
    1698                  epi = self._PRW_episode.GetData()
    1699                  if epi is None:
    1700                          # create new episode, Jim wants it to auto-open
    1701                          epi = self._PRW_episode.GetData(can_create = True, is_open = True)
    1702                  self.data['pk_episode'] = epi
    1703
    1704                  self.data.save()
    1705
    1706                  return True
    1707          #----------------------------------------------------------------
    1708 -        def _refresh_as_new(self):
    1709                  self._PRW_component.SetText(u'', None)
    1710                  self._LBL_component.Enable(True)
    1711                  self._PRW_component.Enable(True)
    1712                  self._TCTRL_brand_ingredients.SetValue(u'')
    1713                  self._TCTRL_brand_ingredients.SetToolTipString(u'')
    1714
    1715                  self._LBL_or.Enable(True)
    1716
    1717                  self._PRW_substance.SetText(u'', None)
    1718                  self._PRW_substance.Enable(True)
    1719
    1720                  self._PRW_preparation.SetText(u'', None)
    1721                  self._PRW_preparation.Enable(True)
    1722
    1723                  self._PRW_schedule.SetText(u'', None)
    1724                  self._PRW_duration.SetText(u'', None)
    1725                  self._PRW_aim.SetText(u'', None)
    1726                  self._PRW_notes.SetText(u'', None)
    1727                  self._PRW_episode.SetText(u'', None)
    1728
    1729                  self._CHBOX_long_term.SetValue(False)
    1730                  self._CHBOX_approved.SetValue(True)
    1731
    1732                  self._DP_started.SetData(gmDateTime.pydt_now_here())
    1733                  self._DP_discontinued.SetData(None)
    1734                  self._PRW_discontinue_reason.SetValue(u'')
    1735
    1736                  self.__refresh_allergies()
    1737
    1738                  self._PRW_component.SetFocus()
    1739          #----------------------------------------------------------------
    1740 -        def _refresh_from_existing(self):
    1741
    1742                  self._TCTRL_brand_ingredients.SetValue(u'')
    1743                  self._TCTRL_brand_ingredients.SetToolTipString(u'')
    1744
    1745                  if self.data['pk_brand'] is None:
    1746                          self.__refresh_from_existing_substance()
    1747                  else:
    1748                          self.__refresh_from_existing_component()
    1749
    1750                  # no editing of substance or component
    1751                  self._LBL_component.Enable(False)
    1752                  self._PRW_component.Enable(False)
    1753                  self._LBL_or.Enable(False)
    1754                  self._PRW_substance.Enable(False)
    1755
    1756                  if self.data['is_long_term']:
    1757                          self._CHBOX_long_term.SetValue(True)
    1758                          self._PRW_duration.Enable(False)
    1759                          self._PRW_duration.SetText(gmTools.u_infinity, None)
    1760                          self._BTN_discontinued_as_planned.Enable(False)
    1761                  else:
    1762                          self._CHBOX_long_term.SetValue(False)
    1763                          self._PRW_duration.Enable(True)
    1764                          self._BTN_discontinued_as_planned.Enable(True)
    1765                          self._PRW_duration.SetData(self.data['duration'])
    1766  #                       if self.data['duration'] is None:
    1767  #                               self._PRW_duration.SetText(u'', None)
    1768  #                       else:
    1769  #                               self._PRW_duration.SetText(gmDateTime.format_interval(self.data['duration'], gmDateTime.acc_days), self.data['duration'])
    1770                  self._PRW_aim.SetText(gmTools.coalesce(self.data['aim'], u''), self.data['aim'])
    1771                  self._PRW_notes.SetText(gmTools.coalesce(self.data['notes'], u''), self.data['notes'])
    1772                  self._PRW_episode.SetData(self.data['pk_episode'])
    1773                  self._PRW_schedule.SetText(gmTools.coalesce(self.data['schedule'], u''), self.data['schedule'])
    1774
    1775                  self._CHBOX_approved.SetValue(self.data['intake_is_approved_of'])
    1776
    1777                  self._DP_started.SetData(self.data['started'])
    1778                  self._DP_discontinued.SetData(self.data['discontinued'])
    1779                  self._PRW_discontinue_reason.SetValue(gmTools.coalesce(self.data['discontinue_reason'], u''))
    1780                  if self.data['discontinued'] is not None:
    1781                          self._PRW_discontinue_reason.Enable()
    1782
    1783                  self.__refresh_allergies()
    1784
    1785                  self._PRW_schedule.SetFocus()
    1786          #----------------------------------------------------------------
    1787 -        def __refresh_from_existing_substance(self):
    1788                  self._LBL_component.Enable(False)
    1789                  self._PRW_component.Enable(False)
    1790                  self._PRW_component.SetText(u'', None)
    1791                  self._PRW_component.display_as_valid(True)
    1792
    1793                  self._LBL_or.Enable(False)
    1794
    1795                  # disable for 1.3 since we aren't saving
    1796                  # the change which in combination spells
    1797                  # doom for patient safety
    1798                  #self._PRW_substance.Enable(True)
    1799                  self._PRW_substance.Enable(False)
    1800                  self._PRW_substance.SetText (
    1801                          u'%s %s %s' % (self.data['substance'], self.data['amount'], self.data['unit']),
    1802                          self.data['pk_substance']
    1803                  )
    1804
    1805                  self._PRW_preparation.SetText(self.data['preparation'], self.data['preparation'])
    1806                  # see above
    1807                  self._PRW_preparation.Enable(True)
    1808                  self._PRW_preparation.Enable(False)
    1809          #----------------------------------------------------------------
    1810 -        def __refresh_from_existing_component(self):
    1811                  self._LBL_component.Enable(True)
    1812                  self._PRW_component.Enable(True)
    1813                  self._PRW_component.SetText (
    1814                          u'%s %s %s (%s)' % (self.data['substance'], self.data['amount'], self.data['unit'], self.data['brand']),
    1815                          self.data['pk_drug_component']
    1816                  )
    1817
    1818                  brand = gmMedication.cBrandedDrug(aPK_obj = self.data['pk_brand'])
    1819                  if brand['components'] is not None:
    1820                          self._TCTRL_brand_ingredients.SetValue(u'; '.join(brand['components']))
    1821                          tt = u'%s:\n\n- %s' % (
    1822                                  self.data['brand'],
    1823                                  u'\n- '.join(brand['components'])
    1824                          )
    1825                          self._TCTRL_brand_ingredients.SetToolTipString(tt)
    1826
    1827                  self._LBL_or.Enable(False)
    1828                  self._LBL_substance.Enable(False)
    1829                  self._PRW_substance.SetText(u'', None)
    1830                  self._PRW_substance.display_as_valid(True)
    1831
    1832                  self._PRW_preparation.SetText(self.data['preparation'], self.data['preparation'])
    1833                  self._PRW_preparation.Enable(False)
    1834          #----------------------------------------------------------------
    1835 -        def _refresh_as_new_from_existing(self):
    1836                  self._refresh_as_new()
    1837
    1838                  self._PRW_episode.SetData(self.data['pk_episode'])
    1839                  self._DP_started.SetData(self.data['started'])
    1840
    1841                  self._PRW_component.SetFocus()
    1842          #----------------------------------------------------------------
    1843          # event handlers
    1844          #----------------------------------------------------------------
    1845 -        def _on_leave_component(self):
    1846                  if self._PRW_component.GetData() is None:
    1847                          self._LBL_or.Enable(True)
    1848                          self._PRW_component.SetText(u'', None)
    1849                          self._LBL_substance.Enable(True)
    1850                          self._PRW_substance.Enable(True)
    1851                          self._LBL_preparation.Enable(True)
    1852                          self._PRW_preparation.Enable(True)
    1853                          #self._PRW_preparation.SetText(u'', None)
    1854                          self._TCTRL_brand_ingredients.SetValue(u'')
    1855                          self._TCTRL_brand_ingredients.SetToolTipString(u'')
    1856                  else:
    1857                          self._LBL_or.Enable(False)
    1858                          self._LBL_substance.Enable(False)
    1859                          self._PRW_substance.SetText(u'', None)
    1860                          self._PRW_substance.display_as_valid(True)
    1861                          self._PRW_substance.Enable(False)
    1862                          self._LBL_preparation.Enable(False)
    1863                          self._PRW_preparation.Enable(False)
    1864                          comp = gmMedication.cDrugComponent(aPK_obj = self._PRW_component.GetData())
    1865                          self._PRW_preparation.SetText(comp['preparation'], comp['preparation'])
    1866                          brand = comp.containing_drug
    1867                          if brand['components'] is not None:
    1868                                  self._TCTRL_brand_ingredients.SetValue(u'; '.join(brand['components']))
    1869                                  tt = u'%s:\n\n- %s' % (
    1870                                          brand['brand'],
    1871                                          u'\n- '.join(brand['components'])
    1872                                  )
    1873                                  self._TCTRL_brand_ingredients.SetToolTipString(tt)
    1874          #----------------------------------------------------------------
    1875 -        def _on_leave_substance(self):
    1876                  if self._PRW_substance.GetData() is None:
    1877                          self._LBL_or.Enable(True)
    1878                          self._LBL_component.Enable(True)
    1879                          self._PRW_component.Enable(True)
    1880                          self._PRW_substance.SetText(u'', None)
    1881                  else:
    1882                          self._LBL_or.Enable(False)
    1883                          self._LBL_component.Enable(False)
    1884                          self._PRW_component.SetText(u'', None)
    1885                          self._PRW_component.display_as_valid(True)
    1886                          self._PRW_component.Enable(False)
    1887                          self._LBL_preparation.Enable(True)
    1888                          self._PRW_preparation.Enable(True)
    1889                          self._TCTRL_brand_ingredients.SetValue(u'')
    1890                          self._TCTRL_brand_ingredients.SetToolTipString(u'')
    1891          #----------------------------------------------------------------
    1892 -        def _on_enter_aim(self):
    1893                  # when a drug component/substance is selected (that is, when .GetData()
    1894                  # returns not None) then we do not want to use the GetValue().strip()
    1895                  # result because that will also have amount and unit appended, hence
    1896                  # create the real component or substance instance and take the canonical
    1897                  # substance name from there
    1898                  subst = self._PRW_component.GetValue().strip()
    1899                  if subst != u'':
    1900                          comp = self._PRW_component.GetData(as_instance = True)
    1901                          if comp is None:
    1902                                  self._PRW_aim.set_context(context = u'substance', val = subst)
    1903                                  return
    1904                          self._PRW_aim.set_context(context = u'substance', val = comp['substance'])
    1905                          return
    1906
    1907                  subst = self._PRW_substance.GetValue().strip()
    1908                  if subst == u'':
    1909                          self._PRW_aim.unset_context(context = u'substance')
    1910                          return
    1911                  comp = self._PRW_substance.GetData(as_instance = True)
    1912                  if comp is None:
    1913                          self._PRW_aim.set_context(context = u'substance', val = subst)
    1914                          return
    1915                  self._PRW_aim.set_context(context = u'substance', val = comp['description'])
    1916          #----------------------------------------------------------------
    1917 -        def _on_discontinued_date_changed(self, event):
    1918                  if self._DP_discontinued.GetData() is None:
    1919                          self._PRW_discontinue_reason.Enable(False)
    1920                  else:
    1921                          self._PRW_discontinue_reason.Enable(True)
    1922          #----------------------------------------------------------------
    1923 -        def _on_manage_brands_button_pressed(self, event):
    1924                  manage_branded_drugs(parent = self, ignore_OK_button = True)
    1925          #----------------------------------------------------------------
    1926 -        def _on_manage_substances_button_pressed(self, event):
    1927                  manage_consumable_substances(parent = self)
    1928          #----------------------------------------------------------------
    1929 -        def _on_heart_button_pressed(self, event):
    1930                  gmNetworkTools.open_url_in_browser(url = u'http://qtdrugs.org')
    1931          #----------------------------------------------------------------
    1932 -        def _on_kidneys_button_pressed(self, event):
    1933                  if self._PRW_component.GetData() is not None:
    1934                          search_term = self._PRW_component.GetData(as_instance = True)
    1935                  elif self._PRW_substance.GetData() is not None:
    1936                          search_term = self._PRW_substance.GetData(as_instance = True)
    1937                  elif self._PRW_component.GetValue().strip() != u'':
    1938                          search_term = self._PRW_component.GetValue().strip()
    1939                  else:
    1940                          search_term = self._PRW_substance.GetValue().strip()
    1941
    1942                  gmNetworkTools.open_url_in_browser(url = gmMedication.drug2renal_insufficiency_url(search_term = search_term))
    1943          #----------------------------------------------------------------
    1944 -        def _on_discontinued_as_planned_button_pressed(self, event):
    1945
    1946                  now = gmDateTime.pydt_now_here()
    1947
    1948                  self.__refresh_allergies()
    1949
    1950                  if self.data is None:
    1951                          return
    1952
    1953                  # do we have a (full) plan ?
    1954                  if None not in [self.data['started'], self.data['duration']]:
    1955                          planned_end = self.data['started'] + self.data['duration']
    1956                          # the plan hasn't ended so [Per plan] can't apply ;-)
    1957                          if planned_end > now:
    1958                                  return
    1959                          self._DP_discontinued.SetData(planned_end)
    1960                          self._PRW_discontinue_reason.Enable(True)
    1961                          self._PRW_discontinue_reason.SetValue(u'')
    1962                          return
    1963
    1964                  # we know started but not duration: apparently the plan is to stop today
    1965                  if self.data['started'] is not None:
    1966                          # but we haven't started yet so we can't stop
    1967                          if self.data['started'] > now:
    1968                                  return
    1969
    1970                  self._DP_discontinued.SetData(now)
    1971                  self._PRW_discontinue_reason.Enable(True)
    1972                  self._PRW_discontinue_reason.SetValue(u'')
    1973          #----------------------------------------------------------------
    1974 -        def _on_chbox_long_term_checked(self, event):
    1975                  if self._CHBOX_long_term.IsChecked() is True:
    1976                          self._PRW_duration.Enable(False)
    1977                          self._BTN_discontinued_as_planned.Enable(False)
    1978                          self._PRW_discontinue_reason.Enable(False)
    1979                  else:
    1980                          self._PRW_duration.Enable(True)
    1981                          self._BTN_discontinued_as_planned.Enable(True)
    1982                          self._PRW_discontinue_reason.Enable(True)
    1983
    1984                  self.__refresh_allergies()
    1985          #----------------------------------------------------------------
    1986 -        def turn_into_allergy(self, data=None):
    1987                  if not self.save():
    1988                          return False
    1989
    1990                  return turn_substance_intake_into_allergy (
    1991                          parent = self,
    1992                          intake = self.data,
    1993                          emr = gmPerson.gmCurrentPatient().get_emr()
    1994                  )
    1995
    1996  #============================================================
    1997 -def delete_substance_intake(parent=None, substance=None):
    1998
    1999          subst = gmMedication.cSubstanceIntakeEntry(aPK_obj = substance)
    2000          msg = _(
    2001                  '\n'
    2002                  '[%s]\n'
    2003                  '\n'
    2004                  'It may be prudent to edit (before deletion) the details\n'
    2005                  'of this substance intake entry so as to leave behind\n'
    2006                  'some indication of why it was deleted.\n'
    2007          ) % subst.format()
    2008
    2009          dlg = gmGuiHelpers.c3ButtonQuestionDlg (
    2010                  parent,
    2011                  -1,
    2012                  caption = _('Deleting medication / substance intake'),
    2013                  question = msg,
    2014                  button_defs = [
    2015                          {'label': _('&Edit'), 'tooltip': _('Allow editing of substance intake entry before deletion.'), 'default': True},
    2016                          {'label': _('&Delete'), 'tooltip': _('Delete immediately without editing first.')},
    2017                          {'label': _('&Cancel'), 'tooltip': _('Abort. Do not delete or edit substance intake entry.')}
    2018                  ]
    2019          )
    2020
    2021          edit_first = dlg.ShowModal()
    2022          dlg.Destroy()
    2023
    2024          if edit_first == wx.ID_CANCEL:
    2025                  return
    2026
    2027          if edit_first == wx.ID_YES:
    2028                  edit_intake_of_substance(parent = parent, substance = subst)
    2029                  delete_it = gmGuiHelpers.gm_show_question (
    2030                          aMessage = _('Now delete substance intake entry ?'),
    2031                          aTitle = _('Deleting medication / substance intake')
    2032                  )
    2033          else:
    2034                  delete_it = True
    2035
    2036          if not delete_it:
    2037                  return
    2038
    2039          gmMedication.delete_substance_intake(substance = substance)
    2040  #------------------------------------------------------------
    2041 -def edit_intake_of_substance(parent = None, substance=None):
    2042          ea = cSubstanceIntakeEAPnl(parent = parent, id = -1, substance = substance)
    2043          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (substance is not None))
    2044          dlg.SetTitle(gmTools.coalesce(substance, _('Adding medication/non-medication substance intake'), _('Editing medication/non-medication substance intake')))
    2045          dlg.left_extra_button = (
    2046                  _('Allergy'),
    2047                  _('Document an allergy against this substance.'),
    2048                  ea.turn_into_allergy
    2049          )
    2050          if dlg.ShowModal() == wx.ID_OK:
    2051                  dlg.Destroy()
    2052                  return True
    2053          dlg.Destroy()
    2054          return False
    2055
    2056  #============================================================
    2057  # current substances grid
    2058  #------------------------------------------------------------
    2059 -def configure_medication_list_template(parent=None):
    2060
    2061          if parent is None:
    2062                  parent = wx.GetApp().GetTopWindow()
    2063
    2064          template = gmFormWidgets.manage_form_templates (
    2065                  parent = parent,
    2066                  template_types = ['current medication list']
    2067          )
    2068          option = u'form_templates.medication_list'
    2069
    2070          if template is None:
    2071                  gmDispatcher.send(signal = 'statustext', msg = _('No medication list template configured.'), beep = True)
    2072                  return None
    2073
    2074          if template['engine'] not in [u'L', u'X', u'T']:
    2075                  gmDispatcher.send(signal = 'statustext', msg = _('No medication list template configured.'), beep = True)
    2076                  return None
    2077
    2078          dbcfg = gmCfg.cCfgSQL()
    2079          dbcfg.set (
    2080                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2081                  option = option,
    2082                  value = u'%s - %s' % (template['name_long'], template['external_version'])
    2083          )
    2084
    2085          return template
    2086  #------------------------------------------------------------
    2087 -def print_medication_list(parent=None):
    2088
    2089          if parent is None:
    2090                  parent = wx.GetApp().GetTopWindow()
    2091
    2092          # 1) get template
    2093          dbcfg = gmCfg.cCfgSQL()
    2094          option = u'form_templates.medication_list'
    2095
    2096          template = dbcfg.get2 (
    2097                  option = option,
    2098                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2099                  bias = 'user'
    2100          )
    2101
    2102          if template is None:
    2103                  template = configure_medication_list_template(parent = parent)
    2104                  if template is None:
    2105                          gmGuiHelpers.gm_show_error (
    2106                                  aMessage = _('There is no medication list template configured.'),
    2107                                  aTitle = _('Printing medication list')
    2108                          )
    2109                          return False
    2110          else:
    2111                  try:
    2112                          name, ver = template.split(u' - ')
    2113                  except:
    2114                          _log.exception('problem splitting medication list template name [%s]', template)
    2115                          gmDispatcher.send(signal = 'statustext', msg = _('Problem loading medication list template.'), beep = True)
    2116                          return False
    2117                  template = gmForms.get_form_template(name_long = name, external_version = ver)
    2118                  if template is None:
    2119                          gmGuiHelpers.gm_show_error (
    2120                                  aMessage = _('Cannot load medication list template [%s - %s]') % (name, ver),
    2121                                  aTitle = _('Printing medication list')
    2122                          )
    2123                          return False
    2124
    2125          # 2) process template
    2126          meds_list = gmFormWidgets.generate_form_from_template (
    2127                  parent = parent,
    2128                  template = template,
    2129                  edit = False
    2130          )
    2131          if meds_list is None:
    2132                  return False
    2133
    2134          # 3) print template
    2135          return gmFormWidgets.act_on_generated_forms (
    2136                  parent = parent,
    2137                  forms = [meds_list],
    2138                  jobtype = 'medication_list',
    2139                  #episode_name = u'administrative',
    2140                  episode_name = gmMedication.DEFAULT_MEDICATION_HISTORY_EPISODE,
    2141                  progress_note = _('generated medication list document'),
    2142                  review_copy_as_normal = True
    2143          )
    2144
    2145  #------------------------------------------------------------
    2146 -def configure_prescription_template(parent=None):
    2147
    2148          if parent is None:
    2149                  parent = wx.GetApp().GetTopWindow()
    2150
    2151          template = gmFormWidgets.manage_form_templates (
    2152                  parent = parent,
    2153                  msg = _('Select the default prescription template:'),
    2154                  template_types = ['prescription', 'current medication list']
    2155          )
    2156
    2157          if template is None:
    2158                  gmDispatcher.send(signal = 'statustext', msg = _('No prescription template configured.'), beep = True)
    2159                  return None
    2160
    2161          if template['engine'] not in [u'L', u'X', u'T']:
    2162                  gmDispatcher.send(signal = 'statustext', msg = _('No prescription template configured.'), beep = True)
    2163                  return None
    2164
    2165          option = u'form_templates.prescription'
    2166          dbcfg = gmCfg.cCfgSQL()
    2167          dbcfg.set (
    2168                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2169                  option = option,
    2170                  value = u'%s - %s' % (template['name_long'], template['external_version'])
    2171          )
    2172
    2173          return template
    2174  #------------------------------------------------------------
    2175 -def get_prescription_template(parent=None):
    2176
    2177          if parent is None:
    2178                  parent = wx.GetApp().GetTopWindow()
    2179
    2180          dbcfg = gmCfg.cCfgSQL()
    2181          option = u'form_templates.prescription'
    2182          template_name = dbcfg.get2 (
    2183                  option = option,
    2184                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2185                  bias = 'user'
    2186          )
    2187
    2188          if template_name is None:
    2189                  template = configure_prescription_template(parent = parent)
    2190                  if template is None:
    2191                          gmGuiHelpers.gm_show_error (
    2192                                  aMessage = _('There is no prescription template configured.'),
    2193                                  aTitle = _('Printing prescription')
    2194                          )
    2195                          return None
    2196                  return template
    2197
    2198          try:
    2199                  name, ver = template_name.split(u' - ')
    2200          except:
    2201                  _log.exception('problem splitting prescription template name [%s]', template_name)
    2202                  gmDispatcher.send(signal = 'statustext', msg = _('Problem loading prescription template.'), beep = True)
    2203                  return False
    2204          template = gmForms.get_form_template(name_long = name, external_version = ver)
    2205          if template is None:
    2206                  gmGuiHelpers.gm_show_error (
    2207                          aMessage = _('Cannot load prescription template [%s - %s]') % (name, ver),
    2208                          aTitle = _('Printing prescription')
    2209                  )
    2210                  return None
    2211          return template
    2212  #------------------------------------------------------------
    2213 -def print_prescription(parent=None, emr=None):
    2214
    2215          # 1) get template
    2216          rx_template = get_prescription_template(parent = parent)
    2217          if rx_template is None:
    2218                  return False
    2219
    2220          # 2) process template
    2221          rx = gmFormWidgets.generate_form_from_template (
    2222                  parent = parent,
    2223                  template = rx_template,
    2224                  edit = False
    2225          )
    2226          if rx is None:
    2227                  return False
    2228
    2229          # 3) print template
    2230          return gmFormWidgets.act_on_generated_forms (
    2231                  parent = parent,
    2232                  forms = [rx],
    2233                  jobtype = u'prescription',
    2234                  #episode_name = u'administrative',
    2235                  episode_name = gmMedication.DEFAULT_MEDICATION_HISTORY_EPISODE,
    2236                  progress_note = _('generated prescription'),
    2237                  review_copy_as_normal = True
    2238          )
    2239
    2240  #------------------------------------------------------------
    2241 -def prescribe_drugs(parent=None, emr=None):
    2242
    2243          dbcfg = gmCfg.cCfgSQL()
    2244          rx_mode = dbcfg.get2 (
    2245                  option = u'horst_space.default_prescription_mode',
    2246                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2247                  bias = u'user',
    2248                  default = u'form'                       # set to 'database' to access database
    2249          )
    2250
    2251          if parent is None:
    2252                  parent = wx.GetApp().GetTopWindow()
    2253
    2254          if rx_mode == 'form':
    2255                  return print_prescription(parent = parent, emr = emr)
    2256
    2257          if rx_mode == 'database':
    2258                  drug_db = get_drug_database()
    2259                  if drug_db is None:
    2260                          return
    2261                  drug_db.reviewer = gmStaff.gmCurrentProvider()
    2262                  prescribed_drugs = drug_db.prescribe()
    2263                  update_substance_intake_list_from_prescription (
    2264                          parent = parent,
    2265                          prescribed_drugs = prescribed_drugs,
    2266                          emr = emr
    2267                  )
    2268
    2269  #------------------------------------------------------------
    2270 -def update_substance_intake_list_from_prescription(parent=None, prescribed_drugs=None, emr=None):
    2271
    2272          if len(prescribed_drugs) == 0:
    2273                  return
    2274
    2275          curr_brands =  [ i['pk_brand'] for i in emr.get_current_substance_intakes() if i['pk_brand'] is not None ]
    2276          new_drugs = []
    2277          for drug in prescribed_drugs:
    2278                  if drug['pk_brand'] not in curr_brands:
    2279                          new_drugs.append(drug)
    2280
    2281          if len(new_drugs) == 0:
    2282                  return
    2283
    2284          if parent is None:
    2285                  parent = wx.GetApp().GetTopWindow()
    2286
    2287          dlg = gmListWidgets.cItemPickerDlg (
    2288                  parent,
    2289                  -1,
    2290                  msg = _(
    2291                          'These brands have been prescribed but are not listed\n'
    2292                          'in the current medication list of this patient.\n'
    2293                          '\n'
    2294                          'Please select those you want added to the medication list.'
    2295                  )
    2296          )
    2297          dlg.set_columns (
    2298                  columns = [_('Newly prescribed drugs')],
    2299                  columns_right = [_('Add to medication list')]
    2300          )
    2301          choices = [ (u'%s %s (%s)' % (d['brand'], d['preparation'], u'; '.join(d['components']))) for d in new_drugs ]
    2302          dlg.set_choices (
    2303                  choices = choices,
    2304                  data = new_drugs
    2305          )
    2306          dlg.ShowModal()
    2307          drugs2add = dlg.get_picks()
    2308          dlg.Destroy()
    2309
    2310          if drugs2add is None:
    2311                  return
    2312
    2313          if len(drugs2add) == 0:
    2314                  return
    2315
    2316          for drug in drugs2add:
    2317                  # only add first component since all other components get added by a trigger ...
    2318                  intake = emr.add_substance_intake (
    2319                          pk_component = drug['pk_components'][0],
    2320                          episode = emr.add_episode(episode_name = gmMedication.DEFAULT_MEDICATION_HISTORY_EPISODE)['pk_episode'],
    2321                  )
    2322                  if intake is None:
    2323                          continue
    2324                  intake['intake_is_approved_of'] = True
    2325                  intake.save()
    2326
    2327          return
    2328  #------------------------------------------------------------
    2329 -class cCurrentSubstancesGrid(wx.grid.Grid):
    2330          """A grid class for displaying current substance intake.
    2331
    2332          - does NOT listen to the currently active patient
    2333          - thereby it can display any patient at any time
    2334          """
    2335 -        def __init__(self, *args, **kwargs):
    2336
    2337                  wx.grid.Grid.__init__(self, *args, **kwargs)
    2338
    2339                  self.__patient = None
    2340                  self.__row_data = {}
    2341                  self.__prev_row = None
    2342                  self.__prev_tooltip_row = None
    2343                  self.__prev_cell_0 = None
    2344                  self.__grouping_mode = u'issue'
    2345                  self.__filter_show_unapproved = True
    2346                  self.__filter_show_inactive = True
    2347
    2348                  self.__grouping2col_labels = {
    2349                          u'issue': [
    2350                                  _('Health issue'),
    2351                                  _('Substance'),
    2352                                  _('Strength'),
    2353                                  _('Schedule'),
    2354                                  _('Started'),
    2355                                  _('Duration / Until'),
    2356                                  _('Brand'),
    2357                                  _('Advice')
    2358                          ],
    2359                          u'brand': [
    2360                                  _('Brand'),
    2361                                  _('Schedule'),
    2362                                  _('Substance'),
    2363                                  _('Strength'),
    2364                                  _('Started'),
    2365                                  _('Duration / Until'),
    2366                                  _('Health issue'),
    2367                                  _('Advice')
    2368                          ],
    2369                          u'episode': [
    2370                                  _('Episode'),
    2371                                  _('Substance'),
    2372                                  _('Strength'),
    2373                                  _('Schedule'),
    2374                                  _('Started'),
    2375                                  _('Duration / Until'),
    2376                                  _('Brand'),
    2377                                  _('Advice')
    2378                          ]
    2379                  }
    2380
    2381                  self.__grouping2order_by_clauses = {
    2382                          u'issue': u'pk_health_issue nulls first, substance, started',
    2383                          u'episode': u'pk_health_issue nulls first, episode, substance, started',
    2384                          u'brand': u'brand nulls last, substance, started'
    2385                  }
    2386
    2387                  self.__init_ui()
    2388                  self.__register_events()
    2389          #------------------------------------------------------------
    2390          # external API
    2391          #------------------------------------------------------------
    2392 -        def get_selected_cells(self):
    2393
    2394                  sel_block_top_left = self.GetSelectionBlockTopLeft()
    2395                  sel_block_bottom_right = self.GetSelectionBlockBottomRight()
    2396                  sel_cols = self.GetSelectedCols()
    2397                  sel_rows = self.GetSelectedRows()
    2398
    2399                  selected_cells = []
    2400
    2401                  # individually selected cells (ctrl-click)
    2402                  selected_cells += self.GetSelectedCells()
    2403
    2404                  # selected rows
    2405                  selected_cells += list (
    2406                          (row, col)
    2407                                  for row in sel_rows
    2408                                  for col in xrange(self.GetNumberCols())
    2409                  )
    2410
    2411                  # selected columns
    2412                  selected_cells += list (
    2413                          (row, col)
    2414                                  for row in xrange(self.GetNumberRows())
    2415                                  for col in sel_cols
    2416                  )
    2417
    2418                  # selection blocks
    2419                  for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
    2420                          selected_cells += [
    2421                                  (row, col)
    2422                                          for row in xrange(top_left[0], bottom_right[0] + 1)
    2423                                          for col in xrange(top_left[1], bottom_right[1] + 1)
    2424                          ]
    2425
    2426                  return set(selected_cells)
    2427          #------------------------------------------------------------
    2428 -        def get_selected_rows(self):
    2429                  rows = {}
    2430
    2431                  for row, col in self.get_selected_cells():
    2432                          rows[row] = True
    2433
    2434                  return rows.keys()
    2435          #------------------------------------------------------------
    2436 -        def get_selected_data(self):
    2437                  return [ self.__row_data[row] for row in self.get_selected_rows() ]
    2438          #------------------------------------------------------------
    2439 -        def repopulate_grid(self):
    2440
    2441                  self.empty_grid()
    2442
    2443                  if self.__patient is None:
    2444                          return
    2445
    2446                  emr = self.__patient.get_emr()
    2447                  meds = emr.get_current_substance_intakes (
    2448                          order_by = self.__grouping2order_by_clauses[self.__grouping_mode],
    2449                          include_unapproved = self.__filter_show_unapproved,
    2450                          include_inactive = self.__filter_show_inactive
    2451                  )
    2452                  if not meds:
    2453                          return
    2454
    2455                  self.BeginBatch()
    2456
    2457                  # columns
    2458                  labels = self.__grouping2col_labels[self.__grouping_mode]
    2459                  if self.__filter_show_unapproved:
    2460                          self.AppendCols(numCols = len(labels) + 1)
    2461                  else:
    2462                          self.AppendCols(numCols = len(labels))
    2463                  for col_idx in range(len(labels)):
    2464                          self.SetColLabelValue(col_idx, labels[col_idx])
    2465                  if self.__filter_show_unapproved:
    2466                          #self.SetColLabelValue(len(labels), u'OK?')
    2467                          self.SetColLabelValue(len(labels), u'')
    2468                          self.SetColSize(len(labels), 40)
    2469
    2470                  self.AppendRows(numRows = len(meds))
    2471
    2472                  # loop over data
    2473                  for row_idx in range(len(meds)):
    2474                          med = meds[row_idx]
    2475                          self.__row_data[row_idx] = med
    2476
    2477                          if med['is_currently_active'] is True:
    2478                                  atcs = []
    2479                                  if med['atc_substance'] is not None:
    2480                                          atcs.append(med['atc_substance'])
    2481  #                               if med['atc_brand'] is not None:
    2482  #                                       atcs.append(med['atc_brand'])
    2483  #                               allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],), brand = med['brand'])
    2484                                  allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],))
    2485                                  if allg not in [None, False]:
    2486                                          attr = self.GetOrCreateCellAttr(row_idx, 0)
    2487                                          if allg['type'] == u'allergy':
    2488                                                  attr.SetTextColour('red')
    2489                                          else:
    2490                                                  #attr.SetTextColour('yellow')           # too light
    2491                                                  #attr.SetTextColour('pink')                     # too light
    2492                                                  #attr.SetTextColour('dark orange')      # slightly better
    2493                                                  attr.SetTextColour('magenta')
    2494                                          self.SetRowAttr(row_idx, attr)
    2495                          else:
    2496                                  attr = self.GetOrCreateCellAttr(row_idx, 0)
    2497                                  attr.SetTextColour('grey')
    2498                                  self.SetRowAttr(row_idx, attr)
    2499
    2500                          if self.__grouping_mode == u'episode':
    2501                                  if med['pk_episode'] is None:
    2502                                          self.__prev_cell_0 = None
    2503                                          epi = gmTools.u_diameter
    2504                                  else:
    2505                                          if self.__prev_cell_0 == med['episode']:
    2506                                                  epi = u''
    2507                                          else:
    2508                                                  self.__prev_cell_0 = med['episode']
    2509                                                  epi = gmTools.coalesce(med['episode'], u'')
    2510                                  self.SetCellValue(row_idx, 0, gmTools.wrap(text = epi, width = 40))
    2511
    2512                                  self.SetCellValue(row_idx, 1, med['substance'])
    2513                                  self.SetCellValue(row_idx, 2, u'%s %s' % (med['amount'], med['unit']))
    2514                                  self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], u''))
    2515                                  self.SetCellValue(row_idx, 4, med.medically_formatted_start)
    2516
    2517                                  if med['is_long_term']:
    2518                                          self.SetCellValue(row_idx, 5, gmTools.u_infinity)
    2519                                  else:
    2520                                          if med['discontinued'] is None:
    2521                                                  if med['duration'] is None:
    2522                                                          self.SetCellValue(row_idx, 5, u'')
    2523                                                  else:
    2524                                                          self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days))
    2525                                          else:
    2526                                                  self.SetCellValue(row_idx, 5, med['discontinued'].strftime('%Y-%m-%d'))
    2527
    2528                                  if med['pk_brand'] is None:
    2529                                          brand = u'%s (%s)' % (gmTools.u_diameter, med['preparation'])
    2530                                  else:
    2531                                          if med['fake_brand']:
    2532                                                  brand = u'%s (%s)' % (
    2533                                                          gmTools.coalesce(med['brand'], u'', _('%s <fake>')),
    2534                                                          med['preparation']
    2535                                                  )
    2536                                          else:
    2537                                                  brand = u'%s (%s)' % (
    2538                                                          gmTools.coalesce(med['brand'], u''),
    2539                                                          med['preparation']
    2540                                                  )
    2541                                  self.SetCellValue(row_idx, 6, gmTools.wrap(text = brand, width = 35))
    2542
    2543                          elif self.__grouping_mode == u'issue':
    2544                                  if med['pk_health_issue'] is None:
    2545                                          self.__prev_cell_0 = None
    2546                                          issue = u'%s%s' % (
    2547                                                  gmTools.u_diameter,
    2548                                                  gmTools.coalesce(med['episode'], u'', u' (%s)')
    2549                                          )
    2550                                  else:
    2551                                          if self.__prev_cell_0 == med['health_issue']:
    2552                                                  issue = u''
    2553                                          else:
    2554                                                  self.__prev_cell_0 = med['health_issue']
    2555                                                  issue = med['health_issue']
    2556                                  self.SetCellValue(row_idx, 0, gmTools.wrap(text = issue, width = 40))
    2557
    2558                                  self.SetCellValue(row_idx, 1, med['substance'])
    2559                                  self.SetCellValue(row_idx, 2, u'%s %s' % (med['amount'], med['unit']))
    2560                                  self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], u''))
    2561                                  self.SetCellValue(row_idx, 4, med.medically_formatted_start)
    2562
    2563                                  if med['is_long_term']:
    2564                                          self.SetCellValue(row_idx, 5, gmTools.u_infinity)
    2565                                  else:
    2566                                          if med['discontinued'] is None:
    2567                                                  if med['duration'] is None:
    2568                                                          self.SetCellValue(row_idx, 5, u'')
    2569                                                  else:
    2570                                                          self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days))
    2571                                          else:
    2572                                                  self.SetCellValue(row_idx, 5, med['discontinued'].strftime('%Y-%m-%d'))
    2573
    2574                                  if med['pk_brand'] is None:
    2575                                          brand = u'%s (%s)' % (gmTools.u_diameter, med['preparation'])
    2576                                  else:
    2577                                          if med['fake_brand']:
    2578                                                  brand = u'%s (%s)' % (
    2579                                                          gmTools.coalesce(med['brand'], u'', _('%s <fake>')),
    2580                                                          med['preparation']
    2581                                                  )
    2582                                          else:
    2583                                                  brand = u'%s (%s)' % (
    2584                                                          gmTools.coalesce(med['brand'], u''),
    2585                                                          med['preparation']
    2586                                                  )
    2587                                  self.SetCellValue(row_idx, 6, gmTools.wrap(text = brand, width = 35))
    2588
    2589                          elif self.__grouping_mode == u'brand':
    2590
    2591                                  if med['pk_brand'] is None:
    2592                                          self.__prev_cell_0 = None
    2593                                          brand =  u'%s (%s)' % (
    2594                                                  gmTools.u_diameter,
    2595                                                  med['preparation']
    2596                                          )
    2597                                  else:
    2598                                          if self.__prev_cell_0 == med['brand']:
    2599                                                  brand = u''
    2600                                          else:
    2601                                                  self.__prev_cell_0 = med['brand']
    2602                                                  if med['fake_brand']:
    2603                                                          brand = u'%s (%s)' % (
    2604                                                                  gmTools.coalesce(med['brand'], u'', _('%s <fake>')),
    2605                                                                  med['preparation']
    2606                                                          )
    2607                                                  else:
    2608                                                          brand = u'%s (%s)' % (
    2609                                                                  gmTools.coalesce(med['brand'], u''),
    2610                                                                  med['preparation']
    2611                                                          )
    2612                                  self.SetCellValue(row_idx, 0, gmTools.wrap(text = brand, width = 35))
    2613
    2614                                  self.SetCellValue(row_idx, 1, gmTools.coalesce(med['schedule'], u''))
    2615                                  self.SetCellValue(row_idx, 2, med['substance'])
    2616                                  self.SetCellValue(row_idx, 3, u'%s %s' % (med['amount'], med['unit']))
    2617                                  self.SetCellValue(row_idx, 4, med.medically_formatted_start)
    2618
    2619                                  if med['is_long_term']:
    2620                                          self.SetCellValue(row_idx, 5, gmTools.u_infinity)
    2621                                  else:
    2622                                          if med['discontinued'] is None:
    2623                                                  if med['duration'] is None:
    2624                                                          self.SetCellValue(row_idx, 5, u'')
    2625                                                  else:
    2626                                                          self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days))
    2627                                          else:
    2628                                                  self.SetCellValue(row_idx, 5, med['discontinued'].strftime('%Y-%m-%d'))
    2629
    2630                                  if med['pk_health_issue'] is None:
    2631                                          issue = u'%s%s' % (
    2632                                                  gmTools.u_diameter,
    2633                                                  gmTools.coalesce(med['episode'], u'', u' (%s)')
    2634                                          )
    2635                                  else:
    2636                                          issue = gmTools.coalesce(med['health_issue'], u'')
    2637                                  self.SetCellValue(row_idx, 6, gmTools.wrap(text = issue, width = 40))
    2638
    2639                          else:
    2640                                  raise ValueError('unknown grouping mode [%s]' % self.__grouping_mode)
    2641
    2642                          if med['notes'] is not None:
    2643                                  self.SetCellValue(row_idx, 7, gmTools.wrap(text = med['notes'], width = 50))
    2644
    2645                          if self.__filter_show_unapproved:
    2646                                  self.SetCellValue (
    2647                                          row_idx,
    2648                                          len(labels),
    2649                                          #gmTools.bool2subst(med['intake_is_approved_of'], gmTools.u_checkmark_thin, u'', u'?')
    2650                                          gmTools.bool2subst(med['intake_is_approved_of'], gmTools.u_checkmark_thin, gmTools.u_frowning_face, u'?')
    2651                                  )
    2652                                  font = self.GetCellFont(row_idx, len(labels))
    2653                                  font.SetPointSize(font.GetPointSize() + 2)
    2654                                  self.SetCellFont(row_idx, len(labels), font)
    2655
    2656                          #self.SetCellAlignment(row, col, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
    2657
    2658                  self.AutoSize()
    2659                  self.EndBatch()
    2660          #------------------------------------------------------------
    2661 -        def empty_grid(self):
    2662                  self.BeginBatch()
    2663                  self.ClearGrid()
    2664                  # Windows cannot do "nothing", it rather decides to assert()
    2665                  # on thinking it is supposed to do nothing
    2666                  if self.GetNumberRows() > 0:
    2667                          self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
    2668                  if self.GetNumberCols() > 0:
    2669                          self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
    2670                  self.EndBatch()
    2671                  self.__row_data = {}
    2672                  self.__prev_cell_0 = None
    2673          #------------------------------------------------------------
    2674 -        def show_info_on_entry(self):
    2675
    2676                  if len(self.__row_data) == 0:
    2677                          return
    2678
    2679                  sel_rows = self.get_selected_rows()
    2680                  if len(sel_rows) != 1:
    2681                          return
    2682
    2683                  drug_db = get_drug_database()
    2684                  if drug_db is None:
    2685                          return
    2686
    2687                  intake = self.get_selected_data()[0]            # just in case
    2688                  if intake['brand'] is None:
    2689                          drug_db.show_info_on_substance(substance_intake = intake)
    2690                  else:
    2691                          drug_db.show_info_on_drug(substance_intake = intake)
    2692          #------------------------------------------------------------
    2693 -        def show_renal_insufficiency_info(self):
    2694                  search_term = None
    2695                  if len(self.__row_data) > 0:
    2696                          sel_rows = self.get_selected_rows()
    2697                          if len(sel_rows) == 1:
    2698                                  search_term = self.get_selected_data()[0]
    2699                  gmNetworkTools.open_url_in_browser(url = gmMedication.drug2renal_insufficiency_url(search_term = search_term))
    2700          #------------------------------------------------------------
    2701 -        def show_cardiac_info(self):
    2702                  gmNetworkTools.open_url_in_browser(url = u'http://qtdrugs.org')
    2703          #------------------------------------------------------------
    2704 -        def report_ADR(self):
    2705                  dbcfg = gmCfg.cCfgSQL()
    2706                  url = dbcfg.get2 (
    2707                          option = u'external.urls.report_ADR',
    2708                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2709                          bias = u'user',
    2710                          default = u'https://dcgma.org/uaw/meldung.php'          # http://www.akdae.de/Arzneimittelsicherheit/UAW-Meldung/UAW-Meldung-online.html
    2711                  )
    2712                  gmNetworkTools.open_url_in_browser(url = url)
    2713          #------------------------------------------------------------
    2714 -        def prescribe(self):
    2715                  prescribe_drugs (
    2716                          parent = self,
    2717                          emr = self.__patient.emr
    2718                  )
    2719          #------------------------------------------------------------
    2720 -        def check_interactions(self):
    2721
    2722                  if len(self.__row_data) == 0:
    2723                          return
    2724
    2725                  drug_db = get_drug_database()
    2726                  if drug_db is None:
    2727                          return
    2728
    2729                  if len(self.get_selected_rows()) > 1:
    2730                          drug_db.check_interactions(substance_intakes = self.get_selected_data())
    2731                  else:
    2732                          drug_db.check_interactions(substance_intakes = self.__row_data.values())
    2733          #------------------------------------------------------------
    2734 -        def add_substance(self):
    2735                  edit_intake_of_substance(parent = self, substance = None)
    2736          #------------------------------------------------------------
    2737 -        def edit_substance(self):
    2738
    2739                  rows = self.get_selected_rows()
    2740
    2741                  if len(rows) == 0:
    2742                          return
    2743
    2744                  if len(rows) > 1:
    2745                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit more than one substance at once.'), beep = True)
    2746                          return
    2747
    2748                  subst = self.get_selected_data()[0]
    2749                  edit_intake_of_substance(parent = self, substance = subst)
    2750          #------------------------------------------------------------
    2751 -        def delete_substance(self):
    2752
    2753                  rows = self.get_selected_rows()
    2754
    2755                  if len(rows) == 0:
    2756                          return
    2757
    2758                  if len(rows) > 1:
    2759                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete more than one substance at once.'), beep = True)
    2760                          return
    2761
    2762                  subst = self.get_selected_data()[0]
    2763                  delete_substance_intake(parent = self, substance = subst['pk_substance_intake'])
    2764          #------------------------------------------------------------
    2765 -        def create_allergy_from_substance(self):
    2766                  rows = self.get_selected_rows()
    2767
    2768                  if len(rows) == 0:
    2769                          return
    2770
    2771                  if len(rows) > 1:
    2772                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot create allergy from more than one substance at once.'), beep = True)
    2773                          return
    2774
    2775                  return turn_substance_intake_into_allergy (
    2776                          parent = self,
    2777                          intake = self.get_selected_data()[0],
    2778                          emr = self.__patient.get_emr()
    2779                  )
    2780          #------------------------------------------------------------
    2781 -        def print_medication_list(self):
    2782                  # there could be some filtering/user interaction going on here
    2783                  print_medication_list(parent = self)
    2784          #------------------------------------------------------------
    2785 -        def get_row_tooltip(self, row=None):
    2786
    2787                  try:
    2788                          entry = self.__row_data[row]
    2789                  except KeyError:
    2790                          return u' '
    2791
    2792                  emr = self.__patient.get_emr()
    2793                  atcs = []
    2794                  if entry['atc_substance'] is not None:
    2795                          atcs.append(entry['atc_substance'])
    2796  #               if entry['atc_brand'] is not None:
    2797  #                       atcs.append(entry['atc_brand'])
    2798  #               allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (entry['substance'],), brand = entry['brand'])
    2799                  allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (entry['substance'],))
    2800
    2801                  tt = _('Substance intake entry (%s, %s)   [#%s]                     \n') % (
    2802                          gmTools.bool2subst (
    2803                                  boolean = entry['is_currently_active'],
    2804                                  true_return = gmTools.bool2subst (
    2805                                          boolean = entry['seems_inactive'],
    2806                                          true_return = _('active, needs check'),
    2807                                          false_return = _('active'),
    2808                                          none_return = _('assumed active')
    2809                                  ),
    2810                                  false_return = _('inactive')
    2811                          ),
    2812                          gmTools.bool2subst (
    2813                                  boolean = entry['intake_is_approved_of'],
    2814                                  true_return = _('approved'),
    2815                                  false_return = _('unapproved')
    2816                          ),
    2817                          entry['pk_substance_intake']
    2818                  )
    2819
    2820                  if allg not in [None, False]:
    2821                          certainty = gmTools.bool2subst(allg['definite'], _('definite'), _('suspected'))
    2822                          tt += u'\n'
    2823                          tt += u' !! ---- Cave ---- !!\n'
    2824                          tt += u' %s (%s): %s (%s)\n' % (
    2825                                  allg['l10n_type'],
    2826                                  certainty,
    2827                                  allg['descriptor'],
    2828                                  gmTools.coalesce(allg['reaction'], u'')[:40]
    2829                          )
    2830                          tt += u'\n'
    2831
    2832                  tt += u' ' + _('Substance: %s   [#%s]\n') % (entry['substance'], entry['pk_substance'])
    2833                  tt += u' ' + _('Preparation: %s\n') % entry['preparation']
    2834                  tt += u' ' + _('Amount per dose: %s %s') % (entry['amount'], entry['unit'])
    2835                  tt += u'\n'
    2836                  tt += gmTools.coalesce(entry['atc_substance'], u'', _(' ATC (substance): %s\n'))
    2837
    2838                  tt += u'\n'
    2839
    2840                  tt += gmTools.coalesce (
    2841                          entry['brand'],
    2842                          u'',
    2843                          _(' Brand name: %%s   [#%s]\n') % entry['pk_brand']
    2844                  )
    2845                  tt += gmTools.coalesce(entry['atc_brand'], u'', _(' ATC (brand): %s\n'))
    2846
    2847                  tt += u'\n'
    2848
    2849                  tt += gmTools.coalesce(entry['schedule'], u'', _(' Regimen: %s\n'))
    2850
    2851                  if entry['is_long_term']:
    2852                          duration = u' %s %s' % (gmTools.u_right_arrow, gmTools.u_infinity)
    2853                  else:
    2854                          if entry['duration'] is None:
    2855                                  duration = u''
    2856                          else:
    2857                                  duration = u' %s %s' % (gmTools.u_right_arrow, gmDateTime.format_interval(entry['duration'], gmDateTime.acc_days))
    2858
    2859                  tt += _(' Started %s%s%s\n') % (
    2860                          gmDateTime.pydt_strftime(entry['started'], '%Y %b %d'),
    2861                          duration,
    2862                          gmTools.bool2subst(entry['is_long_term'], _(' (long-term)'), _(' (short-term)'), u'')
    2863                  )
    2864
    2865                  if entry['discontinued'] is not None:
    2866                          tt += _(' Discontinued %s\n') % gmDateTime.pydt_strftime(entry['discontinued'], '%Y %b %d')
    2867                          tt += _(' Reason: %s\n') % entry['discontinue_reason']
    2868
    2869                  tt += u'\n'
    2870
    2871                  tt += gmTools.coalesce(entry['aim'], u'', _(' Aim: %s\n'))
    2872                  tt += gmTools.coalesce(entry['episode'], u'', _(' Episode: %s\n'))
    2873                  tt += gmTools.coalesce(entry['health_issue'], u'', _(' Health issue: %s\n'))
    2874                  tt += gmTools.coalesce(entry['notes'], u'', _(' Advice: %s\n'))
    2875
    2876                  tt += u'\n'
    2877
    2878                  tt += _(u'Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % ({
    2879                          'row_ver': entry['row_version'],
    2880                          'mod_when': gmDateTime.pydt_strftime(entry['modified_when'], '%Y %b %d  %H:%M:%S'),
    2881                          'mod_by': entry['modified_by']
    2882                  })
    2883
    2884                  return tt
    2885          #------------------------------------------------------------
    2886          # internal helpers
    2887          #------------------------------------------------------------
    2888 -        def __init_ui(self):
    2889                  self.CreateGrid(0, 1)
    2890                  self.EnableEditing(0)
    2891                  self.EnableDragGridSize(1)
    2892                  self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows)
    2893
    2894                  self.SetColLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTER)
    2895
    2896                  self.SetRowLabelSize(0)
    2897                  self.SetRowLabelAlignment(horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
    2898          #------------------------------------------------------------
    2899          # properties
    2900          #------------------------------------------------------------
    2901 -        def _get_patient(self):
    2902                  return self.__patient
    2903
    2904 -        def _set_patient(self, patient):
    2905                  self.__patient = patient
    2906                  self.repopulate_grid()
    2907
    2908          patient = property(_get_patient, _set_patient)
    2909          #------------------------------------------------------------
    2910 -        def _get_grouping_mode(self):
    2911                  return self.__grouping_mode
    2912
    2913 -        def _set_grouping_mode(self, mode):
    2914                  self.__grouping_mode = mode
    2915                  self.repopulate_grid()
    2916
    2917          grouping_mode = property(_get_grouping_mode, _set_grouping_mode)
    2918          #------------------------------------------------------------
    2919 -        def _get_filter_show_unapproved(self):
    2920                  return self.__filter_show_unapproved
    2921
    2922 -        def _set_filter_show_unapproved(self, val):
    2923                  self.__filter_show_unapproved = val
    2924                  self.repopulate_grid()
    2925
    2926          filter_show_unapproved = property(_get_filter_show_unapproved, _set_filter_show_unapproved)
    2927          #------------------------------------------------------------
    2928 -        def _get_filter_show_inactive(self):
    2929                  return self.__filter_show_inactive
    2930
    2931 -        def _set_filter_show_inactive(self, val):
    2932                  self.__filter_show_inactive = val
    2933                  self.repopulate_grid()
    2934
    2935          filter_show_inactive = property(_get_filter_show_inactive, _set_filter_show_inactive)
    2936          #------------------------------------------------------------
    2937          # event handling
    2938          #------------------------------------------------------------
    2939 -        def __register_events(self):
    2940                  # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow
    2941                  self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
    2942                  #self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
    2943                  #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels)
    2944
    2945                  # editing cells
    2946                  self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
    2947          #------------------------------------------------------------
    2948 -        def __on_mouse_over_cells(self, evt):
    2949                  """Calculate where the mouse is and set the tooltip dynamically."""
    2950
    2951                  # Use CalcUnscrolledPosition() to get the mouse position within the
    2952                  # entire grid including what's offscreen
    2953                  x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
    2954
    2955                  # use this logic to prevent tooltips outside the actual cells
    2956                  # apply to GetRowSize, too
    2957  #        tot = 0
    2958  #        for col in xrange(self.NumberCols):
    2959  #            tot += self.GetColSize(col)
    2960  #            if xpos <= tot:
    2961  #                self.tool_tip.Tip = 'Tool tip for Column %s' % (
    2962  #                    self.GetColLabelValue(col))
    2963  #                break
    2964  #            else:  # mouse is in label area beyond the right-most column
    2965  #            self.tool_tip.Tip = ''
    2966
    2967                  row, col = self.XYToCell(x, y)
    2968
    2969                  if row == self.__prev_tooltip_row:
    2970                          return
    2971
    2972                  self.__prev_tooltip_row = row
    2973
    2974                  try:
    2975                          evt.GetEventObject().SetToolTipString(self.get_row_tooltip(row = row))
    2976                  except KeyError:
    2977                          pass
    2978          #------------------------------------------------------------
    2979 -        def __on_cell_left_dclicked(self, evt):
    2980                  row = evt.GetRow()
    2981                  data = self.__row_data[row]
    2982                  edit_intake_of_substance(parent = self, substance = data)
    2983
    2984  #============================================================
    2985 -def configure_default_medications_lab_panel(parent=None):
    2986
    2987          panels = gmPathLab.get_test_panels(order_by = u'description')
    2988          gmCfgWidgets.configure_string_from_list_option (
    2989                  parent = parent,
    2990                  message = _(
    2991                          '\n'
    2992                          'Select the measurements panel to show in the medications plugin.'
    2993                          '\n'
    2994                  ),
    2995                  option = u'horstspace.medications_plugin.lab_panel',
    2996                  bias = 'user',
    2997                  default_value = None,
    2998                  choices = [ u'%s%s' % (p['description'], gmTools.coalesce(p['comment'], u'', u' (%s)')) for p in panels ],
    2999                  columns = [_('Measurements panel')],
    3000                  data = [ p['pk_test_panel'] for p in panels ],
    3001                  caption = _('Configuring medications plugin measurements panel')
    3002          )
    3003
    3004  #============================================================
    3005  from Gnumed.wxGladeWidgets import wxgCurrentSubstancesPnl
    3006
    3007 -class cCurrentSubstancesPnl(wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl, gmRegetMixin.cRegetOnPaintMixin):
    3008
    3009          """Panel holding a grid with current substances. Used as notebook page."""
    3010
    3011 -        def __init__(self, *args, **kwargs):
    3012
    3013                  wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl.__init__(self, *args, **kwargs)
    3014                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    3015
    3016                  self.__lab_panel = None
    3017                  self.__lab_default_text_color = self._TCTRL_lab.GetForegroundColour()
    3018
    3019                  self.__register_interests()
    3020          #-----------------------------------------------------
    3021          # reget-on-paint mixin API
    3022          #-----------------------------------------------------
    3023 -        def _populate_with_data(self):
    3024                  """Populate cells with data from model."""
    3025                  pat = gmPerson.gmCurrentPatient()
    3026                  if pat.connected:
    3027                          self._grid_substances.patient = pat
    3028                          self.__refresh_gfr(pat)
    3029                          self.__refresh_lab(patient = pat)
    3030                  else:
    3031                          self._grid_substances.patient = None
    3032                          self.__clear_gfr()
    3033                          self.__refresh_lab(patient = None)
    3034                  return True
    3035          #--------------------------------------------------------
    3036 -        def __refresh_lab(self, patient):
    3037                  self._TCTRL_lab.SetDefaultStyle(wx.TextAttr(self.__lab_default_text_color))
    3038                  self._TCTRL_lab.SetValue(u'')
    3039                  self._TCTRL_lab.Hide()
    3040
    3041                  if patient is None:
    3042                          self.Layout()
    3043                          return
    3044
    3045                  if self.__lab_panel is None:
    3046                          self.Layout()
    3047                          return
    3048
    3049                  results = self.__lab_panel.get_most_recent_results(pk_patient = patient.ID, order_by = u'unified_abbrev')
    3050                  if len(results) == 0:
    3051                          self.Layout()
    3052                          return
    3053
    3054                  now = gmDateTime.pydt_now_here()
    3055
    3056                  # look for GFR
    3057                  gfr = patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_gfr_quantity, no_of_results = 1)
    3058                  crea = patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_creatinine_quantity, no_of_results = 1)
    3059                  if crea is None:
    3060                          gfr_3_months_older_than_crea = False
    3061                  else:
    3062                          three_months = pydt.timedelta(weeks = 14)
    3063                          gfr_3_months_older_than_crea = (crea['clin_when'] - gfr['clin_when']) > three_months
    3064                  # if GFR not found in results or old, then calculate
    3065                  if (gfr is None) or gfr_3_months_older_than_crea:
    3066                          calc = gmClinicalCalculator.cClinicalCalculator()
    3067                          calc.patient = patient
    3068                          gfr = calc.eGFR
    3069                          if gfr.numeric_value is None:
    3070                                  gfr_msg = u'?'
    3071                          else:
    3072                                  gfr_msg = _(u'%.1f (%s ago)') % (
    3073                                          gfr.numeric_value,
    3074                                          gmDateTime.format_interval_medically(now - gfr.date_valid)
    3075                                          #gmDateTime.pydt_strftime (gfr.date_valid, format = '%b %Y')
    3076                                  )
    3077                          self._TCTRL_lab.SetDefaultStyle(wx.TextAttr('blue'))
    3078                          self._TCTRL_lab.AppendText(_('eGFR:'))
    3079                          self._TCTRL_lab.SetDefaultStyle(wx.TextAttr(self.__lab_default_text_color))
    3080                          self._TCTRL_lab.AppendText(u' ' + gfr_msg)
    3081                          self._TCTRL_lab.AppendText(u' || ')
    3082
    3083                  for most_recent in results:
    3084                          if most_recent.is_considered_abnormal:
    3085                                  self._TCTRL_lab.SetDefaultStyle(wx.TextAttr('red'))
    3086                                  txt = _('%s: %s%s%s (%s ago)') % (
    3087                                          most_recent['unified_abbrev'],
    3088                                          most_recent['unified_val'],
    3089                                          gmTools.coalesce(most_recent['val_unit'], u'', u' %s'),
    3090                                          gmTools.coalesce(most_recent.formatted_abnormality_indicator, u'', u' %s'),
    3091                                          gmDateTime.format_interval_medically(now - most_recent['clin_when'])
    3092                                  )
    3093                                  self._TCTRL_lab.AppendText(txt)
    3094                                  self._TCTRL_lab.SetDefaultStyle(wx.TextAttr(self.__lab_default_text_color))
    3095                          else:
    3096                                  self._TCTRL_lab.SetDefaultStyle(wx.TextAttr('blue'))
    3097                                  self._TCTRL_lab.AppendText(u'%s:' % most_recent['unified_abbrev'])
    3098                                  self._TCTRL_lab.SetDefaultStyle(wx.TextAttr(self.__lab_default_text_color))
    3099                                  txt = _(' %s%s%s (%s ago)') % (
    3100                                          most_recent['unified_val'],
    3101                                          gmTools.coalesce(most_recent['val_unit'], u'', u' %s'),
    3102                                          gmTools.coalesce(most_recent.formatted_abnormality_indicator, u'', u' %s'),
    3103                                          gmDateTime.format_interval_medically(now - most_recent['clin_when'])
    3104                                  )
    3105                                  self._TCTRL_lab.AppendText(txt)
    3106                          self._TCTRL_lab.AppendText(u' || ')
    3107
    3108                  self._TCTRL_lab.Show()
    3109                  self.Layout()
    3110          #--------------------------------------------------------
    3111 -        def __refresh_gfr(self, patient):
    3112                  gfr = patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_gfr_quantity, no_of_results = 1)
    3113                  if gfr is None:
    3114                          calc = gmClinicalCalculator.cClinicalCalculator()
    3115                          calc.patient = patient
    3116                          gfr = calc.eGFR
    3117                          if gfr.numeric_value is None:
    3118                                  msg = _('GFR: ?')
    3119                                  tt = gfr.message
    3120                          else:
    3121                                  msg = _('eGFR: %.1f (%s)') % (
    3122                                          gfr.numeric_value,
    3123                                          gmDateTime.pydt_strftime (
    3124                                                  gfr.date_valid,
    3125                                                  format = '%b %Y'
    3126                                          )
    3127                                  )
    3128                                  tt = gfr.format (
    3129                                          left_margin = 0,
    3130                                          width = 50,
    3131                                          eol = u'\n',
    3132                                          with_formula = True,
    3133                                          with_warnings = True,
    3134                                          with_variables = False,
    3135                                          with_sub_results = True,
    3136                                          return_list = False
    3137                                  )
    3138                  else:
    3139                          msg = u'%s: %s %s (%s)\n' % (
    3140                                  gfr['unified_abbrev'],
    3141                                  gfr['unified_val'],
    3142                                  gmTools.coalesce(gfr['abnormality_indicator'], u'', u' (%s)'),
    3143                                  gmDateTime.pydt_strftime (
    3144                                          gfr['clin_when'],
    3145                                          format = '%b %Y'
    3146                                  )
    3147                          )
    3148                          tt = _('GFR reported by path lab')
    3149
    3150                  self._LBL_gfr.SetLabel(msg)
    3151                  self._LBL_gfr.SetToolTipString(tt)
    3152                  self._LBL_gfr.Refresh()
    3153                  self.Layout()
    3154          #--------------------------------------------------------
    3155 -        def __clear_gfr(self):
    3156                  self._LBL_gfr.SetLabel(_('GFR: ?'))
    3157                  self._LBL_gfr.Refresh()
    3158                  self.Layout()
    3159          #--------------------------------------------------------
    3160          # event handling
    3161          #--------------------------------------------------------
    3162 -        def __register_interests(self):
    3163                  gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
    3164                  gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
    3165                  gmDispatcher.connect(signal = u'clin.substance_intake_mod_db', receiver = self._schedule_data_reget)
    3166                  gmDispatcher.connect(signal = u'clin.test_result_mod_db', receiver = self._on_test_result_mod)
    3167                  # active_substance_mod_db
    3168                  # substance_brand_mod_db
    3169          #--------------------------------------------------------
    3170 -        def _on_test_result_mod(self):
    3171                  wx.CallAfter(self.__on_test_result_mod)
    3172          #--------------------------------------------------------
    3173 -        def __on_test_result_mod(self):
    3174                  self.__refresh_lab(patient = self._grid_substances.patient)
    3175          #--------------------------------------------------------
    3176 -        def _on_pre_patient_selection(self):
    3177                  wx.CallAfter(self.__on_pre_patient_selection)
    3178
    3179 -        def __on_pre_patient_selection(self):
    3180                  dbcfg = gmCfg.cCfgSQL()
    3181                  pk_panel = dbcfg.get2 (
    3182                          option = u'horstspace.medications_plugin.lab_panel',
    3183                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    3184                          bias = 'user'
    3185                  )
    3186                  if pk_panel is None:
    3187                          self.__lab_panel = None
    3188                  else:
    3189                          self.__lab_panel = gmPathLab.cTestPanel(aPK_obj = pk_panel)
    3190                  self._grid_substances.patient = None
    3191                  self.__refresh_lab(patient = None)
    3192          #--------------------------------------------------------
    3193 -        def _on_post_patient_selection(self):
    3194                  wx.CallAfter(self.__on_post_patient_selection)
    3195
    3196 -        def __on_post_patient_selection(self):
    3197                  self._schedule_data_reget()
    3198          #--------------------------------------------------------
    3199 -        def _on_add_button_pressed(self, event):
    3200                  self._grid_substances.add_substance()
    3201          #--------------------------------------------------------
    3202 -        def _on_edit_button_pressed(self, event):
    3203                  self._grid_substances.edit_substance()
    3204          #--------------------------------------------------------
    3205 -        def _on_delete_button_pressed(self, event):
    3206                  self._grid_substances.delete_substance()
    3207          #--------------------------------------------------------
    3208 -        def _on_info_button_pressed(self, event):
    3209                  self._grid_substances.show_info_on_entry()
    3210          #--------------------------------------------------------
    3211 -        def _on_interactions_button_pressed(self, event):
    3212                  self._grid_substances.check_interactions()
    3213          #--------------------------------------------------------
    3214 -        def _on_issue_grouping_selected(self, event):
    3215                  self._grid_substances.grouping_mode = 'issue'
    3216          #--------------------------------------------------------
    3217 -        def _on_episode_grouping_selected(self, event):
    3218                  self._grid_substances.grouping_mode = 'episode'
    3219          #--------------------------------------------------------
    3220 -        def _on_brand_grouping_selected(self, event):
    3221                  self._grid_substances.grouping_mode = 'brand'
    3222          #--------------------------------------------------------
    3223 -        def _on_show_unapproved_checked(self, event):
    3224                  self._grid_substances.filter_show_unapproved = self._CHBOX_show_unapproved.GetValue()
    3225          #--------------------------------------------------------
    3226 -        def _on_show_inactive_checked(self, event):
    3227                  self._grid_substances.filter_show_inactive = self._CHBOX_show_inactive.GetValue()
    3228          #--------------------------------------------------------
    3229 -        def _on_print_button_pressed(self, event):
    3230                  self._grid_substances.print_medication_list()
    3231          #--------------------------------------------------------
    3232 -        def _on_allergy_button_pressed(self, event):
    3233                  self._grid_substances.create_allergy_from_substance()
    3234          #--------------------------------------------------------
    3235 -        def _on_button_kidneys_pressed(self, event):
    3236                  self._grid_substances.show_renal_insufficiency_info()
    3237          #--------------------------------------------------------
    3238 -        def _on_button_heart_pressed(self, event):
    3239                  self._grid_substances.show_cardiac_info()
    3240          #--------------------------------------------------------
    3241 -        def _on_adr_button_pressed(self, event):
    3242                  self._grid_substances.report_ADR()
    3243          #--------------------------------------------------------
    3244 -        def _on_rx_button_pressed(self, event):
    3245                  self._grid_substances.prescribe()
    3246  #============================================================
    3247  # main
    3248  #------------------------------------------------------------
    3249  if __name__ == '__main__':
    3250
    3251          if len(sys.argv) < 2:
    3252                  sys.exit()
    3253
    3254          if sys.argv[1] != 'test':
    3255                  sys.exit()
    3256
    3257          from Gnumed.business import gmPersonSearch
    3258
    3259          pat = gmPersonSearch.ask_for_patient()
    3260          if pat is None:
    3261                  sys.exit()
    3262          gmPerson.set_active_patient(patient = pat)
    3263
    3264          #----------------------------------------
    3265          app = wx.PyWidgetTester(size = (600, 600))
    3266  #       #app.SetWidget(cATCPhraseWheel, -1)
    3267  #       app.SetWidget(cSubstancePhraseWheel, -1)
    3268  #       app.MainLoop()
    3269          manage_substance_intakes()
    3270
    3271  #============================================================
    3272