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