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

Source Code for Module Gnumed.wxpython.gmPatSearchWidgets

   1  #  coding: latin-1 
   2  """GNUmed quick person search widgets. 
   3   
   4  This widget allows to search for persons based on the 
   5  critera name, date of birth and person ID. It goes to 
   6  considerable lengths to understand the user's intent from 
   7  her input. For that to work well we need per-culture 
   8  query generators. However, there's always the fallback 
   9  generator. 
  10  """ 
  11  #============================================================ 
  12  __version__ = "$Revision: 1.132 $" 
  13  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
  14  __license__ = 'GPL (for details see http://www.gnu.org/)' 
  15   
  16  import sys, os.path, glob, datetime as pyDT, re as regex, logging, webbrowser 
  17   
  18   
  19  import wx 
  20   
  21   
  22  if __name__ == '__main__': 
  23          sys.path.insert(0, '../../') 
  24          from Gnumed.pycommon import gmLog2 
  25  from Gnumed.pycommon import gmDispatcher, gmPG2, gmI18N, gmCfg, gmTools 
  26  from Gnumed.pycommon import gmDateTime, gmMatchProvider, gmCfg2 
  27  from Gnumed.business import gmPerson, gmKVK, gmSurgery, gmCA_MSVA, gmPersonSearch 
  28  from Gnumed.wxpython import gmGuiHelpers, gmDemographicsWidgets, gmAuthWidgets 
  29  from Gnumed.wxpython import gmRegetMixin, gmPhraseWheel, gmEditArea 
  30   
  31   
  32  _log = logging.getLogger('gm.person') 
  33  _log.info(__version__) 
  34   
  35  _cfg = gmCfg2.gmCfgData() 
  36   
  37  ID_PatPickList = wx.NewId() 
  38  ID_BTN_AddNew = wx.NewId() 
  39   
  40  #============================================================ 
