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

Source Code for Module Gnumed.wxpython.gmMedicationWidgets

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