| Trees | Indices | Help |
|
|---|
|
|
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 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmPatSearchWidgets.py,v $
13 # $Id: gmPatSearchWidgets.py,v 1.132 2010/02/07 15:17:06 ncq Exp $
14 __version__ = "$Revision: 1.132 $"
15 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
16 __license__ = 'GPL (for details see http://www.gnu.org/)'
17
18 import sys, os.path, glob, datetime as pyDT, re as regex, logging, webbrowser
19
20
21 import wx
22
23
24 if __name__ == '__main__':
25 sys.path.insert(0, '../../')
26 from Gnumed.pycommon import gmLog2
27 from Gnumed.pycommon import gmDispatcher, gmPG2, gmI18N, gmCfg, gmTools, gmDateTime, gmMatchProvider, gmCfg2
28 from Gnumed.business import gmPerson, gmKVK, gmSurgery
29 from Gnumed.wxpython import gmGuiHelpers, gmDemographicsWidgets, gmAuthWidgets, gmRegetMixin, gmPhraseWheel, gmEditArea
30 from Gnumed.wxGladeWidgets import wxgSelectPersonFromListDlg, wxgSelectPersonDTOFromListDlg, wxgMergePatientsDlg
31
32
33 _log = logging.getLogger('gm.person')
34 _log.info(__version__)
35
36 _cfg = gmCfg2.gmCfgData()
37
38 ID_PatPickList = wx.NewId()
39 ID_BTN_AddNew = wx.NewId()
40
41 #============================================================
45 #============================================================
47
49 wxgMergePatientsDlg.wxgMergePatientsDlg.__init__(self, *args, **kwargs)
50
51 curr_pat = gmPerson.gmCurrentPatient()
52 if curr_pat.connected:
53 self._TCTRL_patient1.person = curr_pat
54 self._TCTRL_patient1._display_name()
55 self._RBTN_patient1.SetValue(True)
56 #--------------------------------------------------------
155 #============================================================
157
159 wxgSelectPersonFromListDlg.wxgSelectPersonFromListDlg.__init__(self, *args, **kwargs)
160
161 self.__cols = [
162 _('Title'),
163 _('Lastname'),
164 _('Firstname'),
165 _('Nickname'),
166 _('DOB'),
167 _('Gender'),
168 _('last visit'),
169 _('found via')
170 ]
171 self.__init_ui()
172 #--------------------------------------------------------
176 #--------------------------------------------------------
178 self._LCTRL_persons.DeleteAllItems()
179
180 pos = len(persons) + 1
181 if pos == 1:
182 return False
183
184 for person in persons:
185 row_num = self._LCTRL_persons.InsertStringItem(pos, label = gmTools.coalesce(person['title'], ''))
186 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = person['lastnames'])
187 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = person['firstnames'])
188 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = gmTools.coalesce(person['preferred'], ''))
189 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = person.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()))
190 self._LCTRL_persons.SetStringItem(index = row_num, col = 5, label = gmTools.coalesce(person['l10n_gender'], '?'))
191 label = u''
192 if person.is_patient:
193 enc = person.get_last_encounter()
194 if enc is not None:
195 label = u'%s (%s)' % (enc['started'].strftime('%x').decode(gmI18N.get_encoding()), enc['l10n_type'])
196 self._LCTRL_persons.SetStringItem(index = row_num, col = 6, label = label)
197 try: self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = person['match_type'])
198 except:
199 _log.exception('cannot set match_type field')
200 self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = u'??')
201
202 for col in range(len(self.__cols)):
203 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE)
204
205 self._BTN_select.Enable(False)
206 self._LCTRL_persons.SetFocus()
207 self._LCTRL_persons.Select(0)
208
209 self._LCTRL_persons.set_data(data=persons)
210 #--------------------------------------------------------
212 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
213 #--------------------------------------------------------
214 # event handlers
215 #--------------------------------------------------------
219 #--------------------------------------------------------
226 #============================================================
227 -class cSelectPersonDTOFromListDlg(wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg):
228
230 wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg.__init__(self, *args, **kwargs)
231
232 self.__cols = [
233 _('Source'),
234 _('Lastname'),
235 _('Firstname'),
236 _('DOB'),
237 _('Gender')
238 ]
239 self.__init_ui()
240 #--------------------------------------------------------
244 #--------------------------------------------------------
246 self._LCTRL_persons.DeleteAllItems()
247
248 pos = len(dtos) + 1
249 if pos == 1:
250 return False
251
252 for rec in dtos:
253 row_num = self._LCTRL_persons.InsertStringItem(pos, label = rec['source'])
254 dto = rec['dto']
255 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = dto.lastnames)
256 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = dto.firstnames)
257 if dto.dob is None:
258 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = u'')
259 else:
260 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = dto.dob.strftime('%x').decode(gmI18N.get_encoding()))
261 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = gmTools.coalesce(dto.gender, ''))
262
263 for col in range(len(self.__cols)):
264 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE)
265
266 self._BTN_select.Enable(False)
267 self._LCTRL_persons.SetFocus()
268 self._LCTRL_persons.Select(0)
269
270 self._LCTRL_persons.set_data(data=dtos)
271 #--------------------------------------------------------
273 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
274 #--------------------------------------------------------
275 # event handlers
276 #--------------------------------------------------------
280 #--------------------------------------------------------
287 #============================================================
289
290 bdt_files = []
291
292 # some can be auto-detected
293 # MCS/Isynet: $DRIVE:\Winacs\TEMP\BDTxx.tmp where xx is the workplace
294 candidates = []
295 drives = 'cdefghijklmnopqrstuvwxyz'
296 for drive in drives:
297 candidate = drive + ':\Winacs\TEMP\BDT*.tmp'
298 candidates.extend(glob.glob(candidate))
299 for candidate in candidates:
300 path, filename = os.path.split(candidate)
301 # FIXME: add encoding !
302 bdt_files.append({'file': candidate, 'source': 'MCS/Isynet %s' % filename[-6:-4]})
303
304 # some need to be configured
305 # aggregate sources
306 src_order = [
307 ('explicit', 'return'),
308 ('workbase', 'append'),
309 ('local', 'append'),
310 ('user', 'append'),
311 ('system', 'append')
312 ]
313 xdt_profiles = _cfg.get (
314 group = 'workplace',
315 option = 'XDT profiles',
316 source_order = src_order
317 )
318 if xdt_profiles is None:
319 return []
320
321 # first come first serve
322 src_order = [
323 ('explicit', 'return'),
324 ('workbase', 'return'),
325 ('local', 'return'),
326 ('user', 'return'),
327 ('system', 'return')
328 ]
329 for profile in xdt_profiles:
330 name = _cfg.get (
331 group = 'XDT profile %s' % profile,
332 option = 'filename',
333 source_order = src_order
334 )
335 if name is None:
336 _log.error('XDT profile [%s] does not define a <filename>' % profile)
337 continue
338 encoding = _cfg.get (
339 group = 'XDT profile %s' % profile,
340 option = 'encoding',
341 source_order = src_order
342 )
343 if encoding is None:
344 _log.warning('xDT source profile [%s] does not specify an <encoding> for BDT file [%s]' % (profile, name))
345 source = _cfg.get (
346 group = 'XDT profile %s' % profile,
347 option = 'source',
348 source_order = src_order
349 )
350 dob_format = _cfg.get (
351 group = 'XDT profile %s' % profile,
352 option = 'DOB format',
353 source_order = src_order
354 )
355 if dob_format is None:
356 _log.warning('XDT profile [%s] does not define a date of birth format in <DOB format>' % profile)
357 bdt_files.append({'file': name, 'source': source, 'encoding': encoding, 'dob_format': dob_format})
358
359 dtos = []
360 for bdt_file in bdt_files:
361 try:
362 # FIXME: potentially return several persons per file
363 dto = gmPerson.get_person_from_xdt (
364 filename = bdt_file['file'],
365 encoding = bdt_file['encoding'],
366 dob_format = bdt_file['dob_format']
367 )
368
369 except IOError:
370 gmGuiHelpers.gm_show_info (
371 _(
372 'Cannot access BDT file\n\n'
373 ' [%s]\n\n'
374 'to import patient.\n\n'
375 'Please check your configuration.'
376 ) % bdt_file,
377 _('Activating xDT patient')
378 )
379 _log.exception('cannot access xDT file [%s]' % bdt_file['file'])
380 continue
381 except:
382 gmGuiHelpers.gm_show_error (
383 _(
384 'Cannot load patient from BDT file\n\n'
385 ' [%s]'
386 ) % bdt_file,
387 _('Activating xDT patient')
388 )
389 _log.exception('cannot read patient from xDT file [%s]' % bdt_file['file'])
390 continue
391
392 dtos.append({'dto': dto, 'source': gmTools.coalesce(bdt_file['source'], dto.source)})
393
394 return dtos
395 #============================================================
397
398 pracsoft_files = []
399
400 # try detecting PATIENTS.IN files
401 candidates = []
402 drives = 'cdefghijklmnopqrstuvwxyz'
403 for drive in drives:
404 candidate = drive + ':\MDW2\PATIENTS.IN'
405 candidates.extend(glob.glob(candidate))
406 for candidate in candidates:
407 drive, filename = os.path.splitdrive(candidate)
408 pracsoft_files.append({'file': candidate, 'source': 'PracSoft (AU): drive %s' % drive})
409
410 # add configured one(s)
411 src_order = [
412 ('explicit', 'append'),
413 ('workbase', 'append'),
414 ('local', 'append'),
415 ('user', 'append'),
416 ('system', 'append')
417 ]
418 fnames = _cfg.get (
419 group = 'AU PracSoft PATIENTS.IN',
420 option = 'filename',
421 source_order = src_order
422 )
423
424 src_order = [
425 ('explicit', 'return'),
426 ('user', 'return'),
427 ('system', 'return'),
428 ('local', 'return'),
429 ('workbase', 'return')
430 ]
431 source = _cfg.get (
432 group = 'AU PracSoft PATIENTS.IN',
433 option = 'source',
434 source_order = src_order
435 )
436
437 if source is not None:
438 for fname in fnames:
439 fname = os.path.abspath(os.path.expanduser(fname))
440 if os.access(fname, os.R_OK):
441 pracsoft_files.append({'file': os.path.expanduser(fname), 'source': source})
442 else:
443 _log.error('cannot read [%s] in AU PracSoft profile' % fname)
444
445 # and parse them
446 dtos = []
447 for pracsoft_file in pracsoft_files:
448 try:
449 tmp = gmPerson.get_persons_from_pracsoft_file(filename = pracsoft_file['file'])
450 except:
451 _log.exception('cannot parse PracSoft file [%s]' % pracsoft_file['file'])
452 continue
453 for dto in tmp:
454 dtos.append({'dto': dto, 'source': pracsoft_file['source']})
455
456 return dtos
457 #============================================================
459
460 dbcfg = gmCfg.cCfgSQL()
461 kvk_dir = os.path.abspath(os.path.expanduser(dbcfg.get2 (
462 option = 'DE.KVK.spool_dir',
463 workplace = gmSurgery.gmCurrentPractice().active_workplace,
464 bias = 'workplace',
465 default = u'/var/spool/kvkd/'
466 )))
467 dtos = []
468 for dto in gmKVK.get_available_kvks_as_dtos(spool_dir = kvk_dir):
469 dtos.append({'dto': dto, 'source': 'KVK'})
470
471 return dtos
472 #============================================================
473 -def get_person_from_external_sources(parent=None, search_immediately=False, activate_immediately=False):
474 """Load patient from external source.
475
476 - scan external sources for candidates
477 - let user select source
478 - if > 1 available: always
479 - if only 1 available: depending on search_immediately
480 - search for patients matching info from external source
481 - if more than one match:
482 - let user select patient
483 - if no match:
484 - create patient
485 - activate patient
486 """
487 # get DTOs from interfaces
488 dtos = []
489 dtos.extend(load_persons_from_xdt())
490 dtos.extend(load_persons_from_pracsoft_au())
491 dtos.extend(load_persons_from_kvks())
492
493 # no external persons
494 if len(dtos) == 0:
495 gmDispatcher.send(signal='statustext', msg=_('No patients found in external sources.'))
496 return None
497
498 # one external patient with DOB - already active ?
499 if (len(dtos) == 1) and (dtos[0]['dto'].dob is not None):
500 dto = dtos[0]['dto']
501 # is it already the current patient ?
502 curr_pat = gmPerson.gmCurrentPatient()
503 if curr_pat.connected:
504 key_dto = dto.firstnames + dto.lastnames + dto.dob.strftime('%Y-%m-%d') + dto.gender
505 names = curr_pat.get_active_name()
506 key_pat = names['firstnames'] + names['lastnames'] + curr_pat.get_formatted_dob(format = '%Y-%m-%d') + curr_pat['gender']
507 _log.debug('current patient: %s' % key_pat)
508 _log.debug('dto patient : %s' % key_dto)
509 if key_dto == key_pat:
510 gmDispatcher.send(signal='statustext', msg=_('The only external patient is already active in GNUmed.'), beep=False)
511 return None
512
513 # one external person - look for internal match immediately ?
514 if (len(dtos) == 1) and search_immediately:
515 dto = dtos[0]['dto']
516
517 # several external persons
518 else:
519 if parent is None:
520 parent = wx.GetApp().GetTopWindow()
521 dlg = cSelectPersonDTOFromListDlg(parent=parent, id=-1)
522 dlg.set_dtos(dtos=dtos)
523 result = dlg.ShowModal()
524 if result == wx.ID_CANCEL:
525 return None
526 dto = dlg.get_selected_dto()['dto']
527 dlg.Destroy()
528
529 # search
530 idents = dto.get_candidate_identities(can_create=True)
531 if idents is None:
532 gmGuiHelpers.gm_show_info (_(
533 'Cannot create new patient:\n\n'
534 ' [%s %s (%s), %s]'
535 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())),
536 _('Activating external patient')
537 )
538 return None
539
540 if len(idents) == 1:
541 ident = idents[0]
542
543 if len(idents) > 1:
544 if parent is None:
545 parent = wx.GetApp().GetTopWindow()
546 dlg = cSelectPersonFromListDlg(parent=parent, id=-1)
547 dlg.set_persons(persons=idents)
548 result = dlg.ShowModal()
549 if result == wx.ID_CANCEL:
550 return None
551 ident = dlg.get_selected_person()
552 dlg.Destroy()
553
554 if activate_immediately:
555 if not set_active_patient(patient = ident):
556 gmGuiHelpers.gm_show_info (
557 _(
558 'Cannot activate patient:\n\n'
559 '%s %s (%s)\n'
560 '%s'
561 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())),
562 _('Activating external patient')
563 )
564 return None
565
566 dto.import_extra_data(identity = ident)
567 dto.delete_from_source()
568
569 return ident
570 #============================================================
572 """Widget for smart search for persons."""
573
575
576 try:
577 kwargs['style'] = kwargs['style'] | wx.TE_PROCESS_ENTER
578 except KeyError:
579 kwargs['style'] = wx.TE_PROCESS_ENTER
580
581 # need to explicitly process ENTER events to avoid
582 # them being handed over to the next control
583 wx.TextCtrl.__init__(self, *args, **kwargs)
584
585 self.person = None
586
587 self.SetToolTipString (_(
588 'To search for a person type any of: \n'
589 '\n'
590 ' - fragment of last or first name\n'
591 " - date of birth (can start with '$' or '*')\n"
592 " - GNUmed ID of person (can start with '#')\n"
593 ' - exterenal ID of person\n'
594 '\n'
595 'and hit <ENTER>.\n'
596 '\n'
597 'Shortcuts:\n'
598 ' <F2>\n'
599 ' - scan external sources for persons\n'
600 ' <CURSOR-UP>\n'
601 ' - recall most recently used search term\n'
602 ' <CURSOR-DOWN>\n'
603 ' - list 10 most recently found persons\n'
604 ))
605
606 # FIXME: set query generator
607 self.__person_searcher = gmPerson.cPatientSearcher_SQL()
608
609 self._prev_search_term = None
610 self.__prev_idents = []
611 self._lclick_count = 0
612
613 self._display_name()
614
615 self.__register_events()
616 #--------------------------------------------------------
617 # utility methods
618 #--------------------------------------------------------
620 name = u''
621
622 if self.person is not None:
623 name = self.person['description']
624
625 self.SetValue(name)
626 #--------------------------------------------------------
628
629 if not isinstance(ident, gmPerson.cIdentity):
630 return False
631
632 # only unique identities
633 for known_ident in self.__prev_idents:
634 if known_ident['pk_identity'] == ident['pk_identity']:
635 return True
636
637 self.__prev_idents.append(ident)
638
639 # and only 10 of them
640 if len(self.__prev_idents) > 10:
641 self.__prev_idents.pop(0)
642
643 return True
644 #--------------------------------------------------------
645 # event handling
646 #--------------------------------------------------------
648 wx.EVT_CHAR(self, self.__on_char)
649 wx.EVT_SET_FOCUS(self, self._on_get_focus)
650 wx.EVT_KILL_FOCUS (self, self._on_loose_focus)
651 wx.EVT_TEXT_ENTER (self, self.GetId(), self.__on_enter)
652 #--------------------------------------------------------
654 """upon tabbing in
655
656 - select all text in the field so that the next
657 character typed will delete it
658 """
659 wx.CallAfter(self.SetSelection, -1, -1)
660 evt.Skip()
661 #--------------------------------------------------------
663 # - redraw the currently active name upon losing focus
664
665 # if we use wx.EVT_KILL_FOCUS we will also receive this event
666 # when closing our application or loosing focus to another
667 # application which is NOT what we intend to achieve,
668 # however, this is the least ugly way of doing this due to
669 # certain vagaries of wxPython (see the Wiki)
670
671 # just for good measure
672 wx.CallAfter(self.SetSelection, 0, 0)
673
674 self._display_name()
675 self._remember_ident(self.person)
676
677 evt.Skip()
678 #--------------------------------------------------------
681
683 """True: patient was selected.
684 False: no patient was selected.
685 """
686 keycode = evt.GetKeyCode()
687
688 # list of previously active patients
689 if keycode == wx.WXK_DOWN:
690 evt.Skip()
691 if len(self.__prev_idents) == 0:
692 return False
693
694 dlg = cSelectPersonFromListDlg(parent = wx.GetTopLevelParent(self), id = -1)
695 dlg.set_persons(persons = self.__prev_idents)
696 result = dlg.ShowModal()
697 if result == wx.ID_OK:
698 wx.BeginBusyCursor()
699 self.person = dlg.get_selected_person()
700 self._display_name()
701 dlg.Destroy()
702 wx.EndBusyCursor()
703 return True
704
705 dlg.Destroy()
706 return False
707
708 # recall previous search fragment
709 if keycode == wx.WXK_UP:
710 evt.Skip()
711 # FIXME: cycling through previous fragments
712 if self._prev_search_term is not None:
713 self.SetValue(self._prev_search_term)
714 return False
715
716 # invoke external patient sources
717 if keycode == wx.WXK_F2:
718 evt.Skip()
719 dbcfg = gmCfg.cCfgSQL()
720 search_immediately = bool(dbcfg.get2 (
721 option = 'patient_search.external_sources.immediately_search_if_single_source',
722 workplace = gmSurgery.gmCurrentPractice().active_workplace,
723 bias = 'user',
724 default = 0
725 ))
726 p = get_person_from_external_sources (
727 parent = wx.GetTopLevelParent(self),
728 search_immediately = search_immediately
729 )
730 if p is not None:
731 self.person = p
732 self._display_name()
733 return True
734 return False
735
736 # FIXME: invoke add new person
737 # FIXME: add popup menu apart from system one
738
739 evt.Skip()
740 #--------------------------------------------------------
742 """This is called from the ENTER handler."""
743
744 # ENTER but no search term ?
745 curr_search_term = self.GetValue().strip()
746 if curr_search_term == '':
747 return None
748
749 # same person anywys ?
750 if self.person is not None:
751 if curr_search_term == self.person['description']:
752 return None
753
754 # remember search fragment
755 if self.IsModified():
756 self._prev_search_term = curr_search_term
757
758 self._on_enter(search_term = curr_search_term)
759 #--------------------------------------------------------
761 """This can be overridden in child classes."""
762
763 wx.BeginBusyCursor()
764
765 # get list of matching ids
766 idents = self.__person_searcher.get_identities(search_term)
767
768 if idents is None:
769 wx.EndBusyCursor()
770 gmGuiHelpers.gm_show_info (
771 _('Error searching for matching persons.\n\n'
772 'Search term: "%s"'
773 ) % search_term,
774 _('selecting person')
775 )
776 return None
777
778 _log.info("%s matching person(s) found", len(idents))
779
780 if len(idents) == 0:
781 wx.EndBusyCursor()
782
783 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
784 wx.GetTopLevelParent(self),
785 -1,
786 caption = _('Selecting patient'),
787 question = _(
788 'Cannot find any matching patients for the search term\n\n'
789 ' "%s"\n\n'
790 'You may want to try a shorter search term.\n'
791 ) % search_term,
792 button_defs = [
793 {'label': _('Go back'), 'tooltip': _('Go back and search again.'), 'default': True},
794 {'label': _('Create new'), 'tooltip': _('Create new patient.')}
795 ]
796 )
797 if dlg.ShowModal() != wx.ID_NO:
798 return
799
800 #wiz = gmDemographicsWidgets.cNewPatientWizard(parent = self.GetParent())
801 #result = wiz.RunWizard(activate = False)
802 #if result is False:
803 # return None
804 #self.person = result
805 success = gmDemographicsWidgets.create_new_person(parent = self, activate = True)
806 if success:
807 self.person = gmPerson.gmCurrentPatient()
808 else:
809 self.person = None
810 self._display_name()
811 return None
812
813 # only one matching identity
814 if len(idents) == 1:
815 self.person = idents[0]
816 self._display_name() # needed when the found patient is the same as the active one
817 wx.EndBusyCursor()
818 return None
819
820 # more than one matching identity: let user select from pick list
821 dlg = cSelectPersonFromListDlg(parent=wx.GetTopLevelParent(self), id=-1)
822 dlg.set_persons(persons=idents)
823 wx.EndBusyCursor()
824 result = dlg.ShowModal()
825 if result == wx.ID_CANCEL:
826 dlg.Destroy()
827 return None
828
829 wx.BeginBusyCursor()
830 self.person = dlg.get_selected_person()
831 dlg.Destroy()
832 self._display_name() # needed when the found patient is the same as the active one
833 wx.EndBusyCursor()
834
835 return None
836 #============================================================
838
839 # warn if DOB is missing
840 try:
841 patient['dob']
842 check_dob = True
843 except TypeError:
844 check_dob = False
845
846 if check_dob:
847 if patient['dob'] is None:
848 gmGuiHelpers.gm_show_warning (
849 aTitle = _('Checking date of birth'),
850 aMessage = _(
851 '\n'
852 ' %s\n'
853 '\n'
854 'The date of birth for this patient is not known !\n'
855 '\n'
856 'You can proceed to work on the patient but\n'
857 'GNUmed will be unable to assist you with\n'
858 'age-related decisions.\n'
859 ) % patient['description_gender']
860 )
861
862 return gmPerson.set_active_patient(patient = patient, forced_reload = forced_reload)
863 #------------------------------------------------------------
865
867
868 cPersonSearchCtrl.__init__(self, *args, **kwargs)
869
870 selector_tooltip = _(
871 'Patient search field. \n'
872 '\n'
873 'To search, type any of:\n'
874 ' - fragment of last or first name\n'
875 " - date of birth (can start with '$' or '*')\n"
876 " - patient ID (can start with '#')\n"
877 'and hit <ENTER>.\n'
878 '\n'
879 '<CURSOR-UP>\n'
880 ' - recall most recently used search term\n'
881 '<CURSOR-DOWN>\n'
882 ' - list 10 most recently activated patients\n'
883 '<F2>\n'
884 ' - scan external sources for patients to import and activate\n'
885 )
886 self.SetToolTip(wx.ToolTip(selector_tooltip))
887
888 # get configuration
889 cfg = gmCfg.cCfgSQL()
890
891 self.__always_dismiss_on_search = bool (
892 cfg.get2 (
893 option = 'patient_search.always_dismiss_previous_patient',
894 workplace = gmSurgery.gmCurrentPractice().active_workplace,
895 bias = 'user',
896 default = 0
897 )
898 )
899
900 self.__always_reload_after_search = bool (
901 cfg.get2 (
902 option = 'patient_search.always_reload_new_patient',
903 workplace = gmSurgery.gmCurrentPractice().active_workplace,
904 bias = 'user',
905 default = 0
906 )
907 )
908
909 self.__register_events()
910 #--------------------------------------------------------
911 # utility methods
912 #--------------------------------------------------------
914 #name = u''
915 name = _('<type here to search patient>')
916
917 curr_pat = gmPerson.gmCurrentPatient()
918 if curr_pat.connected:
919 name = curr_pat['description']
920 if curr_pat.locked:
921 name = _('%(name)s (locked)') % {'name': name}
922
923 self.SetValue(name)
924 #--------------------------------------------------------
926 if not set_active_patient(patient=pat, forced_reload = self.__always_reload_after_search):
927 _log.error('cannot change active patient')
928 return None
929
930 self._remember_ident(pat)
931
932 dbcfg = gmCfg.cCfgSQL()
933 dob_distance = dbcfg.get2 (
934 option = u'patient_search.dob_warn_interval',
935 workplace = gmSurgery.gmCurrentPractice().active_workplace,
936 bias = u'user',
937 default = u'1 week'
938 )
939
940 if pat.dob_in_range(dob_distance, dob_distance):
941 now = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone)
942 enc = gmI18N.get_encoding()
943 gmDispatcher.send(signal = 'statustext', msg = _(
944 '%(pat)s turns %(age)s on %(month)s %(day)s ! (today is %(month_now)s %(day_now)s)') % {
945 'pat': pat.get_description_gender(),
946 'age': pat.get_medical_age().strip('y'),
947 'month': pat.get_formatted_dob(format = '%B', encoding = enc),
948 'day': pat.get_formatted_dob(format = '%d', encoding = enc),
949 'month_now': now.strftime('%B').decode(enc),
950 'day_now': now.strftime('%d')
951 }
952 )
953
954 return True
955 #--------------------------------------------------------
956 # event handling
957 #--------------------------------------------------------
959 # client internal signals
960 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
961 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_name_identity_change)
962 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_name_identity_change)
963
964 gmDispatcher.connect(signal = 'patient_locked', receiver = self._on_post_patient_selection)
965 gmDispatcher.connect(signal = 'patient_unlocked', receiver = self._on_post_patient_selection)
966 #----------------------------------------------
969 #----------------------------------------------
971 if gmPerson.gmCurrentPatient().connected:
972 self.person = gmPerson.gmCurrentPatient().patient
973 else:
974 self.person = None
975 wx.CallAfter(self._display_name)
976 #----------------------------------------------
978
979 if self.__always_dismiss_on_search:
980 _log.warning("dismissing patient before patient search")
981 self._set_person_as_active_patient(-1)
982
983 super(self.__class__, self)._on_enter(search_term=search_term)
984
985 if self.person is None:
986 return
987
988 self._set_person_as_active_patient(self.person)
989 self._display_name()
990 #----------------------------------------------
996 #============================================================
997 # waiting list widgets
998 #============================================================
1000
1002
1003 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1004
1005 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = [])
1006 mp.setThresholds(1, 2, 2)
1007 self.matcher = mp
1008 self.selection_only = False
1009
1010 #--------------------------------------------------------
1012 self.matcher.set_items([ {'data': i, 'label': i, 'weight': 1} for i in items ])
1013
1014 #============================================================
1015 from Gnumed.wxGladeWidgets import wxgWaitingListEntryEditAreaPnl
1016
1017 -class cWaitingListEntryEditAreaPnl(wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
1018
1020
1021 try:
1022 self.patient = kwargs['patient']
1023 del kwargs['patient']
1024 except KeyError:
1025 self.patient = None
1026
1027 try:
1028 data = kwargs['entry']
1029 del kwargs['entry']
1030 except KeyError:
1031 data = None
1032
1033 wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl.__init__(self, *args, **kwargs)
1034 gmEditArea.cGenericEditAreaMixin.__init__(self)
1035
1036 if data is None:
1037 self.mode = 'new'
1038 else:
1039 self.data = data
1040 self.mode = 'edit'
1041
1042 praxis = gmSurgery.gmCurrentPractice()
1043 pats = praxis.waiting_list_patients
1044 zones = {}
1045 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ])
1046 self._PRW_zone.update_matcher(items = zones.keys())
1047 #--------------------------------------------------------
1048 # edit area mixin API
1049 #--------------------------------------------------------
1051 if self.patient is None:
1052 self._PRW_patient.person = None
1053 self._PRW_patient.Enable(True)
1054 self._PRW_patient.SetFocus()
1055 else:
1056 self._PRW_patient.person = self.patient
1057 self._PRW_patient.Enable(False)
1058 self._PRW_comment.SetFocus()
1059 self._PRW_patient._display_name()
1060
1061 self._PRW_comment.SetValue(u'')
1062 self._PRW_zone.SetValue(u'')
1063 self._SPCTRL_urgency.SetValue(0)
1064 #--------------------------------------------------------
1066 self._PRW_patient.person = gmPerson.cIdentity(aPK_obj = self.data['pk_identity'])
1067 self._PRW_patient.Enable(False)
1068 self._PRW_patient._display_name()
1069
1070 self._PRW_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
1071 self._PRW_zone.SetValue(gmTools.coalesce(self.data['waiting_zone'], u''))
1072 self._SPCTRL_urgency.SetValue(self.data['urgency'])
1073
1074 self._PRW_comment.SetFocus()
1075 #--------------------------------------------------------
1077 validity = True
1078
1079 self.display_tctrl_as_valid(tctrl = self._PRW_patient, valid = (self._PRW_patient.person is not None))
1080 validity = (self._PRW_patient.person is not None)
1081
1082 if validity is False:
1083 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add to waiting list. Missing essential input.'))
1084
1085 return validity
1086 #----------------------------------------------------------------
1088 # FIXME: filter out dupes
1089 self._PRW_patient.person.put_on_waiting_list (
1090 urgency = self._SPCTRL_urgency.GetValue(),
1091 comment = gmTools.none_if(self._PRW_comment.GetValue().strip(), u''),
1092 zone = gmTools.none_if(self._PRW_zone.GetValue().strip(), u'')
1093 )
1094 # dummy:
1095 self.data = {'pk_identity': None, 'comment': None, 'waiting_zone': None, 'urgency': 0}
1096 return True
1097 #----------------------------------------------------------------
1099 gmSurgery.gmCurrentPractice().update_in_waiting_list (
1100 pk = self.data['pk_waiting_list'],
1101 urgency = self._SPCTRL_urgency.GetValue(),
1102 comment = self._PRW_comment.GetValue().strip(),
1103 zone = self._PRW_zone.GetValue().strip()
1104 )
1105 return True
1106 #============================================================
1107 from Gnumed.wxGladeWidgets import wxgWaitingListPnl
1108
1110
1112
1113 wxgWaitingListPnl.wxgWaitingListPnl.__init__(self, *args, **kwargs)
1114 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1115
1116 self.__current_zone = None
1117
1118 self.__init_ui()
1119 self.__register_events()
1120 #--------------------------------------------------------
1121 # interal helpers
1122 #--------------------------------------------------------
1124 self._LCTRL_patients.set_columns ([
1125 _('Zone'),
1126 _('Urgency'),
1127 #' ! ',
1128 _('Waiting time'),
1129 _('Patient'),
1130 _('Born'),
1131 _('Comment')
1132 ])
1133 self._LCTRL_patients.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1134 self._PRW_zone.add_callback_on_selection(callback = self._on_zone_selected)
1135 self._PRW_zone.add_callback_on_lose_focus(callback = self._on_zone_selected)
1136 #--------------------------------------------------------
1138 gmDispatcher.connect(signal = u'waiting_list_generic_mod_db', receiver = self._on_waiting_list_modified)
1139 #--------------------------------------------------------
1141
1142 praxis = gmSurgery.gmCurrentPractice()
1143 pats = praxis.waiting_list_patients
1144
1145 # set matcher to all zones currently in use
1146 zones = {}
1147 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ])
1148 self._PRW_zone.update_matcher(items = zones.keys())
1149 del zones
1150
1151 # filter patient list by zone and set waiting list
1152 self.__current_zone = self._PRW_zone.GetValue().strip()
1153 if self.__current_zone == u'':
1154 pats = [ p for p in pats ]
1155 else:
1156 pats = [ p for p in pats if p['waiting_zone'] == self.__current_zone ]
1157
1158 self._LCTRL_patients.set_string_items (
1159 [ [
1160 gmTools.coalesce(p['waiting_zone'], u''),
1161 p['urgency'],
1162 p['waiting_time_formatted'].replace(u'00 ', u'', 1).replace('00:', u'').lstrip('0'),
1163 u'%s, %s (%s)' % (p['lastnames'], p['firstnames'], p['l10n_gender']),
1164 p['dob'].strftime('%x').decode(gmI18N.get_encoding()),
1165 gmTools.coalesce(p['comment'], u'')
1166 ] for p in pats
1167 ]
1168 )
1169 self._LCTRL_patients.set_column_widths()
1170 self._LCTRL_patients.set_data(pats)
1171 self._LCTRL_patients.Refresh()
1172 self._LCTRL_patients.SetToolTipString ( _(
1173 '%s patients are waiting.\n'
1174 '\n'
1175 'Doubleclick to activate (entry will stay in list).'
1176 ) % len(pats))
1177
1178 self._LBL_no_of_patients.SetLabel(_('(%s patients)') % len(pats))
1179
1180 if len(pats) == 0:
1181 self._BTN_activate.Enable(False)
1182 self._BTN_activateplus.Enable(False)
1183 self._BTN_remove.Enable(False)
1184 self._BTN_edit.Enable(False)
1185 self._BTN_up.Enable(False)
1186 self._BTN_down.Enable(False)
1187 else:
1188 self._BTN_activate.Enable(True)
1189 self._BTN_activateplus.Enable(True)
1190 self._BTN_remove.Enable(True)
1191 self._BTN_edit.Enable(True)
1192 if len(pats) > 1:
1193 self._BTN_up.Enable(True)
1194 self._BTN_down.Enable(True)
1195 #--------------------------------------------------------
1196 # event handlers
1197 #--------------------------------------------------------
1199 if self.__current_zone == self._PRW_zone.GetValue().strip():
1200 return True
1201 wx.CallAfter(self.__refresh_waiting_list)
1202 return True
1203 #--------------------------------------------------------
1206 #--------------------------------------------------------
1208 item = self._LCTRL_patients.get_selected_item_data(only_one=True)
1209 if item is None:
1210 return
1211 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity'])
1212 wx.CallAfter(set_active_patient, patient = pat)
1213 #--------------------------------------------------------
1220 #--------------------------------------------------------
1228 #--------------------------------------------------------
1240 #--------------------------------------------------------
1249 #--------------------------------------------------------
1255 #--------------------------------------------------------
1261 #--------------------------------------------------------
1267 #--------------------------------------------------------
1268 # edit
1269 #--------------------------------------------------------
1270 # reget-on-paint API
1271 #--------------------------------------------------------
1275 #============================================================
1276 # main
1277 #------------------------------------------------------------
1278 if __name__ == "__main__":
1279
1280 if len(sys.argv) > 1:
1281 if sys.argv[1] == 'test':
1282 gmI18N.activate_locale()
1283 gmI18N.install_domain()
1284
1285 app = wx.PyWidgetTester(size = (200, 40))
1286 # app.SetWidget(cSelectPersonFromListDlg, -1)
1287 # app.SetWidget(cPersonSearchCtrl, -1)
1288 # app.SetWidget(cActivePatientSelector, -1)
1289 app.SetWidget(cWaitingListPnl, -1)
1290 app.MainLoop()
1291
1292 #============================================================
1293 # docs
1294 #------------------------------------------------------------
1295 # functionality
1296 # -------------
1297 # - hitting ENTER on non-empty field (and more than threshold chars)
1298 # - start search
1299 # - display results in a list, prefixed with numbers
1300 # - last name
1301 # - first name
1302 # - gender
1303 # - age
1304 # - city + street (no ZIP, no number)
1305 # - last visit (highlighted if within a certain interval)
1306 # - arbitrary marker (e.g. office attendance this quartal, missing KVK, appointments, due dates)
1307 # - if none found -> go to entry of new patient
1308 # - scrolling in this list
1309 # - ENTER selects patient
1310 # - ESC cancels selection
1311 # - number selects patient
1312 #
1313 # - hitting cursor-up/-down
1314 # - cycle through history of last 10 search fragments
1315 #
1316 # - hitting alt-L = List, alt-P = previous
1317 # - show list of previous ten patients prefixed with numbers
1318 # - scrolling in list
1319 # - ENTER selects patient
1320 # - ESC cancels selection
1321 # - number selects patient
1322 #
1323 # - hitting ALT-N
1324 # - immediately goes to entry of new patient
1325 #
1326 # - hitting cursor-right in a patient selection list
1327 # - pops up more detail about the patient
1328 # - ESC/cursor-left goes back to list
1329 #
1330 # - hitting TAB
1331 # - makes sure the currently active patient is displayed
1332
1333 #------------------------------------------------------------
1334 # samples
1335 # -------
1336 # working:
1337 # Ian Haywood
1338 # Haywood Ian
1339 # Haywood
1340 # Amador Jimenez (yes, two last names but no hyphen: Spain, for example)
1341 # Ian Haywood 19/12/1977
1342 # 19/12/1977
1343 # 19-12-1977
1344 # 19.12.1977
1345 # 19771219
1346 # $dob
1347 # *dob
1348 # #ID
1349 # ID
1350 # HIlbert, karsten
1351 # karsten, hilbert
1352 # kars, hilb
1353 #
1354 # non-working:
1355 # Haywood, Ian <40
1356 # ?, Ian 1977
1357 # Ian Haywood, 19/12/77
1358 # PUPIC
1359 # "hilb; karsten, 23.10.74"
1360
1361 #------------------------------------------------------------
1362 # notes
1363 # -----
1364 # >> 3. There are countries in which people have more than one
1365 # >> (significant) lastname (spanish-speaking countries are one case :), some
1366 # >> asian countries might be another one).
1367 # -> we need per-country query generators ...
1368
1369 # search case sensitive by default, switch to insensitive if not found ?
1370
1371 # accent insensitive search:
1372 # select * from * where to_ascii(column, 'encoding') like '%test%';
1373 # may not work with Unicode
1374
1375 # phrase wheel is most likely too slow
1376
1377 # extend search fragment history
1378
1379 # ask user whether to send off level 3 queries - or thread them
1380
1381 # we don't expect patient IDs in complicated patterns, hence any digits signify a date
1382
1383 # FIXME: make list window fit list size ...
1384
1385 # clear search field upon get-focus ?
1386
1387 # F1 -> context help with hotkey listing
1388
1389 # th -> th|t
1390 # v/f/ph -> f|v|ph
1391 # maybe don't do umlaut translation in the first 2-3 letters
1392 # such that not to defeat index use for the first level query ?
1393
1394 # user defined function key to start search
1395
1396 #============================================================
1397 # $Log: gmPatSearchWidgets.py,v $
1398 # Revision 1.132 2010/02/07 15:17:06 ncq
1399 # - don't use the old new-patient wizard anymore
1400 #
1401 # Revision 1.131 2010/01/31 18:19:41 ncq
1402 # - show hint when no patient selected
1403 #
1404 # Revision 1.130 2009/12/21 15:12:29 ncq
1405 # - cleanup
1406 # - fix typo
1407 # - missing return
1408 #
1409 # Revision 1.129 2009/11/15 01:10:34 ncq
1410 # - cleanup
1411 #
1412 # Revision 1.128 2009/07/17 09:25:06 ncq
1413 # - ! -> Urgency as per list
1414 # - adding acts on the current patient *only*
1415 # - add missing Destroy
1416 #
1417 # Revision 1.127 2009/07/02 20:56:26 ncq
1418 # - used edit area dlg2
1419 #
1420 # Revision 1.126 2009/07/01 17:10:35 ncq
1421 # - need to return state from set_active_patient
1422 #
1423 # Revision 1.125 2009/06/20 12:47:17 ncq
1424 # - only display last encounter in search results if
1425 # patient has clinical data (that is, is a patient)
1426 #
1427 # Revision 1.124 2009/06/04 16:27:47 ncq
1428 # - add set active patient and use it
1429 # - adjust to dob-less persons
1430 #
1431 # Revision 1.123 2009/04/21 17:00:00 ncq
1432 # - edit area dlg now takes single_entry argument
1433 #
1434 # Revision 1.122 2009/02/05 14:30:36 ncq
1435 # - only run new-patient-wizard if user explicitely said so
1436 # - do not try to set active patient if user cancelled new patient wizard
1437 #
1438 # Revision 1.121 2009/02/04 12:35:18 ncq
1439 # - support editing waiting list entries
1440 #
1441 # Revision 1.120 2009/01/30 12:11:43 ncq
1442 # - waiting list entry edit area
1443 #
1444 # Revision 1.119 2009/01/22 11:16:41 ncq
1445 # - implement moving waiting list entries
1446 #
1447 # Revision 1.118 2009/01/21 22:39:02 ncq
1448 # - waiting zones phrasewheel and use it
1449 #
1450 # Revision 1.117 2009/01/21 18:04:41 ncq
1451 # - implement most of waiting list
1452 #
1453 # Revision 1.116 2009/01/17 23:08:31 ncq
1454 # - waiting list
1455 #
1456 # Revision 1.115 2008/12/17 21:59:22 ncq
1457 # - add support for merging patients
1458 #
1459 # Revision 1.114 2008/12/09 23:43:27 ncq
1460 # - use description_gender
1461 # - no more hardcoded plugin raising after patient activation
1462 #
1463 # Revision 1.113 2008/10/12 16:26:46 ncq
1464 # - cleanup
1465 #
1466 # Revision 1.112 2008/09/01 20:28:51 ncq
1467 # - properly handle case when several option sources define AU PracSoft source
1468 #
1469 # Revision 1.111 2008/08/28 18:34:18 ncq
1470 # - make active patient selector react to patient activation,
1471 # name/identity change all by itself with updating its display,
1472 # don't let top panel do it for us
1473 #
1474 # Revision 1.110 2008/07/28 20:27:20 ncq
1475 # - do not try to activate None person
1476 #
1477 # Revision 1.109 2008/07/07 13:43:17 ncq
1478 # - current patient .connected
1479 #
1480 # Revision 1.108 2008/05/13 14:13:57 ncq
1481 # - fix on-focus-select-all behaviour
1482 # - don't display search term after name - when a search failed this gets confusing
1483 #
1484 # Revision 1.107 2008/04/16 20:39:39 ncq
1485 # - working versions of the wxGlade code and use it, too
1486 # - show client version in login dialog
1487 #
1488 # Revision 1.106 2008/03/20 15:31:59 ncq
1489 # - missing \n added
1490 #
1491 # Revision 1.105 2008/03/09 20:18:22 ncq
1492 # - cleanup
1493 # - load_patient_* -> get_person_*
1494 # - make cPatientSelector() generic -> cPersonSearchCtrl()
1495 #
1496 # Revision 1.104 2008/02/25 17:40:18 ncq
1497 # - new style logging
1498 #
1499 # Revision 1.103 2008/01/30 14:09:39 ncq
1500 # - switch to new style cfg file support
1501 # - cleanup
1502 #
1503 # Revision 1.102 2008/01/27 21:17:49 ncq
1504 # - improve message on patient not found
1505 #
1506 # Revision 1.101 2008/01/22 12:24:55 ncq
1507 # - include search fragment into patient name display
1508 # - reenable on kill focus handler restoring patient name
1509 # - improved wording on patient not found
1510 #
1511 # Revision 1.100 2008/01/11 16:15:33 ncq
1512 # - first/last -> first-/lastnames
1513 #
1514 # Revision 1.99 2008/01/05 16:41:27 ncq
1515 # - remove logging from gm_show_*()
1516 #
1517 # Revision 1.98 2007/12/11 12:49:26 ncq
1518 # - explicit signal handling
1519 #
1520 # Revision 1.97 2007/11/12 23:05:55 ncq
1521 # - import extra data from DTOs
1522 #
1523 # Revision 1.96 2007/11/10 20:58:59 ncq
1524 # - use dto.get_candidate_identities() and dto.delete_from_source()
1525 #
1526 # Revision 1.95 2007/10/19 12:52:34 ncq
1527 # - implement search_immediately in load_patient_from_external_source()
1528 #
1529 # Revision 1.94 2007/10/12 14:20:09 ncq
1530 # - prepare "activate_immediately" in load_patient_from_external_sources()
1531 #
1532 # Revision 1.93 2007/10/12 13:33:06 ncq
1533 # - if only one external patient available - activate it right away
1534 #
1535 # Revision 1.92 2007/10/11 12:15:09 ncq
1536 # - make filling patient selector list more robust in absence of match_type field
1537 #
1538 # Revision 1.91 2007/10/07 12:32:42 ncq
1539 # - workplace property now on gmSurgery.gmCurrentPractice() borg
1540 #
1541 # Revision 1.90 2007/09/10 12:38:12 ncq
1542 # - improve wording on announcing upcoming patient birthday
1543 #
1544 # Revision 1.89 2007/08/28 14:18:13 ncq
1545 # - no more gm_statustext()
1546 #
1547 # Revision 1.88 2007/08/12 00:12:41 ncq
1548 # - no more gmSignals.py
1549 #
1550 # Revision 1.87 2007/07/17 16:00:28 ncq
1551 # - check existence of PracSoft import file
1552 #
1553 # Revision 1.86 2007/07/11 21:11:08 ncq
1554 # - display patient locked state
1555 # - listen on patient lock/unlock events
1556 #
1557 # Revision 1.85 2007/07/09 12:46:33 ncq
1558 # - move cDataMiningPnl to gmDataMiningWidgets.py
1559 #
1560 # Revision 1.84 2007/07/07 12:43:25 ncq
1561 # - in cDataMiningPnl use cPatientListingCtrl
1562 #
1563 # Revision 1.83 2007/06/28 12:40:48 ncq
1564 # - handle dto.dob being optional now
1565 # - support dto source gotten from xdt file
1566 #
1567 # Revision 1.82 2007/06/12 16:03:58 ncq
1568 # - some comments
1569 # - fix typo
1570 # - better error display on failing queries
1571 #
1572 # Revision 1.81 2007/06/10 10:12:55 ncq
1573 # - options need names
1574 #
1575 # Revision 1.80 2007/05/18 15:55:58 ncq
1576 # - auto-select first item in person/dto selector
1577 #
1578 # Revision 1.79 2007/05/14 14:56:41 ncq
1579 # - fix typo
1580 #
1581 # Revision 1.78 2007/05/14 13:52:24 ncq
1582 # - add display_name() in two places to fix visual glitch with search
1583 #
1584 # Revision 1.77 2007/05/14 13:37:42 ncq
1585 # - don't do anything if the only external patient is
1586 # already the active patient in GNUmed
1587 #
1588 # Revision 1.76 2007/05/14 13:11:24 ncq
1589 # - use statustext() signal
1590 #
1591 # Revision 1.75 2007/05/07 08:04:36 ncq
1592 # - a bit of cleanup
1593 #
1594 # Revision 1.74 2007/04/19 13:13:47 ncq
1595 # - cleanup
1596 #
1597 # Revision 1.73 2007/04/11 14:53:33 ncq
1598 # - do some safeguarding against binary/large files being dropped onto
1599 # the data mining plugin - check mimetype and size
1600 #
1601 # Revision 1.72 2007/04/09 22:03:57 ncq
1602 # - make data mining panel a file drop target
1603 #
1604 # Revision 1.71 2007/04/09 21:12:49 ncq
1605 # - better wording in contribute email
1606 # - properly unicode() SQL results
1607 #
1608 # Revision 1.70 2007/04/09 18:52:47 ncq
1609 # - magic patient activation from report result list
1610 #
1611 # Revision 1.69 2007/04/09 16:31:06 ncq
1612 # - add _on_contribute
1613 #
1614 # Revision 1.68 2007/04/08 21:17:14 ncq
1615 # - add more event handlers to data mining panel
1616 #
1617 # Revision 1.67 2007/04/07 22:45:28 ncq
1618 # - add save handler to data mining panel
1619 #
1620 # Revision 1.66 2007/04/06 23:15:21 ncq
1621 # - add data mining panel
1622 #
1623 # Revision 1.65 2007/04/01 15:29:51 ncq
1624 # - safely get_encoding()
1625 #
1626 # Revision 1.64 2007/03/02 15:38:47 ncq
1627 # - decode() strftime() to u''
1628 #
1629 # Revision 1.63 2007/02/22 17:41:13 ncq
1630 # - adjust to gmPerson changes
1631 #
1632 # Revision 1.62 2007/02/17 14:01:26 ncq
1633 # - gmCurrentProvider.workplace now property
1634 # - notify about birthday after activating patient
1635 # - remove crufty code/docs
1636 #
1637 # Revision 1.61 2007/02/15 14:58:08 ncq
1638 # - tie KVKs intoi external patient sources framework
1639 #
1640 # Revision 1.60 2007/02/13 17:07:38 ncq
1641 # - tie PracSoft PATIENTS.IN file into external patients framework
1642 # - *always* let user decide on whether to activate an external patient
1643 # even if only a single source provides a patient
1644 #
1645 # Revision 1.59 2007/01/20 22:52:27 ncq
1646 # - .KeyCode -> GetKeyCode()
1647 #
1648 # Revision 1.58 2007/01/18 22:07:52 ncq
1649 # - (Get)KeyCode() -> KeyCode so 2.8 can do
1650 #
1651 # Revision 1.57 2007/01/10 23:04:12 ncq
1652 # - support explicit DOB format for xDT files
1653 #
1654 # Revision 1.56 2006/12/13 14:57:16 ncq
1655 # - inform about no patients found in external sources
1656 #
1657 # Revision 1.55 2006/11/24 14:23:19 ncq
1658 # - self.Close() does not need wx.ID_*
1659 #
1660 # Revision 1.54 2006/11/24 09:56:03 ncq
1661 # - improved message when error searching patient
1662 #
1663 # Revision 1.53 2006/11/20 19:11:04 ncq
1664 # - improved message when no matching patient found
1665 #
1666 # Revision 1.52 2006/11/20 17:05:55 ncq
1667 # - do not search if supposed search term matches 'description' of current patient
1668 #
1669 # Revision 1.51 2006/11/01 12:54:40 ncq
1670 # - there may not be a previous encounter so don't try to
1671 # format it's start date if so
1672 #
1673 # Revision 1.50 2006/10/31 12:43:09 ncq
1674 # - out with the crap
1675 # - no more patient expanders
1676 #
1677 # Revision 1.49 2006/10/30 16:46:52 ncq
1678 # - missing encoding in xDT source defs does not *have* to be
1679 # an error as the file itself may contain the encoding itself
1680 #
1681 # Revision 1.48 2006/10/28 14:57:17 ncq
1682 # - use cPatient.get_last_encounter()
1683 #
1684 # Revision 1.47 2006/10/28 12:34:53 ncq
1685 # - make person and dto selector dialogs handle functionality themselves
1686 # - remove person selector panel class
1687 # - act on ENTER/double-click in person/dto select list
1688 #
1689 # Revision 1.46 2006/10/25 07:46:44 ncq
1690 # - Format() -> strftime() since datetime.datetime does not have .Format()
1691 #
1692 # Revision 1.45 2006/10/24 13:26:43 ncq
1693 # - switch to gmPG2
1694 #
1695 # Revision 1.44 2006/09/13 07:55:11 ncq
1696 # - handle encoding in xDT patient sources
1697 #
1698 # Revision 1.43 2006/09/06 07:22:34 ncq
1699 # - add missing import for glob module
1700 #
1701 # Revision 1.42 2006/09/01 14:46:30 ncq
1702 # - add (untested) MCS/Isynet external patient source
1703 #
1704 # Revision 1.41 2006/08/09 15:00:47 ncq
1705 # - better search widget tooltip
1706 #
1707 # Revision 1.40 2006/07/30 18:48:18 ncq
1708 # - invoke load_external_patient on <F2> in searcher
1709 # - robustify by commenting out shaky KVK code
1710 #
1711 # Revision 1.39 2006/07/30 17:51:00 ncq
1712 # - cleanup
1713 #
1714 # Revision 1.38 2006/07/27 17:07:18 ncq
1715 # - cleanup
1716 # - make Cursor-Down the way to invoke previous patients
1717 #
1718 # Revision 1.37 2006/07/26 13:22:37 ncq
1719 # - degrade non-fatal error messages to info messages
1720 #
1721 # Revision 1.36 2006/07/26 13:15:03 ncq
1722 # - cleanup
1723 #
1724 # Revision 1.35 2006/07/24 19:38:39 ncq
1725 # - fix "prev patients" list (alt-p) in patient selector
1726 # - start obsoleting old (ugly) patient pick list
1727 #
1728 # Revision 1.34 2006/07/24 14:18:31 ncq
1729 # - finish pat/dto selection dialogs
1730 # - use them in loading external patients and selecting among matches in search control
1731 #
1732 # Revision 1.33 2006/07/24 11:31:11 ncq
1733 # - cleanup
1734 # - add dialogs to select person/person-dto from list
1735 # - use dto-selection dialog when loading external patient
1736 #
1737 # Revision 1.32 2006/07/22 15:18:24 ncq
1738 # - better error logging
1739 #
1740 # Revision 1.31 2006/07/21 14:48:39 ncq
1741 # - proper returns from load_patient_from_external_sources()
1742 #
1743 # Revision 1.30 2006/07/19 21:41:13 ncq
1744 # - support list of xdt files
1745 #
1746 # Revision 1.29 2006/07/18 21:18:13 ncq
1747 # - add proper load_patient_from_external_sources()
1748 #
1749 # Revision 1.28 2006/05/15 13:36:00 ncq
1750 # - signal cleanup:
1751 # - activating_patient -> pre_patient_selection
1752 # - patient_selected -> post_patient_selection
1753 #
1754 # Revision 1.27 2006/05/12 12:18:11 ncq
1755 # - whoami -> whereami cleanup
1756 # - use gmCurrentProvider()
1757 #
1758 # Revision 1.26 2006/05/04 09:49:20 ncq
1759 # - get_clinical_record() -> get_emr()
1760 # - adjust to changes in set_active_patient()
1761 # - need explicit set_active_patient() after ask_for_patient() if wanted
1762 #
1763 # Revision 1.25 2005/12/14 17:01:51 ncq
1764 # - use improved db cfg option getting
1765 #
1766 # Revision 1.24 2005/09/28 21:27:30 ncq
1767 # - a lot of wx2.6-ification
1768 #
1769 # Revision 1.23 2005/09/27 20:44:59 ncq
1770 # - wx.wx* -> wx.*
1771 #
1772 # Revision 1.22 2005/09/26 18:01:51 ncq
1773 # - use proper way to import wx26 vs wx2.4
1774 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES
1775 # - time for fixup
1776 #
1777 # Revision 1.21 2005/09/24 09:17:29 ncq
1778 # - some wx2.6 compatibility fixes
1779 #
1780 # Revision 1.20 2005/09/12 15:18:05 ncq
1781 # - fix faulty call to SetActivePatient() found by Richard when using
1782 # always_dismiss_after_search
1783 #
1784 # Revision 1.19 2005/09/11 17:35:05 ncq
1785 # - support "patient_search.always_reload_new_patient"
1786 #
1787 # Revision 1.18 2005/09/04 07:31:14 ncq
1788 # - Richard requested the "no active patient" tag be removed
1789 # when no patient is active
1790 #
1791 # Revision 1.17 2005/05/05 06:29:22 ncq
1792 # - if patient not found invoke new patient wizard with activate=true
1793 #
1794 # Revision 1.16 2005/03/08 16:54:13 ncq
1795 # - teach patient picklist about cIdentity
1796 #
1797 # Revision 1.15 2005/02/20 10:33:26 sjtan
1798 #
1799 # disable lose focus to prevent core dumping in a wxPython version.
1800 #
1801 # Revision 1.14 2005/02/13 15:28:07 ncq
1802 # - v_basic_person.i_pk -> pk_identity
1803 #
1804 # Revision 1.13 2005/02/12 13:59:11 ncq
1805 # - v_basic_person.i_id -> i_pk
1806 #
1807 # Revision 1.12 2005/02/01 10:16:07 ihaywood
1808 # refactoring of gmDemographicRecord and follow-on changes as discussed.
1809 #
1810 # gmTopPanel moves to gmHorstSpace
1811 # gmRichardSpace added -- example code at present, haven't even run it myself
1812 # (waiting on some icon .pngs from Richard)
1813 #
1814 # Revision 1.11 2005/01/31 10:37:26 ncq
1815 # - gmPatient.py -> gmPerson.py
1816 #
1817 # Revision 1.10 2004/10/20 12:40:55 ncq
1818 # - some cleanup
1819 #
1820 # Revision 1.9 2004/10/20 07:49:45 sjtan
1821 # small forward wxWidget compatibility change.
1822 #
1823 # Revision 1.7 2004/09/06 22:22:15 ncq
1824 # - properly use setDBParam()
1825 #
1826 # Revision 1.6 2004/09/02 00:40:13 ncq
1827 # - store option always_dismiss_previous_patient if not found
1828 #
1829 # Revision 1.5 2004/09/01 22:04:03 ncq
1830 # - cleanup
1831 # - code order change to avoid exception due to None-check after logging
1832 #
1833 # Revision 1.4 2004/08/29 23:15:58 ncq
1834 # - Richard improved the patient picklist popup
1835 # - plus cleanup/fixes etc
1836 #
1837 # Revision 1.3 2004/08/24 15:41:13 ncq
1838 # - eventually force patient pick list to stay on top
1839 # as suggested by Robin Dunn
1840 #
1841 # Revision 1.2 2004/08/20 13:31:05 ncq
1842 # - cleanup/improve comments/improve naming
1843 # - dismiss patient regardless of search result if so configured
1844 # - don't search on empty search term
1845 #
1846 # Revision 1.1 2004/08/20 06:46:38 ncq
1847 # - used to be gmPatientSelector.py
1848 #
1849 # Revision 1.45 2004/08/19 13:59:14 ncq
1850 # - streamline/cleanup
1851 # - Busy Cursor according to Richard
1852 #
1853 # Revision 1.44 2004/08/18 08:18:35 ncq
1854 # - later wxWidgets version don't support parent=NULL anymore
1855 #
1856 # Revision 1.43 2004/08/02 18:53:36 ncq
1857 # - used wx.Begin/EndBusyCursor() around setting the active patient
1858 #
1859 # Revision 1.42 2004/07/18 19:51:12 ncq
1860 # - cleanup, use True/False, not true/false
1861 # - use run_ro_query(), not run_query()
1862 #
1863 # Revision 1.41 2004/07/15 20:36:11 ncq
1864 # - better default size
1865 #
1866 # Revision 1.40 2004/06/20 16:01:05 ncq
1867 # - please epydoc more carefully
1868 #
1869 # Revision 1.39 2004/06/20 06:49:21 ihaywood
1870 # changes required due to Epydoc's OCD
1871 #
1872 # Revision 1.38 2004/06/04 16:27:12 shilbert
1873 # - giving focus highlights the text and lets you replace it
1874 #
1875 # Revision 1.37 2004/03/27 18:24:11 ncq
1876 # - Ian and I fixed the same bugs again :)
1877 #
1878 # Revision 1.36 2004/03/27 04:37:01 ihaywood
1879 # lnk_person2address now lnk_person_org_address
1880 # sundry bugfixes
1881 #
1882 # Revision 1.35 2004/03/25 11:03:23 ncq
1883 # - getActiveName -> get_names
1884 #
1885 # Revision 1.34 2004/03/20 19:48:07 ncq
1886 # - adapt to flat id list from get_patient_ids
1887 #
1888 # Revision 1.33 2004/03/12 13:23:41 ncq
1889 # - cleanup
1890 #
1891 # Revision 1.32 2004/03/05 11:22:35 ncq
1892 # - import from Gnumed.<pkg>
1893 #
1894 # Revision 1.31 2004/03/04 19:47:06 ncq
1895 # - switch to package based import: from Gnumed.foo import bar
1896 #
1897 # Revision 1.30 2004/02/25 09:46:22 ncq
1898 # - import from pycommon now, not python-common
1899 #
1900 # Revision 1.29 2004/02/05 18:41:31 ncq
1901 # - make _on_patient_selected() thread-safe
1902 # - move SetActivePatient() logic into gmPatient
1903 #
1904 # Revision 1.28 2004/02/04 00:55:02 ncq
1905 # - moved UI-independant patient searching code into business/gmPatient.py where it belongs
1906 #
1907 # Revision 1.27 2003/11/22 14:49:32 ncq
1908 # - fix typo
1909 #
1910 # Revision 1.26 2003/11/22 00:26:10 ihaywood
1911 # Set coding to latin-1 to please python 2.3
1912 #
1913 # Revision 1.25 2003/11/18 23:34:02 ncq
1914 # - don't use reload to force reload of same patient
1915 #
1916 # Revision 1.24 2003/11/17 10:56:38 sjtan
1917 #
1918 # synced and commiting.
1919 #
1920 # Revision 1.23 2003/11/09 17:29:22 shilbert
1921 # - ['demographics'] -> ['demographic record']
1922 #
1923 # Revision 1.22 2003/11/07 20:44:11 ncq
1924 # - some cleanup
1925 # - listen to patient_selected by other widgets
1926 #
1927 # Revision 1.21 2003/11/04 00:22:46 ncq
1928 # - remove unneeded import
1929 #
1930 # Revision 1.20 2003/10/26 17:42:51 ncq
1931 # - cleanup
1932 #
1933 # Revision 1.19 2003/10/26 11:27:10 ihaywood
1934 # gmPatient is now the "patient stub", all demographics stuff in gmDemographics.
1935 #
1936 # Ergregious breakages are fixed, but needs more work
1937 #
1938 # Revision 1.18 2003/10/26 01:36:13 ncq
1939 # - gmTmpPatient -> gmPatient
1940 #
1941 # Revision 1.17 2003/10/19 12:17:57 ncq
1942 # - typo fix
1943 #
1944 # Revision 1.16 2003/09/21 07:52:57 ihaywood
1945 # those bloody umlauts killed by python interpreter!
1946 #
1947 # Revision 1.15 2003/07/07 08:34:31 ihaywood
1948 # bugfixes on gmdrugs.sql for postgres 7.3
1949 #
1950 # Revision 1.14 2003/07/03 15:22:19 ncq
1951 # - removed unused stuff
1952 #
1953 # Revision 1.13 2003/06/29 14:08:02 ncq
1954 # - extra ; removed
1955 # - kvk/incoming/ as default KVK dir
1956 #
1957 # Revision 1.12 2003/04/09 16:20:19 ncq
1958 # - added set selection on get focus -- but we don't tab in yet !!
1959 # - can now set title on pick list
1960 # - added KVK handling :-)
1961 #
1962 # Revision 1.11 2003/04/04 23:54:30 ncq
1963 # - tweaked some parent and style settings here and there, but still
1964 # not where we want to be with the pick list ...
1965 #
1966 # Revision 1.10 2003/04/04 20:46:45 ncq
1967 # - adapt to new gmCurrentPatient()
1968 # - add (ugly) tooltip
1969 # - break out helper _display_name()
1970 # - fix KeyError on ids[0]
1971 #
1972 # Revision 1.9 2003/04/01 16:01:06 ncq
1973 # - fixed handling of no-patients-found result
1974 #
1975 # Revision 1.8 2003/04/01 15:33:22 ncq
1976 # - and double :: of course, duh
1977 #
1978 # Revision 1.7 2003/04/01 15:32:52 ncq
1979 # - stupid indentation error
1980 #
1981 # Revision 1.6 2003/04/01 12:28:14 ncq
1982 # - factored out _normalize_soundalikes()
1983 #
1984 # Revision 1.5 2003/04/01 09:08:27 ncq
1985 # - better Umlaut replacement
1986 # - safer cursor.close() handling
1987 #
1988 # Revision 1.4 2003/03/31 23:38:16 ncq
1989 # - sensitize() helper for smart names upcasing
1990 # - massively rework queries for speedup
1991 #
1992 # Revision 1.3 2003/03/30 00:24:00 ncq
1993 # - typos
1994 # - (hopefully) less confusing printk()s at startup
1995 #
1996 # Revision 1.2 2003/03/28 15:56:04 ncq
1997 # - adapted to GnuMed CVS structure
1998 #
1999
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:01:55 2010 | http://epydoc.sourceforge.net |