41 -def merge_patients(parent=None):
42 dlg = cMergePatientsDlg(parent, -1) 43 result = dlg.ShowModal()
44 #============================================================ 45 from Gnumed.wxGladeWidgets import wxgMergePatientsDlg 46
47 -class cMergePatientsDlg(wxgMergePatientsDlg.wxgMergePatientsDlg):
48
49 - def __init__(self, *args, **kwargs):
50 wxgMergePatientsDlg.wxgMergePatientsDlg.__init__(self, *args, **kwargs) 51 52 curr_pat = gmPerson.gmCurrentPatient() 53 if curr_pat.connected: 54 self._TCTRL_patient1.person = curr_pat 55 self._TCTRL_patient1._display_name() 56 self._RBTN_patient1.SetValue(True)
57 #--------------------------------------------------------
58 - def _on_merge_button_pressed(self, event):
59 60 if self._TCTRL_patient1.person is None: 61 return 62 63 if self._TCTRL_patient2.person is None: 64 return 65 66 if self._RBTN_patient1.GetValue(): 67 patient2keep = self._TCTRL_patient1.person 68 patient2merge = self._TCTRL_patient2.person 69 else: 70 patient2keep = self._TCTRL_patient2.person 71 patient2merge = self._TCTRL_patient1.person 72 73 if patient2merge['lastnames'] == u'Kirk': 74 if _cfg.get(option = 'debug'): 75 webbrowser.open ( 76 url = 'http://en.wikipedia.org/wiki/File:Picard_as_Locutus.jpg', 77 new = False, 78 autoraise = True 79 ) 80 gmGuiHelpers.gm_show_info(_('\n\nYou will be assimilated.\n\n'), _('The Borg')) 81 return 82 else: 83 gmDispatcher.send(signal = 'statustext', msg = _('Cannot merge Kirk into another patient.'), beep = True) 84 return 85 86 doit = gmGuiHelpers.gm_show_question ( 87 aMessage = _( 88 'Are you positively sure you want to merge patient\n\n' 89 ' #%s: %s (%s, %s)\n\n' 90 'into patient\n\n' 91 ' #%s: %s (%s, %s) ?\n\n' 92 'Note that this action can ONLY be reversed by a laborious\n' 93 'manual process requiring in-depth knowledge about databases\n' 94 'and the patients in question !\n' 95 ) % ( 96 patient2merge.ID, 97 patient2merge['description_gender'], 98 patient2merge['gender'], 99 patient2merge.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()), 100 patient2keep.ID, 101 patient2keep['description_gender'], 102 patient2keep['gender'], 103 patient2keep.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()) 104 ), 105 aTitle = _('Merging patients: confirmation'), 106 cancel_button = False 107 ) 108 if not doit: 109 return 110 111 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Merging patients')) 112 if conn is None: 113 return 114 115 success, msg = patient2keep.assimilate_identity(other_identity = patient2merge, link_obj = conn) 116 conn.close() 117 if not success: 118 gmDispatcher.send(signal = 'statustext', msg = msg, beep = True) 119 return 120 121 # announce success, offer to activate kept patient if not active 122 doit = gmGuiHelpers.gm_show_question ( 123 aMessage = _( 124 'The patient\n' 125 '\n' 126 ' #%s: %s (%s, %s)\n' 127 '\n' 128 'has successfully been merged into\n' 129 '\n' 130 ' #%s: %s (%s, %s)\n' 131 '\n' 132 '\n' 133 'Do you want to activate that patient\n' 134 'now for further modifications ?\n' 135 ) % ( 136 patient2merge.ID, 137 patient2merge['description_gender'], 138 patient2merge['gender'], 139 patient2merge.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()), 140 patient2keep.ID, 141 patient2keep['description_gender'], 142 patient2keep['gender'], 143 patient2keep.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()) 144 ), 145 aTitle = _('Merging patients: success'), 146 cancel_button = False 147 ) 148 if doit: 149 if not isinstance(patient2keep, gmPerson.gmCurrentPatient): 150 wx.CallAfter(set_active_patient, patient = patient2keep) 151 152 if self.IsModal(): 153 self.EndModal(wx.ID_OK) 154 else: 155 self.Close()
156 #============================================================ 157 from Gnumed.wxGladeWidgets import wxgSelectPersonFromListDlg 158
159 -class cSelectPersonFromListDlg(wxgSelectPersonFromListDlg.wxgSelectPersonFromListDlg):
160
161 - def __init__(self, *args, **kwargs):
162 wxgSelectPersonFromListDlg.wxgSelectPersonFromListDlg.__init__(self, *args, **kwargs) 163 164 self.__cols = [ 165 _('Title'), 166 _('Lastname'), 167 _('Firstname'), 168 _('Nickname'), 169 _('DOB'), 170 _('Gender'), 171 _('last visit'), 172 _('found via') 173 ] 174 self.__init_ui()
175 #--------------------------------------------------------
176 - def __init_ui(self):
177 for col in range(len(self.__cols)): 178 self._LCTRL_persons.InsertColumn(col, self.__cols[col])
179 #--------------------------------------------------------
180 - def set_persons(self, persons=None):
181 self._LCTRL_persons.DeleteAllItems() 182 183 pos = len(persons) + 1 184 if pos == 1: 185 return False 186 187 for person in persons: 188 row_num = self._LCTRL_persons.InsertStringItem(pos, label = gmTools.coalesce(person['title'], '')) 189 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = person['lastnames']) 190 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = person['firstnames']) 191 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = gmTools.coalesce(person['preferred'], '')) 192 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = person.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding())) 193 self._LCTRL_persons.SetStringItem(index = row_num, col = 5, label = gmTools.coalesce(person['l10n_gender'], '?')) 194 label = u'' 195 if person.is_patient: 196 enc = person.get_last_encounter() 197 if enc is not None: 198 label = u'%s (%s)' % (enc['started'].strftime('%x').decode(gmI18N.get_encoding()), enc['l10n_type']) 199 self._LCTRL_persons.SetStringItem(index = row_num, col = 6, label = label) 200 try: self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = person['match_type']) 201 except: 202 _log.exception('cannot set match_type field') 203 self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = u'??') 204 205 for col in range(len(self.__cols)): 206 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE) 207 208 self._BTN_select.Enable(False) 209 self._LCTRL_persons.SetFocus() 210 self._LCTRL_persons.Select(0) 211 212 self._LCTRL_persons.set_data(data=persons)
213 #--------------------------------------------------------
214 - def get_selected_person(self):
215 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
216 #-------------------------------------------------------- 217 # event handlers 218 #--------------------------------------------------------
219 - def _on_list_item_selected(self, evt):
220 self._BTN_select.Enable(True) 221 return
222 #--------------------------------------------------------
223 - def _on_list_item_activated(self, evt):
224 self._BTN_select.Enable(True) 225 if self.IsModal(): 226 self.EndModal(wx.ID_OK) 227 else: 228 self.Close()
229 #============================================================ 230 from Gnumed.wxGladeWidgets import wxgSelectPersonDTOFromListDlg 231
232 -class cSelectPersonDTOFromListDlg(wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg):
233
234 - def __init__(self, *args, **kwargs):
235 wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg.__init__(self, *args, **kwargs) 236 237 self.__cols = [ 238 _('Source'), 239 _('Lastname'), 240 _('Firstname'), 241 _('DOB'), 242 _('Gender') 243 ] 244 self.__init_ui()
245 #--------------------------------------------------------
246 - def __init_ui(self):
247 for col in range(len(self.__cols)): 248 self._LCTRL_persons.InsertColumn(col, self.__cols[col])
249 #--------------------------------------------------------
250 - def set_dtos(self, dtos=None):
251 self._LCTRL_persons.DeleteAllItems() 252 253 pos = len(dtos) + 1 254 if pos == 1: 255 return False 256 257 for rec in dtos: 258 row_num = self._LCTRL_persons.InsertStringItem(pos, label = rec['source']) 259 dto = rec['dto'] 260 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = dto.lastnames) 261 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = dto.firstnames) 262 if dto.dob is None: 263 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = u'') 264 else: 265 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = dto.dob.strftime('%x').decode(gmI18N.get_encoding())) 266 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = gmTools.coalesce(dto.gender, '')) 267 268 for col in range(len(self.__cols)): 269 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE) 270 271 self._BTN_select.Enable(False) 272 self._LCTRL_persons.SetFocus() 273 self._LCTRL_persons.Select(0) 274 275 self._LCTRL_persons.set_data(data=dtos)
276 #--------------------------------------------------------
277 - def get_selected_dto(self):
278 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
279 #-------------------------------------------------------- 280 # event handlers 281 #--------------------------------------------------------
282 - def _on_list_item_selected(self, evt):
283 self._BTN_select.Enable(True) 284 return
285 #--------------------------------------------------------
286 - def _on_list_item_activated(self, evt):
287 self._BTN_select.Enable(True) 288 if self.IsModal(): 289 self.EndModal(wx.ID_OK) 290 else: 291 self.Close()
292 293 #============================================================
294 -def load_persons_from_ca_msva():
295 296 group = u'CA Medical Manager MSVA' 297 298 src_order = [ 299 ('explicit', 'append'), 300 ('workbase', 'append'), 301 ('local', 'append'), 302 ('user', 'append'), 303 ('system', 'append') 304 ] 305 msva_files = _cfg.get ( 306 group = group, 307 option = 'filename', 308 source_order = src_order 309 ) 310 if msva_files is None: 311 return [] 312 313 dtos = [] 314 for msva_file in msva_files: 315 try: 316 # FIXME: potentially return several persons per file 317 msva_dtos = gmCA_MSVA.read_persons_from_msva_file(filename = msva_file) 318 except StandardError: 319 gmGuiHelpers.gm_show_error ( 320 _( 321 'Cannot load patient from Medical Manager MSVA file\n\n' 322 ' [%s]' 323 ) % msva_file, 324 _('Activating MSVA patient') 325 ) 326 _log.exception('cannot read patient from MSVA file [%s]' % msva_file) 327 continue 328 329 dtos.extend([ {'dto': dto, 'source': dto.source} for dto in msva_dtos ]) 330 #dtos.extend([ {'dto': dto} for dto in msva_dtos ]) 331 332 return dtos
333 334 #============================================================ 335
336 -def load_persons_from_xdt():
337 338 bdt_files = [] 339 340 # some can be auto-detected 341 # MCS/Isynet: $DRIVE:\Winacs\TEMP\BDTxx.tmp where xx is the workplace 342 candidates = [] 343 drives = 'cdefghijklmnopqrstuvwxyz' 344 for drive in drives: 345 candidate = drive + ':\Winacs\TEMP\BDT*.tmp' 346 candidates.extend(glob.glob(candidate)) 347 for candidate in candidates: 348 path, filename = os.path.split(candidate) 349 # FIXME: add encoding ! 350 bdt_files.append({'file': candidate, 'source': 'MCS/Isynet %s' % filename[-6:-4]}) 351 352 # some need to be configured 353 # aggregate sources 354 src_order = [ 355 ('explicit', 'return'), 356 ('workbase', 'append'), 357 ('local', 'append'), 358 ('user', 'append'), 359 ('system', 'append') 360 ] 361 xdt_profiles = _cfg.get ( 362 group = 'workplace', 363 option = 'XDT profiles', 364 source_order = src_order 365 ) 366 if xdt_profiles is None: 367 return [] 368 369 # first come first serve 370 src_order = [ 371 ('explicit', 'return'), 372 ('workbase', 'return'), 373 ('local', 'return'), 374 ('user', 'return'), 375 ('system', 'return') 376 ] 377 for profile in xdt_profiles: 378 name = _cfg.get ( 379 group = 'XDT profile %s' % profile, 380 option = 'filename', 381 source_order = src_order 382 ) 383 if name is None: 384 _log.error('XDT profile [%s] does not define a <filename>' % profile) 385 continue 386 encoding = _cfg.get ( 387 group = 'XDT profile %s' % profile, 388 option = 'encoding', 389 source_order = src_order 390 ) 391 if encoding is None: 392 _log.warning('xDT source profile [%s] does not specify an <encoding> for BDT file [%s]' % (profile, name)) 393 source = _cfg.get ( 394 group = 'XDT profile %s' % profile, 395 option = 'source', 396 source_order = src_order 397 ) 398 dob_format = _cfg.get ( 399 group = 'XDT profile %s' % profile, 400 option = 'DOB format', 401 source_order = src_order 402 ) 403 if dob_format is None: 404 _log.warning('XDT profile [%s] does not define a date of birth format in <DOB format>' % profile) 405 bdt_files.append({'file': name, 'source': source, 'encoding': encoding, 'dob_format': dob_format}) 406 407 dtos = [] 408 for bdt_file in bdt_files: 409 try: 410 # FIXME: potentially return several persons per file 411 dto = gmPerson.get_person_from_xdt ( 412 filename = bdt_file['file'], 413 encoding = bdt_file['encoding'], 414 dob_format = bdt_file['dob_format'] 415 ) 416 417 except IOError: 418 gmGuiHelpers.gm_show_info ( 419 _( 420 'Cannot access BDT file\n\n' 421 ' [%s]\n\n' 422 'to import patient.\n\n' 423 'Please check your configuration.' 424 ) % bdt_file, 425 _('Activating xDT patient') 426 ) 427 _log.exception('cannot access xDT file [%s]' % bdt_file['file']) 428 continue 429 except: 430 gmGuiHelpers.gm_show_error ( 431 _( 432 'Cannot load patient from BDT file\n\n' 433 ' [%s]' 434 ) % bdt_file, 435 _('Activating xDT patient') 436 ) 437 _log.exception('cannot read patient from xDT file [%s]' % bdt_file['file']) 438 continue 439 440 dtos.append({'dto': dto, 'source': gmTools.coalesce(bdt_file['source'], dto.source)}) 441 442 return dtos
443 444 #============================================================ 445
446 -def load_persons_from_pracsoft_au():
447 448 pracsoft_files = [] 449 450 # try detecting PATIENTS.IN files 451 candidates = [] 452 drives = 'cdefghijklmnopqrstuvwxyz' 453 for drive in drives: 454 candidate = drive + ':\MDW2\PATIENTS.IN' 455 candidates.extend(glob.glob(candidate)) 456 for candidate in candidates: 457 drive, filename = os.path.splitdrive(candidate) 458 pracsoft_files.append({'file': candidate, 'source': 'PracSoft (AU): drive %s' % drive}) 459 460 # add configured one(s) 461 src_order = [ 462 ('explicit', 'append'), 463 ('workbase', 'append'), 464 ('local', 'append'), 465 ('user', 'append'), 466 ('system', 'append') 467 ] 468 fnames = _cfg.get ( 469 group = 'AU PracSoft PATIENTS.IN', 470 option = 'filename', 471 source_order = src_order 472 ) 473 474 src_order = [ 475 ('explicit', 'return'), 476 ('user', 'return'), 477 ('system', 'return'), 478 ('local', 'return'), 479 ('workbase', 'return') 480 ] 481 source = _cfg.get ( 482 group = 'AU PracSoft PATIENTS.IN', 483 option = 'source', 484 source_order = src_order 485 ) 486 487 if source is not None: 488 for fname in fnames: 489 fname = os.path.abspath(os.path.expanduser(fname)) 490 if os.access(fname, os.R_OK): 491 pracsoft_files.append({'file': os.path.expanduser(fname), 'source': source}) 492 else: 493 _log.error('cannot read [%s] in AU PracSoft profile' % fname) 494 495 # and parse them 496 dtos = [] 497 for pracsoft_file in pracsoft_files: 498 try: 499 tmp = gmPerson.get_persons_from_pracsoft_file(filename = pracsoft_file['file']) 500 except: 501 _log.exception('cannot parse PracSoft file [%s]' % pracsoft_file['file']) 502 continue 503 for dto in tmp: 504 dtos.append({'dto': dto, 'source': pracsoft_file['source']}) 505 506 return dtos
507 #============================================================
508 -def load_persons_from_kvks():
509 510 dbcfg = gmCfg.cCfgSQL() 511 kvk_dir = os.path.abspath(os.path.expanduser(dbcfg.get2 ( 512 option = 'DE.KVK.spool_dir', 513 workplace = gmSurgery.gmCurrentPractice().active_workplace, 514 bias = 'workplace', 515 default = u'/var/spool/kvkd/' 516 ))) 517 dtos = [] 518 for dto in gmKVK.get_available_kvks_as_dtos(spool_dir = kvk_dir): 519 dtos.append({'dto': dto, 'source': 'KVK'}) 520 521 return dtos
522 #============================================================
523 -def get_person_from_external_sources(parent=None, search_immediately=False, activate_immediately=False):
524 """Load patient from external source. 525 526 - scan external sources for candidates 527 - let user select source 528 - if > 1 available: always 529 - if only 1 available: depending on search_immediately 530 - search for patients matching info from external source 531 - if more than one match: 532 - let user select patient 533 - if no match: 534 - create patient 535 - activate patient 536 """ 537 # get DTOs from interfaces 538 dtos = [] 539 dtos.extend(load_persons_from_xdt()) 540 dtos.extend(load_persons_from_pracsoft_au()) 541 dtos.extend(load_persons_from_kvks()) 542 dtos.extend(load_persons_from_ca_msva()) 543 544 # no external persons 545 if len(dtos) == 0: 546 gmDispatcher.send(signal='statustext', msg=_('No patients found in external sources.')) 547 return None 548 549 # one external patient with DOB - already active ? 550 if (len(dtos) == 1) and (dtos[0]['dto'].dob is not None): 551 dto = dtos[0]['dto'] 552 # is it already the current patient ? 553 curr_pat = gmPerson.gmCurrentPatient() 554 if curr_pat.connected: 555 key_dto = dto.firstnames + dto.lastnames + dto.dob.strftime('%Y-%m-%d') + dto.gender 556 names = curr_pat.get_active_name() 557 key_pat = names['firstnames'] + names['lastnames'] + curr_pat.get_formatted_dob(format = '%Y-%m-%d') + curr_pat['gender'] 558 _log.debug('current patient: %s' % key_pat) 559 _log.debug('dto patient : %s' % key_dto) 560 if key_dto == key_pat: 561 gmDispatcher.send(signal='statustext', msg=_('The only external patient is already active in GNUmed.'), beep=False) 562 return None 563 564 # one external person - look for internal match immediately ? 565 if (len(dtos) == 1) and search_immediately: 566 dto = dtos[0]['dto'] 567 568 # several external persons 569 else: 570 if parent is None: 571 parent = wx.GetApp().GetTopWindow() 572 dlg = cSelectPersonDTOFromListDlg(parent=parent, id=-1) 573 dlg.set_dtos(dtos=dtos) 574 result = dlg.ShowModal() 575 if result == wx.ID_CANCEL: 576 return None 577 dto = dlg.get_selected_dto()['dto'] 578 dlg.Destroy() 579 580 # search 581 idents = dto.get_candidate_identities(can_create=True) 582 if idents is None: 583 gmGuiHelpers.gm_show_info (_( 584 'Cannot create new patient:\n\n' 585 ' [%s %s (%s), %s]' 586 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())), 587 _('Activating external patient') 588 ) 589 return None 590 591 if len(idents) == 1: 592 ident = idents[0] 593 594 if len(idents) > 1: 595 if parent is None: 596 parent = wx.GetApp().GetTopWindow() 597 dlg = cSelectPersonFromListDlg(parent=parent, id=-1) 598 dlg.set_persons(persons=idents) 599 result = dlg.ShowModal() 600 if result == wx.ID_CANCEL: 601 return None 602 ident = dlg.get_selected_person() 603 dlg.Destroy() 604 605 if activate_immediately: 606 if not set_active_patient(patient = ident): 607 gmGuiHelpers.gm_show_info ( 608 _( 609 'Cannot activate patient:\n\n' 610 '%s %s (%s)\n' 611 '%s' 612 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())), 613 _('Activating external patient') 614 ) 615 return None 616 617 dto.import_extra_data(identity = ident) 618 dto.delete_from_source() 619 620 return ident
621 #============================================================
622 -class cPersonSearchCtrl(wx.TextCtrl):
623 """Widget for smart search for persons.""" 624
625 - def __init__(self, *args, **kwargs):
626 627 try: 628 kwargs['style'] = kwargs['style'] | wx.TE_PROCESS_ENTER 629 except KeyError: 630 kwargs['style'] = wx.TE_PROCESS_ENTER 631 632 # need to explicitly process ENTER events to avoid 633 # them being handed over to the next control 634 wx.TextCtrl.__init__(self, *args, **kwargs) 635 636 self.person = None 637 638 self._tt_search_hints = _( 639 'To search for a person type any of: \n' 640 '\n' 641 ' - fragment of last or first name\n' 642 " - date of birth (can start with '$' or '*')\n" 643 " - GNUmed ID of person (can start with '#')\n" 644 ' - exterenal ID of person\n' 645 '\n' 646 'and hit <ENTER>.\n' 647 '\n' 648 'Shortcuts:\n' 649 ' <F2>\n' 650 ' - scan external sources for persons\n' 651 ' <CURSOR-UP>\n' 652 ' - recall most recently used search term\n' 653 ' <CURSOR-DOWN>\n' 654 ' - list 10 most recently found persons\n' 655 ) 656 self.SetToolTipString(self._tt_search_hints) 657 658 # FIXME: set query generator 659 self.__person_searcher = gmPersonSearch.cPatientSearcher_SQL() 660 661 self._prev_search_term = None 662 self.__prev_idents = [] 663 self._lclick_count = 0 664 665 self.__register_events()
666 #-------------------------------------------------------- 667 # properties 668 #--------------------------------------------------------
669 - def _set_person(self, person):
670 self.__person = person 671 wx.CallAfter(self._display_name)
672
673 - def _get_person(self):
674 return self.__person
675 676 person = property(_get_person, _set_person) 677 #-------------------------------------------------------- 678 # utility methods 679 #--------------------------------------------------------
680 - def _display_name(self):
681 name = u'' 682 683 if self.person is not None: 684 name = self.person['description'] 685 686 self.SetValue(name)
687 #--------------------------------------------------------
688 - def _remember_ident(self, ident=None):
689 690 if not isinstance(ident, gmPerson.cIdentity): 691 return False 692 693 # only unique identities 694 for known_ident in self.__prev_idents: 695 if known_ident['pk_identity'] == ident['pk_identity']: 696 return True 697 698 self.__prev_idents.append(ident) 699 700 # and only 10 of them 701 if len(self.__prev_idents) > 10: 702 self.__prev_idents.pop(0) 703 704 return True
705 #-------------------------------------------------------- 706 # event handling 707 #--------------------------------------------------------
708 - def __register_events(self):
709 wx.EVT_CHAR(self, self.__on_char) 710 wx.EVT_SET_FOCUS(self, self._on_get_focus) 711 wx.EVT_KILL_FOCUS (self, self._on_loose_focus) 712 wx.EVT_TEXT_ENTER (self, self.GetId(), self.__on_enter)
713 #--------------------------------------------------------
714 - def _on_get_focus(self, evt):
715 """upon tabbing in 716 717 - select all text in the field so that the next 718 character typed will delete it 719 """ 720 wx.CallAfter(self.SetSelection, -1, -1) 721 evt.Skip()
722 #--------------------------------------------------------
723 - def _on_loose_focus(self, evt):
724 # - redraw the currently active name upon losing focus 725 726 # if we use wx.EVT_KILL_FOCUS we will also receive this event 727 # when closing our application or loosing focus to another 728 # application which is NOT what we intend to achieve, 729 # however, this is the least ugly way of doing this due to 730 # certain vagaries of wxPython (see the Wiki) 731 732 # just for good measure 733 wx.CallAfter(self.SetSelection, 0, 0) 734 735 self._display_name() 736 self._remember_ident(self.person) 737 738 evt.Skip()
739 #--------------------------------------------------------
740 - def __on_char(self, evt):
741 self._on_char(evt)
742
743 - def _on_char(self, evt):
744 """True: patient was selected. 745 False: no patient was selected. 746 """ 747 keycode = evt.GetKeyCode() 748 749 # list of previously active patients 750 if keycode == wx.WXK_DOWN: 751 evt.Skip() 752 if len(self.__prev_idents) == 0: 753 return False 754 755 dlg = cSelectPersonFromListDlg(parent = wx.GetTopLevelParent(self), id = -1) 756 dlg.set_persons(persons = self.__prev_idents) 757 result = dlg.ShowModal() 758 if result == wx.ID_OK: 759 wx.BeginBusyCursor() 760 self.person = dlg.get_selected_person() 761 dlg.Destroy() 762 wx.EndBusyCursor() 763 return True 764 765 dlg.Destroy() 766 return False 767 768 # recall previous search fragment 769 if keycode == wx.WXK_UP: 770 evt.Skip() 771 # FIXME: cycling through previous fragments 772 if self._prev_search_term is not None: 773 self.SetValue(self._prev_search_term) 774 return False 775 776 # invoke external patient sources 777 if keycode == wx.WXK_F2: 778 evt.Skip() 779 dbcfg = gmCfg.cCfgSQL() 780 search_immediately = bool(dbcfg.get2 ( 781 option = 'patient_search.external_sources.immediately_search_if_single_source', 782 workplace = gmSurgery.gmCurrentPractice().active_workplace, 783 bias = 'user', 784 default = 0 785 )) 786 p = get_person_from_external_sources ( 787 parent = wx.GetTopLevelParent(self), 788 search_immediately = search_immediately 789 ) 790 if p is not None: 791 self.person = p 792 return True 793 return False 794 795 # FIXME: invoke add new person 796 # FIXME: add popup menu apart from system one 797 798 evt.Skip()
799 #--------------------------------------------------------
800 - def __on_enter(self, evt):
801 """This is called from the ENTER handler.""" 802 803 # ENTER but no search term ? 804 curr_search_term = self.GetValue().strip() 805 if curr_search_term == '': 806 return None 807 808 # same person anywys ? 809 if self.person is not None: 810 if curr_search_term == self.person['description']: 811 return None 812 813 # remember search fragment 814 if self.IsModified(): 815 self._prev_search_term = curr_search_term 816 817 self._on_enter(search_term = curr_search_term)
818 #--------------------------------------------------------
819 - def _on_enter(self, search_term=None):
820 """This can be overridden in child classes.""" 821 822 wx.BeginBusyCursor() 823 824 # get list of matching ids 825 idents = self.__person_searcher.get_identities(search_term) 826 827 if idents is None: 828 wx.EndBusyCursor() 829 gmGuiHelpers.gm_show_info ( 830 _('Error searching for matching persons.\n\n' 831 'Search term: "%s"' 832 ) % search_term, 833 _('selecting person') 834 ) 835 return None 836 837 _log.info("%s matching person(s) found", len(idents)) 838 839 if len(idents) == 0: 840 wx.EndBusyCursor() 841 842 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 843 wx.GetTopLevelParent(self), 844 -1, 845 caption = _('Selecting patient'), 846 question = _( 847 'Cannot find any matching patients for the search term\n\n' 848 ' "%s"\n\n' 849 'You may want to try a shorter search term.\n' 850 ) % search_term, 851 button_defs = [ 852 {'label': _('Go back'), 'tooltip': _('Go back and search again.'), 'default': True}, 853 {'label': _('Create new'), 'tooltip': _('Create new patient.')} 854 ] 855 ) 856 if dlg.ShowModal() != wx.ID_NO: 857 return 858 859 success = gmDemographicsWidgets.create_new_person(activate = True) 860 if success: 861 self.person = gmPerson.gmCurrentPatient() 862 else: 863 self.person = None 864 return None 865 866 # only one matching identity 867 if len(idents) == 1: 868 self.person = idents[0] 869 wx.EndBusyCursor() 870 return None 871 872 # more than one matching identity: let user select from pick list 873 dlg = cSelectPersonFromListDlg(parent=wx.GetTopLevelParent(self), id=-1) 874 dlg.set_persons(persons=idents) 875 wx.EndBusyCursor() 876 result = dlg.ShowModal() 877 if result == wx.ID_CANCEL: 878 dlg.Destroy() 879 return None 880 881 wx.BeginBusyCursor() 882 self.person = dlg.get_selected_person() 883 dlg.Destroy() 884 wx.EndBusyCursor() 885 886 return None
887 #============================================================
888 -def set_active_patient(patient=None, forced_reload=False):
889 890 # warn if DOB is missing 891 try: 892 patient['dob'] 893 check_dob = True 894 except TypeError: 895 check_dob = False 896 897 if check_dob: 898 if patient['dob'] is None: 899 gmGuiHelpers.gm_show_warning ( 900 aTitle = _('Checking date of birth'), 901 aMessage = _( 902 '\n' 903 ' %s\n' 904 '\n' 905 'The date of birth for this patient is not known !\n' 906 '\n' 907 'You can proceed to work on the patient but\n' 908 'GNUmed will be unable to assist you with\n' 909 'age-related decisions.\n' 910 ) % patient['description_gender'] 911 ) 912 913 success = gmPerson.set_active_patient(patient = patient, forced_reload = forced_reload) 914 915 if success: 916 if patient['dob'] is not None: 917 dbcfg = gmCfg.cCfgSQL() 918 dob_distance = dbcfg.get2 ( 919 option = u'patient_search.dob_warn_interval', 920 workplace = gmSurgery.gmCurrentPractice().active_workplace, 921 bias = u'user', 922 default = u'1 week' 923 ) 924 925 if patient.dob_in_range(dob_distance, dob_distance): 926 now = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone) 927 enc = gmI18N.get_encoding() 928 gmDispatcher.send(signal = 'statustext', msg = _( 929 '%(pat)s turns %(age)s on %(month)s %(day)s ! (today is %(month_now)s %(day_now)s)') % { 930 'pat': patient.get_description_gender(), 931 'age': patient.get_medical_age().strip('y'), 932 'month': patient.get_formatted_dob(format = '%B', encoding = enc), 933 'day': patient.get_formatted_dob(format = '%d', encoding = enc), 934 'month_now': now.strftime('%B').decode(enc), 935 'day_now': now.strftime('%d') 936 } 937 ) 938 939 return success
940 #------------------------------------------------------------
941 -class cActivePatientSelector(cPersonSearchCtrl):
942
943 - def __init__ (self, *args, **kwargs):
944 945 cPersonSearchCtrl.__init__(self, *args, **kwargs) 946 947 # selector_tooltip = _( 948 # 'Patient search field. \n' 949 # '\n' 950 # 'To search, type any of:\n' 951 # ' - fragment of last or first name\n' 952 # " - date of birth (can start with '$' or '*')\n" 953 # " - patient ID (can start with '#')\n" 954 # 'and hit <ENTER>.\n' 955 # '\n' 956 # '<CURSOR-UP>\n' 957 # ' - recall most recently used search term\n' 958 # '<CURSOR-DOWN>\n' 959 # ' - list 10 most recently activated patients\n' 960 # '<F2>\n' 961 # ' - scan external sources for patients to import and activate\n' 962 # ) 963 # self.SetToolTip(wx.ToolTip(selector_tooltip)) 964 965 # get configuration 966 cfg = gmCfg.cCfgSQL() 967 968 self.__always_dismiss_on_search = bool ( 969 cfg.get2 ( 970 option = 'patient_search.always_dismiss_previous_patient', 971 workplace = gmSurgery.gmCurrentPractice().active_workplace, 972 bias = 'user', 973 default = 0 974 ) 975 ) 976 977 self.__always_reload_after_search = bool ( 978 cfg.get2 ( 979 option = 'patient_search.always_reload_new_patient', 980 workplace = gmSurgery.gmCurrentPractice().active_workplace, 981 bias = 'user', 982 default = 0 983 ) 984 ) 985 986 self.__register_events()
987 #-------------------------------------------------------- 988 # utility methods 989 #--------------------------------------------------------
990 - def _display_name(self):
991 name = _('<type here to search patient>') 992 993 curr_pat = gmPerson.gmCurrentPatient() 994 if curr_pat.connected: 995 name = curr_pat['description'] 996 if curr_pat.locked: 997 name = _('%(name)s (locked)') % {'name': name} 998 999 self.SetValue(name) 1000 1001 if self.person is None: 1002 self.SetToolTipString(self._tt_search_hints) 1003 return 1004 1005 tt = u'%s%s-----------------------------------\n%s' % ( 1006 gmTools.coalesce(self.person['emergency_contact'], u'', _('In case of emergency contact:') + u'\n %s\n'), 1007 gmTools.coalesce(self.person['comment'], u'', u'\n%s\n'), 1008 self._tt_search_hints 1009 ) 1010 self.SetToolTipString(tt)
1011 #--------------------------------------------------------
1012 - def _set_person_as_active_patient(self, pat):
1013 if not set_active_patient(patient=pat, forced_reload = self.__always_reload_after_search): 1014 _log.error('cannot change active patient') 1015 return None 1016 1017 self._remember_ident(pat) 1018 1019 return True
1020 #-------------------------------------------------------- 1021 # event handling 1022 #--------------------------------------------------------
1023 - def __register_events(self):
1024 # client internal signals 1025 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 1026 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_name_identity_change) 1027 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_name_identity_change) 1028 1029 gmDispatcher.connect(signal = 'patient_locked', receiver = self._on_post_patient_selection) 1030 gmDispatcher.connect(signal = 'patient_unlocked', receiver = self._on_post_patient_selection)
1031 #----------------------------------------------
1032 - def _on_name_identity_change(self, **kwargs):
1033 wx.CallAfter(self._display_name)
1034 #----------------------------------------------
1035 - def _on_post_patient_selection(self, **kwargs):
1036 if gmPerson.gmCurrentPatient().connected: 1037 self.person = gmPerson.gmCurrentPatient().patient 1038 else: 1039 self.person = None
1040 #----------------------------------------------
1041 - def _on_enter(self, search_term = None):
1042 1043 if self.__always_dismiss_on_search: 1044 _log.warning("dismissing patient before patient search") 1045 self._set_person_as_active_patient(-1) 1046 1047 super(self.__class__, self)._on_enter(search_term=search_term) 1048 1049 if self.person is None: 1050 return 1051 1052 self._set_person_as_active_patient(self.person)
1053 #----------------------------------------------
1054 - def _on_char(self, evt):
1055 1056 success = super(self.__class__, self)._on_char(evt) 1057 if success: 1058 self._set_person_as_active_patient(self.person)
1059 #============================================================ 1060 # waiting list widgets 1061 #============================================================
1062 -class cWaitingZonePhraseWheel(gmPhraseWheel.cPhraseWheel):
1063
1064 - def __init__(self, *args, **kwargs):
1065 1066 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1067 1068 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = []) 1069 mp.setThresholds(1, 2, 2) 1070 self.matcher = mp 1071 self.selection_only = False
1072 1073 #--------------------------------------------------------
1074 - def update_matcher(self, items):
1075 self.matcher.set_items([ {'data': i, 'label': i, 'weight': 1} for i in items ])
1076 1077 #============================================================ 1078 from Gnumed.wxGladeWidgets import wxgWaitingListEntryEditAreaPnl 1079
1080 -class cWaitingListEntryEditAreaPnl(wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
1081
1082 - def __init__ (self, *args, **kwargs):
1083 1084 try: 1085 self.patient = kwargs['patient'] 1086 del kwargs['patient'] 1087 except KeyError: 1088 self.patient = None 1089 1090 try: 1091 data = kwargs['entry'] 1092 del kwargs['entry'] 1093 except KeyError: 1094 data = None 1095 1096 wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl.__init__(self, *args, **kwargs) 1097 gmEditArea.cGenericEditAreaMixin.__init__(self) 1098 1099 if data is None: 1100 self.mode = 'new' 1101 else: 1102 self.data = data 1103 self.mode = 'edit' 1104 1105 praxis = gmSurgery.gmCurrentPractice() 1106 pats = praxis.waiting_list_patients 1107 zones = {} 1108 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ]) 1109 self._PRW_zone.update_matcher(items = zones.keys())
1110 #-------------------------------------------------------- 1111 # edit area mixin API 1112 #--------------------------------------------------------
1113 - def _refresh_as_new(self):
1114 if self.patient is None: 1115 self._PRW_patient.person = None 1116 self._PRW_patient.Enable(True) 1117 self._PRW_patient.SetFocus() 1118 else: 1119 self._PRW_patient.person = self.patient 1120 self._PRW_patient.Enable(False) 1121 self._PRW_comment.SetFocus() 1122 self._PRW_patient._display_name() 1123 1124 self._PRW_comment.SetValue(u'') 1125 self._PRW_zone.SetValue(u'') 1126 self._SPCTRL_urgency.SetValue(0)
1127 #--------------------------------------------------------
1128 - def _refresh_from_existing(self):
1129 self._PRW_patient.person = gmPerson.cIdentity(aPK_obj = self.data['pk_identity']) 1130 self._PRW_patient.Enable(False) 1131 self._PRW_patient._display_name() 1132 1133 self._PRW_comment.SetValue(gmTools.coalesce(self.data['comment'], u'')) 1134 self._PRW_zone.SetValue(gmTools.coalesce(self.data['waiting_zone'], u'')) 1135 self._SPCTRL_urgency.SetValue(self.data['urgency']) 1136 1137 self._PRW_comment.SetFocus()
1138 #--------------------------------------------------------
1139 - def _valid_for_save(self):
1140 validity = True 1141 1142 self.display_tctrl_as_valid(tctrl = self._PRW_patient, valid = (self._PRW_patient.person is not None)) 1143 validity = (self._PRW_patient.person is not None) 1144 1145 if validity is False: 1146 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add to waiting list. Missing essential input.')) 1147 1148 return validity
1149 #----------------------------------------------------------------
1150 - def _save_as_new(self):
1151 # FIXME: filter out dupes 1152 self._PRW_patient.person.put_on_waiting_list ( 1153 urgency = self._SPCTRL_urgency.GetValue(), 1154 comment = gmTools.none_if(self._PRW_comment.GetValue().strip(), u''), 1155 zone = gmTools.none_if(self._PRW_zone.GetValue().strip(), u'') 1156 ) 1157 # dummy: 1158 self.data = {'pk_identity': self._PRW_patient.person.ID, 'comment': None, 'waiting_zone': None, 'urgency': 0} 1159 return True
1160 #----------------------------------------------------------------
1161 - def _save_as_update(self):
1162 gmSurgery.gmCurrentPractice().update_in_waiting_list ( 1163 pk = self.data['pk_waiting_list'], 1164 urgency = self._SPCTRL_urgency.GetValue(), 1165 comment = self._PRW_comment.GetValue().strip(), 1166 zone = self._PRW_zone.GetValue().strip() 1167 ) 1168 return True
1169 #============================================================ 1170 from Gnumed.wxGladeWidgets import wxgWaitingListPnl 1171
1172 -class cWaitingListPnl(wxgWaitingListPnl.wxgWaitingListPnl, gmRegetMixin.cRegetOnPaintMixin):
1173
1174 - def __init__ (self, *args, **kwargs):
1175 1176 wxgWaitingListPnl.wxgWaitingListPnl.__init__(self, *args, **kwargs) 1177 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1178 1179 self.__current_zone = None 1180 1181 self.__init_ui() 1182 self.__register_events()
1183 #-------------------------------------------------------- 1184 # interal helpers 1185 #--------------------------------------------------------
1186 - def __init_ui(self):
1187 self._LCTRL_patients.set_columns ([ 1188 _('Zone'), 1189 _('Urgency'), 1190 #' ! ', 1191 _('Waiting time'), 1192 _('Patient'), 1193 _('Born'), 1194 _('Comment') 1195 ]) 1196 self._LCTRL_patients.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 1197 self._PRW_zone.add_callback_on_selection(callback = self._on_zone_selected) 1198 self._PRW_zone.add_callback_on_lose_focus(callback = self._on_zone_selected)
1199 #--------------------------------------------------------
1200 - def __register_events(self):
1201 gmDispatcher.connect(signal = u'waiting_list_generic_mod_db', receiver = self._on_waiting_list_modified)
1202 #--------------------------------------------------------
1203 - def __refresh_waiting_list(self):
1204 1205 praxis = gmSurgery.gmCurrentPractice() 1206 pats = praxis.waiting_list_patients 1207 1208 # set matcher to all zones currently in use 1209 zones = {} 1210 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ]) 1211 self._PRW_zone.update_matcher(items = zones.keys()) 1212 del zones 1213 1214 # filter patient list by zone and set waiting list 1215 self.__current_zone = self._PRW_zone.GetValue().strip() 1216 if self.__current_zone == u'': 1217 pats = [ p for p in pats ] 1218 else: 1219 pats = [ p for p in pats if p['waiting_zone'] == self.__current_zone ] 1220 1221 self._LCTRL_patients.set_string_items ( 1222 [ [ 1223 gmTools.coalesce(p['waiting_zone'], u''), 1224 p['urgency'], 1225 p['waiting_time_formatted'].replace(u'00 ', u'', 1).replace('00:', u'').lstrip('0'), 1226 u'%s, %s (%s)' % (p['lastnames'], p['firstnames'], p['l10n_gender']), 1227 gmTools.coalesce ( 1228 gmTools.coalesce ( 1229 p['dob'], 1230 u'', 1231 function_initial = ('strftime', '%d %b %Y') 1232 ), 1233 u'', 1234 function_initial = ('decode', gmI18N.get_encoding()) 1235 ), 1236 gmTools.coalesce(p['comment'], u'') 1237 ] for p in pats 1238 ] 1239 ) 1240 self._LCTRL_patients.set_column_widths() 1241 self._LCTRL_patients.set_data(pats) 1242 self._LCTRL_patients.Refresh() 1243 self._LCTRL_patients.SetToolTipString ( _( 1244 '%s patients are waiting.\n' 1245 '\n' 1246 'Doubleclick to activate (entry will stay in list).' 1247 ) % len(pats)) 1248 1249 self._LBL_no_of_patients.SetLabel(_('(%s patients)') % len(pats)) 1250 1251 if len(pats) == 0: 1252 self._BTN_activate.Enable(False) 1253 self._BTN_activateplus.Enable(False) 1254 self._BTN_remove.Enable(False) 1255 self._BTN_edit.Enable(False) 1256 self._BTN_up.Enable(False) 1257 self._BTN_down.Enable(False) 1258 else: 1259 self._BTN_activate.Enable(True) 1260 self._BTN_activateplus.Enable(True) 1261 self._BTN_remove.Enable(True) 1262 self._BTN_edit.Enable(True) 1263 if len(pats) > 1: 1264 self._BTN_up.Enable(True) 1265 self._BTN_down.Enable(True)
1266 #-------------------------------------------------------- 1267 # event handlers 1268 #--------------------------------------------------------
1269 - def _on_zone_selected(self, zone=None):
1270 if self.__current_zone == self._PRW_zone.GetValue().strip(): 1271 return True 1272 wx.CallAfter(self.__refresh_waiting_list) 1273 return True
1274 #--------------------------------------------------------
1275 - def _on_waiting_list_modified(self, *args, **kwargs):
1276 wx.CallAfter(self._schedule_data_reget)
1277 #--------------------------------------------------------
1278 - def _on_list_item_activated(self, evt):
1279 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1280 if item is None: 1281 return 1282 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity']) 1283 wx.CallAfter(set_active_patient, patient = pat)
1284 #--------------------------------------------------------
1285 - def _on_activate_button_pressed(self, evt):
1286 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1287 if item is None: 1288 return 1289 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity']) 1290 wx.CallAfter(set_active_patient, patient = pat)
1291 #--------------------------------------------------------
1292 - def _on_activateplus_button_pressed(self, evt):
1293 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1294 if item is None: 1295 return 1296 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity']) 1297 gmSurgery.gmCurrentPractice().remove_from_waiting_list(pk = item['pk_waiting_list']) 1298 wx.CallAfter(set_active_patient, patient = pat)
1299 #--------------------------------------------------------
1300 - def _on_add_patient_button_pressed(self, evt):
1301 1302 curr_pat = gmPerson.gmCurrentPatient() 1303 if not curr_pat.connected: 1304 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add waiting list entry: No patient selected.'), beep = True) 1305 return 1306 1307 ea = cWaitingListEntryEditAreaPnl(self, -1, patient = curr_pat) 1308 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1309 dlg.ShowModal() 1310 dlg.Destroy()
1311 #--------------------------------------------------------
1312 - def _on_edit_button_pressed(self, event):
1313 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1314 if item is None: 1315 return 1316 ea = cWaitingListEntryEditAreaPnl(self, -1, entry = item) 1317 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1318 dlg.ShowModal() 1319 dlg.Destroy()
1320 #--------------------------------------------------------
1321 - def _on_remove_button_pressed(self, evt):
1322 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1323 if item is None: 1324 return 1325 gmSurgery.gmCurrentPractice().remove_from_waiting_list(pk = item['pk_waiting_list'])
1326 #--------------------------------------------------------
1327 - def _on_up_button_pressed(self, evt):
1328 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1329 if item is None: 1330 return 1331 gmSurgery.gmCurrentPractice().raise_in_waiting_list(current_position = item['list_position'])
1332 #--------------------------------------------------------
1333 - def _on_down_button_pressed(self, evt):
1334 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1335 if item is None: 1336 return 1337 gmSurgery.gmCurrentPractice().lower_in_waiting_list(current_position = item['list_position'])
1338 #-------------------------------------------------------- 1339 # edit 1340 #-------------------------------------------------------- 1341 # reget-on-paint API 1342 #--------------------------------------------------------
1343 - def _populate_with_data(self):
1344 self.__refresh_waiting_list() 1345 return True
1346 #============================================================ 1347 # main 1348 #------------------------------------------------------------ 1349 if __name__ == "__main__": 1350 1351 if len(sys.argv) > 1: 1352 if sys.argv[1] == 'test': 1353 gmI18N.activate_locale() 1354 gmI18N.install_domain() 1355 1356 app = wx.PyWidgetTester(size = (200, 40)) 1357 # app.SetWidget(cSelectPersonFromListDlg, -1) 1358 # app.SetWidget(cPersonSearchCtrl, -1) 1359 # app.SetWidget(cActivePatientSelector, -1) 1360 app.SetWidget(cWaitingListPnl, -1) 1361 app.MainLoop() 1362 1363 #============================================================ 1364 # docs 1365 #------------------------------------------------------------ 1366 # functionality 1367 # ------------- 1368 # - hitting ENTER on non-empty field (and more than threshold chars) 1369 # - start search 1370 # - display results in a list, prefixed with numbers 1371 # - last name 1372 # - first name 1373 # - gender 1374 # - age 1375 # - city + street (no ZIP, no number) 1376 # - last visit (highlighted if within a certain interval) 1377 # - arbitrary marker (e.g. office attendance this quartal, missing KVK, appointments, due dates) 1378 # - if none found -> go to entry of new patient 1379 # - scrolling in this list 1380 # - ENTER selects patient 1381 # - ESC cancels selection 1382 # - number selects patient 1383 # 1384 # - hitting cursor-up/-down 1385 # - cycle through history of last 10 search fragments 1386 # 1387 # - hitting alt-L = List, alt-P = previous 1388 # - show list of previous ten patients prefixed with numbers 1389 # - scrolling in list 1390 # - ENTER selects patient 1391 # - ESC cancels selection 1392 # - number selects patient 1393 # 1394 # - hitting ALT-N 1395 # - immediately goes to entry of new patient 1396 # 1397 # - hitting cursor-right in a patient selection list 1398 # - pops up more detail about the patient 1399 # - ESC/cursor-left goes back to list 1400 # 1401 # - hitting TAB 1402 # - makes sure the currently active patient is displayed 1403 1404 #------------------------------------------------------------ 1405 # samples 1406 # ------- 1407 # working: 1408 # Ian Haywood 1409 # Haywood Ian 1410 # Haywood 1411 # Amador Jimenez (yes, two last names but no hyphen: Spain, for example) 1412 # Ian Haywood 19/12/1977 1413 # 19/12/1977 1414 # 19-12-1977 1415 # 19.12.1977 1416 # 19771219 1417 # $dob 1418 # *dob 1419 # #ID 1420 # ID 1421 # HIlbert, karsten 1422 # karsten, hilbert 1423 # kars, hilb 1424 # 1425 # non-working: 1426 # Haywood, Ian <40 1427 # ?, Ian 1977 1428 # Ian Haywood, 19/12/77 1429 # PUPIC 1430 # "hilb; karsten, 23.10.74" 1431 1432 #------------------------------------------------------------ 1433 # notes 1434 # ----- 1435 # >> 3. There are countries in which people have more than one 1436 # >> (significant) lastname (spanish-speaking countries are one case :), some 1437 # >> asian countries might be another one). 1438 # -> we need per-country query generators ... 1439 1440 # search case sensitive by default, switch to insensitive if not found ? 1441 1442 # accent insensitive search: 1443 # select * from * where to_ascii(column, 'encoding') like '%test%'; 1444 # may not work with Unicode 1445 1446 # phrase wheel is most likely too slow 1447 1448 # extend search fragment history 1449 1450 # ask user whether to send off level 3 queries - or thread them 1451 1452 # we don't expect patient IDs in complicated patterns, hence any digits signify a date 1453 1454 # FIXME: make list window fit list size ... 1455 1456 # clear search field upon get-focus ? 1457 1458 # F1 -> context help with hotkey listing 1459 1460 # th -> th|t 1461 # v/f/ph -> f|v|ph 1462 # maybe don't do umlaut translation in the first 2-3 letters 1463 # such that not to defeat index use for the first level query ? 1464 1465 # user defined function key to start search 1466