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

Source Code for Module Gnumed.wxpython.gmMedicationWidgets

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