| Home | 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 __version__ = "$Revision: 1.132 $"
13 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
14 __license__ = 'GPL v2 or later (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
28 from Gnumed.business import gmKVK
29 from Gnumed.business import gmSurgery
30 from Gnumed.business import gmCA_MSVA
31 from Gnumed.business import gmPersonSearch
32 from Gnumed.business import gmProviderInbox
33 from Gnumed.wxpython import gmGuiHelpers, gmDemographicsWidgets, gmAuthWidgets
34 from Gnumed.wxpython import gmRegetMixin, gmPhraseWheel, gmEditArea
35
36
37 _log = logging.getLogger('gm.person')
38 _log.info(__version__)
39
40 _cfg = gmCfg2.gmCfgData()
41
42 ID_PatPickList = wx.NewId()
43 ID_BTN_AddNew = wx.NewId()
44
45 #============================================================
49 #============================================================
50 from Gnumed.wxGladeWidgets import wxgMergePatientsDlg
51
53
55 wxgMergePatientsDlg.wxgMergePatientsDlg.__init__(self, *args, **kwargs)
56
57 curr_pat = gmPerson.gmCurrentPatient()
58 if curr_pat.connected:
59 self._TCTRL_patient1.person = curr_pat
60 self._TCTRL_patient1._display_name()
61 self._RBTN_patient1.SetValue(True)
62 #--------------------------------------------------------
161 #============================================================
162 from Gnumed.wxGladeWidgets import wxgSelectPersonFromListDlg
163
165
167 wxgSelectPersonFromListDlg.wxgSelectPersonFromListDlg.__init__(self, *args, **kwargs)
168
169 self.__cols = [
170 _('Title'),
171 _('Lastname'),
172 _('Firstname'),
173 _('Nickname'),
174 _('DOB'),
175 _('Gender'),
176 _('last visit'),
177 _('found via')
178 ]
179 self.__init_ui()
180 #--------------------------------------------------------
184 #--------------------------------------------------------
186 self._LCTRL_persons.DeleteAllItems()
187
188 pos = len(persons) + 1
189 if pos == 1:
190 return False
191
192 for person in persons:
193 row_num = self._LCTRL_persons.InsertStringItem(pos, label = gmTools.coalesce(person['title'], ''))
194 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = person['lastnames'])
195 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = person['firstnames'])
196 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = gmTools.coalesce(person['preferred'], ''))
197 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = person.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()))
198 self._LCTRL_persons.SetStringItem(index = row_num, col = 5, label = gmTools.coalesce(person['l10n_gender'], '?'))
199 label = u''
200 if person.is_patient:
201 enc = person.get_last_encounter()
202 if enc is not None:
203 label = u'%s (%s)' % (enc['started'].strftime('%x').decode(gmI18N.get_encoding()), enc['l10n_type'])
204 self._LCTRL_persons.SetStringItem(index = row_num, col = 6, label = label)
205 try: self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = person['match_type'])
206 except:
207 _log.exception('cannot set match_type field')
208 self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = u'??')
209
210 for col in range(len(self.__cols)):
211 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE)
212
213 self._BTN_select.Enable(False)
214 self._LCTRL_persons.SetFocus()
215 self._LCTRL_persons.Select(0)
216
217 self._LCTRL_persons.set_data(data=persons)
218 #--------------------------------------------------------
220 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
221 #--------------------------------------------------------
222 # event handlers
223 #--------------------------------------------------------
227 #--------------------------------------------------------
234 #============================================================
235 from Gnumed.wxGladeWidgets import wxgSelectPersonDTOFromListDlg
236
237 -class cSelectPersonDTOFromListDlg(wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg):
238
240 wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg.__init__(self, *args, **kwargs)
241
242 self.__cols = [
243 _('Source'),
244 _('Lastname'),
245 _('Firstname'),
246 _('DOB'),
247 _('Gender')
248 ]
249 self.__init_ui()
250 #--------------------------------------------------------
254 #--------------------------------------------------------
256 self._LCTRL_persons.DeleteAllItems()
257
258 pos = len(dtos) + 1
259 if pos == 1:
260 return False
261
262 for rec in dtos:
263 row_num = self._LCTRL_persons.InsertStringItem(pos, label = rec['source'])
264 dto = rec['dto']
265 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = dto.lastnames)
266 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = dto.firstnames)
267 if dto.dob is None:
268 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = u'')
269 else:
270 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = dto.dob.strftime('%x').decode(gmI18N.get_encoding()))
271 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = gmTools.coalesce(dto.gender, ''))
272
273 for col in range(len(self.__cols)):
274 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE)
275
276 self._BTN_select.Enable(False)
277 self._LCTRL_persons.SetFocus()
278 self._LCTRL_persons.Select(0)
279
280 self._LCTRL_persons.set_data(data=dtos)
281 #--------------------------------------------------------
283 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
284 #--------------------------------------------------------
285 # event handlers
286 #--------------------------------------------------------
290 #--------------------------------------------------------
297
298 #============================================================
300
301 group = u'CA Medical Manager MSVA'
302
303 src_order = [
304 ('explicit', 'append'),
305 ('workbase', 'append'),
306 ('local', 'append'),
307 ('user', 'append'),
308 ('system', 'append')
309 ]
310 msva_files = _cfg.get (
311 group = group,
312 option = 'filename',
313 source_order = src_order
314 )
315 if msva_files is None:
316 return []
317
318 dtos = []
319 for msva_file in msva_files:
320 try:
321 # FIXME: potentially return several persons per file
322 msva_dtos = gmCA_MSVA.read_persons_from_msva_file(filename = msva_file)
323 except StandardError:
324 gmGuiHelpers.gm_show_error (
325 _(
326 'Cannot load patient from Medical Manager MSVA file\n\n'
327 ' [%s]'
328 ) % msva_file,
329 _('Activating MSVA patient')
330 )
331 _log.exception('cannot read patient from MSVA file [%s]' % msva_file)
332 continue
333
334 dtos.extend([ {'dto': dto, 'source': dto.source} for dto in msva_dtos ])
335 #dtos.extend([ {'dto': dto} for dto in msva_dtos ])
336
337 return dtos
338
339 #============================================================
340
342
343 bdt_files = []
344
345 # some can be auto-detected
346 # MCS/Isynet: $DRIVE:\Winacs\TEMP\BDTxx.tmp where xx is the workplace
347 candidates = []
348 drives = 'cdefghijklmnopqrstuvwxyz'
349 for drive in drives:
350 candidate = drive + ':\Winacs\TEMP\BDT*.tmp'
351 candidates.extend(glob.glob(candidate))
352 for candidate in candidates:
353 path, filename = os.path.split(candidate)
354 # FIXME: add encoding !
355 bdt_files.append({'file': candidate, 'source': 'MCS/Isynet %s' % filename[-6:-4]})
356
357 # some need to be configured
358 # aggregate sources
359 src_order = [
360 ('explicit', 'return'),
361 ('workbase', 'append'),
362 ('local', 'append'),
363 ('user', 'append'),
364 ('system', 'append')
365 ]
366 xdt_profiles = _cfg.get (
367 group = 'workplace',
368 option = 'XDT profiles',
369 source_order = src_order
370 )
371 if xdt_profiles is None:
372 return []
373
374 # first come first serve
375 src_order = [
376 ('explicit', 'return'),
377 ('workbase', 'return'),
378 ('local', 'return'),
379 ('user', 'return'),
380 ('system', 'return')
381 ]
382 for profile in xdt_profiles:
383 name = _cfg.get (
384 group = 'XDT profile %s' % profile,
385 option = 'filename',
386 source_order = src_order
387 )
388 if name is None:
389 _log.error('XDT profile [%s] does not define a <filename>' % profile)
390 continue
391 encoding = _cfg.get (
392 group = 'XDT profile %s' % profile,
393 option = 'encoding',
394 source_order = src_order
395 )
396 if encoding is None:
397 _log.warning('xDT source profile [%s] does not specify an <encoding> for BDT file [%s]' % (profile, name))
398 source = _cfg.get (
399 group = 'XDT profile %s' % profile,
400 option = 'source',
401 source_order = src_order
402 )
403 dob_format = _cfg.get (
404 group = 'XDT profile %s' % profile,
405 option = 'DOB format',
406 source_order = src_order
407 )
408 if dob_format is None:
409 _log.warning('XDT profile [%s] does not define a date of birth format in <DOB format>' % profile)
410 bdt_files.append({'file': name, 'source': source, 'encoding': encoding, 'dob_format': dob_format})
411
412 dtos = []
413 for bdt_file in bdt_files:
414 try:
415 # FIXME: potentially return several persons per file
416 dto = gmPerson.get_person_from_xdt (
417 filename = bdt_file['file'],
418 encoding = bdt_file['encoding'],
419 dob_format = bdt_file['dob_format']
420 )
421
422 except IOError:
423 gmGuiHelpers.gm_show_info (
424 _(
425 'Cannot access BDT file\n\n'
426 ' [%s]\n\n'
427 'to import patient.\n\n'
428 'Please check your configuration.'
429 ) % bdt_file,
430 _('Activating xDT patient')
431 )
432 _log.exception('cannot access xDT file [%s]' % bdt_file['file'])
433 continue
434 except:
435 gmGuiHelpers.gm_show_error (
436 _(
437 'Cannot load patient from BDT file\n\n'
438 ' [%s]'
439 ) % bdt_file,
440 _('Activating xDT patient')
441 )
442 _log.exception('cannot read patient from xDT file [%s]' % bdt_file['file'])
443 continue
444
445 dtos.append({'dto': dto, 'source': gmTools.coalesce(bdt_file['source'], dto.source)})
446
447 return dtos
448
449 #============================================================
450
452
453 pracsoft_files = []
454
455 # try detecting PATIENTS.IN files
456 candidates = []
457 drives = 'cdefghijklmnopqrstuvwxyz'
458 for drive in drives:
459 candidate = drive + ':\MDW2\PATIENTS.IN'
460 candidates.extend(glob.glob(candidate))
461 for candidate in candidates:
462 drive, filename = os.path.splitdrive(candidate)
463 pracsoft_files.append({'file': candidate, 'source': 'PracSoft (AU): drive %s' % drive})
464
465 # add configured one(s)
466 src_order = [
467 ('explicit', 'append'),
468 ('workbase', 'append'),
469 ('local', 'append'),
470 ('user', 'append'),
471 ('system', 'append')
472 ]
473 fnames = _cfg.get (
474 group = 'AU PracSoft PATIENTS.IN',
475 option = 'filename',
476 source_order = src_order
477 )
478
479 src_order = [
480 ('explicit', 'return'),
481 ('user', 'return'),
482 ('system', 'return'),
483 ('local', 'return'),
484 ('workbase', 'return')
485 ]
486 source = _cfg.get (
487 group = 'AU PracSoft PATIENTS.IN',
488 option = 'source',
489 source_order = src_order
490 )
491
492 if source is not None:
493 for fname in fnames:
494 fname = os.path.abspath(os.path.expanduser(fname))
495 if os.access(fname, os.R_OK):
496 pracsoft_files.append({'file': os.path.expanduser(fname), 'source': source})
497 else:
498 _log.error('cannot read [%s] in AU PracSoft profile' % fname)
499
500 # and parse them
501 dtos = []
502 for pracsoft_file in pracsoft_files:
503 try:
504 tmp = gmPerson.get_persons_from_pracsoft_file(filename = pracsoft_file['file'])
505 except:
506 _log.exception('cannot parse PracSoft file [%s]' % pracsoft_file['file'])
507 continue
508 for dto in tmp:
509 dtos.append({'dto': dto, 'source': pracsoft_file['source']})
510
511 return dtos
512 #============================================================
514
515 dbcfg = gmCfg.cCfgSQL()
516 kvk_dir = os.path.abspath(os.path.expanduser(dbcfg.get2 (
517 option = 'DE.KVK.spool_dir',
518 workplace = gmSurgery.gmCurrentPractice().active_workplace,
519 bias = 'workplace',
520 default = u'/var/spool/kvkd/'
521 )))
522 dtos = []
523 for dto in gmKVK.get_available_kvks_as_dtos(spool_dir = kvk_dir):
524 dtos.append({'dto': dto, 'source': 'KVK'})
525
526 return dtos
527 #============================================================
528 -def get_person_from_external_sources(parent=None, search_immediately=False, activate_immediately=False):
529 """Load patient from external source.
530
531 - scan external sources for candidates
532 - let user select source
533 - if > 1 available: always
534 - if only 1 available: depending on search_immediately
535 - search for patients matching info from external source
536 - if more than one match:
537 - let user select patient
538 - if no match:
539 - create patient
540 - activate patient
541 """
542 # get DTOs from interfaces
543 dtos = []
544 dtos.extend(load_persons_from_xdt())
545 dtos.extend(load_persons_from_pracsoft_au())
546 dtos.extend(load_persons_from_kvks())
547 dtos.extend(load_persons_from_ca_msva())
548
549 # no external persons
550 if len(dtos) == 0:
551 gmDispatcher.send(signal='statustext', msg=_('No patients found in external sources.'))
552 return None
553
554 # one external patient with DOB - already active ?
555 if (len(dtos) == 1) and (dtos[0]['dto'].dob is not None):
556 dto = dtos[0]['dto']
557 # is it already the current patient ?
558 curr_pat = gmPerson.gmCurrentPatient()
559 if curr_pat.connected:
560 key_dto = dto.firstnames + dto.lastnames + dto.dob.strftime('%Y-%m-%d') + dto.gender
561 names = curr_pat.get_active_name()
562 key_pat = names['firstnames'] + names['lastnames'] + curr_pat.get_formatted_dob(format = '%Y-%m-%d') + curr_pat['gender']
563 _log.debug('current patient: %s' % key_pat)
564 _log.debug('dto patient : %s' % key_dto)
565 if key_dto == key_pat:
566 gmDispatcher.send(signal='statustext', msg=_('The only external patient is already active in GNUmed.'), beep=False)
567 return None
568
569 # one external person - look for internal match immediately ?
570 if (len(dtos) == 1) and search_immediately:
571 dto = dtos[0]['dto']
572
573 # several external persons
574 else:
575 if parent is None:
576 parent = wx.GetApp().GetTopWindow()
577 dlg = cSelectPersonDTOFromListDlg(parent=parent, id=-1)
578 dlg.set_dtos(dtos=dtos)
579 result = dlg.ShowModal()
580 if result == wx.ID_CANCEL:
581 return None
582 dto = dlg.get_selected_dto()['dto']
583 dlg.Destroy()
584
585 # search
586 idents = dto.get_candidate_identities(can_create=True)
587 if idents is None:
588 gmGuiHelpers.gm_show_info (_(
589 'Cannot create new patient:\n\n'
590 ' [%s %s (%s), %s]'
591 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())),
592 _('Activating external patient')
593 )
594 return None
595
596 if len(idents) == 1:
597 ident = idents[0]
598
599 if len(idents) > 1:
600 if parent is None:
601 parent = wx.GetApp().GetTopWindow()
602 dlg = cSelectPersonFromListDlg(parent=parent, id=-1)
603 dlg.set_persons(persons=idents)
604 result = dlg.ShowModal()
605 if result == wx.ID_CANCEL:
606 return None
607 ident = dlg.get_selected_person()
608 dlg.Destroy()
609
610 if activate_immediately:
611 if not set_active_patient(patient = ident):
612 gmGuiHelpers.gm_show_info (
613 _(
614 'Cannot activate patient:\n\n'
615 '%s %s (%s)\n'
616 '%s'
617 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())),
618 _('Activating external patient')
619 )
620 return None
621
622 dto.import_extra_data(identity = ident)
623 dto.delete_from_source()
624
625 return ident
626 #============================================================
628 """Widget for smart search for persons."""
629
631
632 try:
633 kwargs['style'] = kwargs['style'] | wx.TE_PROCESS_ENTER
634 except KeyError:
635 kwargs['style'] = wx.TE_PROCESS_ENTER
636
637 # need to explicitly process ENTER events to avoid
638 # them being handed over to the next control
639 wx.TextCtrl.__init__(self, *args, **kwargs)
640
641 self.person = None
642
643 self._tt_search_hints = _(
644 'To search for a person, type any of: \n'
645 '\n'
646 ' - fragment(s) of last and/or first name(s)\n'
647 " - GNUmed ID of person (can start with '#')\n"
648 ' - any external ID of person\n'
649 " - date of birth (can start with '$' or '*')\n"
650 '\n'
651 'and hit <ENTER>.\n'
652 '\n'
653 'Shortcuts:\n'
654 ' <F2>\n'
655 ' - scan external sources for persons\n'
656 ' <CURSOR-UP>\n'
657 ' - recall most recently used search term\n'
658 ' <CURSOR-DOWN>\n'
659 ' - list 10 most recently found persons\n'
660 )
661 self.SetToolTipString(self._tt_search_hints)
662
663 # FIXME: set query generator
664 self.__person_searcher = gmPersonSearch.cPatientSearcher_SQL()
665
666 self._prev_search_term = None
667 self.__prev_idents = []
668 self._lclick_count = 0
669
670 self.__register_events()
671 #--------------------------------------------------------
672 # properties
673 #--------------------------------------------------------
677
680
681 person = property(_get_person, _set_person)
682 #--------------------------------------------------------
683 # utility methods
684 #--------------------------------------------------------
686 name = u''
687
688 if self.person is not None:
689 name = self.person['description']
690
691 self.SetValue(name)
692 #--------------------------------------------------------
694
695 if not isinstance(ident, gmPerson.cIdentity):
696 return False
697
698 # only unique identities
699 for known_ident in self.__prev_idents:
700 if known_ident['pk_identity'] == ident['pk_identity']:
701 return True
702
703 self.__prev_idents.append(ident)
704
705 # and only 10 of them
706 if len(self.__prev_idents) > 10:
707 self.__prev_idents.pop(0)
708
709 return True
710 #--------------------------------------------------------
711 # event handling
712 #--------------------------------------------------------
714 wx.EVT_CHAR(self, self.__on_char)
715 wx.EVT_SET_FOCUS(self, self._on_get_focus)
716 wx.EVT_KILL_FOCUS (self, self._on_loose_focus)
717 wx.EVT_TEXT_ENTER (self, self.GetId(), self.__on_enter)
718 #--------------------------------------------------------
720 """upon tabbing in
721
722 - select all text in the field so that the next
723 character typed will delete it
724 """
725 wx.CallAfter(self.SetSelection, -1, -1)
726 evt.Skip()
727 #--------------------------------------------------------
729 # - redraw the currently active name upon losing focus
730
731 # if we use wx.EVT_KILL_FOCUS we will also receive this event
732 # when closing our application or loosing focus to another
733 # application which is NOT what we intend to achieve,
734 # however, this is the least ugly way of doing this due to
735 # certain vagaries of wxPython (see the Wiki)
736
737 # just for good measure
738 wx.CallAfter(self.SetSelection, 0, 0)
739
740 self._display_name()
741 self._remember_ident(self.person)
742
743 evt.Skip()
744 #--------------------------------------------------------
747
749 """True: patient was selected.
750 False: no patient was selected.
751 """
752 keycode = evt.GetKeyCode()
753
754 # list of previously active patients
755 if keycode == wx.WXK_DOWN:
756 evt.Skip()
757 if len(self.__prev_idents) == 0:
758 return False
759
760 dlg = cSelectPersonFromListDlg(parent = wx.GetTopLevelParent(self), id = -1)
761 dlg.set_persons(persons = self.__prev_idents)
762 result = dlg.ShowModal()
763 if result == wx.ID_OK:
764 wx.BeginBusyCursor()
765 self.person = dlg.get_selected_person()
766 dlg.Destroy()
767 wx.EndBusyCursor()
768 return True
769
770 dlg.Destroy()
771 return False
772
773 # recall previous search fragment
774 if keycode == wx.WXK_UP:
775 evt.Skip()
776 # FIXME: cycling through previous fragments
777 if self._prev_search_term is not None:
778 self.SetValue(self._prev_search_term)
779 return False
780
781 # invoke external patient sources
782 if keycode == wx.WXK_F2:
783 evt.Skip()
784 dbcfg = gmCfg.cCfgSQL()
785 search_immediately = bool(dbcfg.get2 (
786 option = 'patient_search.external_sources.immediately_search_if_single_source',
787 workplace = gmSurgery.gmCurrentPractice().active_workplace,
788 bias = 'user',
789 default = 0
790 ))
791 p = get_person_from_external_sources (
792 parent = wx.GetTopLevelParent(self),
793 search_immediately = search_immediately
794 )
795 if p is not None:
796 self.person = p
797 return True
798 return False
799
800 # FIXME: invoke add new person
801 # FIXME: add popup menu apart from system one
802
803 evt.Skip()
804 #--------------------------------------------------------
806 """This is called from the ENTER handler."""
807
808 # ENTER but no search term ?
809 curr_search_term = self.GetValue().strip()
810 if curr_search_term == '':
811 return None
812
813 # same person anywys ?
814 if self.person is not None:
815 if curr_search_term == self.person['description']:
816 return None
817
818 # remember search fragment
819 if self.IsModified():
820 self._prev_search_term = curr_search_term
821
822 self._on_enter(search_term = curr_search_term)
823 #--------------------------------------------------------
825 """This can be overridden in child classes."""
826
827 wx.BeginBusyCursor()
828
829 # get list of matching ids
830 idents = self.__person_searcher.get_identities(search_term)
831
832 if idents is None:
833 wx.EndBusyCursor()
834 gmGuiHelpers.gm_show_info (
835 _('Error searching for matching persons.\n\n'
836 'Search term: "%s"'
837 ) % search_term,
838 _('selecting person')
839 )
840 return None
841
842 _log.info("%s matching person(s) found", len(idents))
843
844 if len(idents) == 0:
845 wx.EndBusyCursor()
846
847 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
848 wx.GetTopLevelParent(self),
849 -1,
850 caption = _('Selecting patient'),
851 question = _(
852 'Cannot find any matching patients for the search term\n\n'
853 ' "%s"\n\n'
854 'You may want to try a shorter search term.\n'
855 ) % search_term,
856 button_defs = [
857 {'label': _('Go back'), 'tooltip': _('Go back and search again.'), 'default': True},
858 {'label': _('Create new'), 'tooltip': _('Create new patient.')}
859 ]
860 )
861 if dlg.ShowModal() != wx.ID_NO:
862 return
863
864 success = gmDemographicsWidgets.create_new_person(activate = True)
865 if success:
866 self.person = gmPerson.gmCurrentPatient()
867 else:
868 self.person = None
869 return None
870
871 # only one matching identity
872 if len(idents) == 1:
873 self.person = idents[0]
874 wx.EndBusyCursor()
875 return None
876
877 # more than one matching identity: let user select from pick list
878 dlg = cSelectPersonFromListDlg(parent=wx.GetTopLevelParent(self), id=-1)
879 dlg.set_persons(persons=idents)
880 wx.EndBusyCursor()
881 result = dlg.ShowModal()
882 if result == wx.ID_CANCEL:
883 dlg.Destroy()
884 return None
885
886 wx.BeginBusyCursor()
887 self.person = dlg.get_selected_person()
888 dlg.Destroy()
889 wx.EndBusyCursor()
890
891 return None
892 #============================================================
894
895 if patient is None:
896 return
897
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 return
914 #------------------------------------------------------------
916
917 if patient is None:
918 return True
919
920 curr_prov = gmPerson.gmCurrentProvider()
921
922 # can view my own chart
923 if patient.ID == curr_prov['pk_identity']:
924 return True
925
926 if patient.ID not in [ s['pk_identity'] for s in gmPerson.get_staff_list() ]:
927 return True
928
929 proceed = gmGuiHelpers.gm_show_question (
930 aTitle = _('Privacy check'),
931 aMessage = _(
932 'You have selected the chart of a member of staff,\n'
933 'for whom privacy is especially important:\n'
934 '\n'
935 ' %s (%s)\n'
936 '\n'
937 'This may be OK depending on circumstances.\n'
938 '\n'
939 'Please be aware that accessing patient charts is\n'
940 'logged and that %s%s will be\n'
941 'notified of the access if you choose to proceed.\n'
942 '\n'
943 'Are you sure you want to draw this chart ?'
944 ) % (
945 patient.get_description_gender(),
946 patient.get_formatted_dob(),
947 gmTools.coalesce(patient['title'], u'', u'%s '),
948 patient['lastnames']
949 )
950 )
951
952 if proceed:
953 prov = u'%s (%s%s %s)' % (
954 curr_prov['short_alias'],
955 gmTools.coalesce(curr_prov['title'], u'', u'%s '),
956 curr_prov['firstnames'],
957 curr_prov['lastnames']
958 )
959 pat = u'%s%s %s' % (
960 gmTools.coalesce(patient['title'], u'', u'%s '),
961 patient['firstnames'],
962 patient['lastnames']
963 )
964 # notify the staff member
965 gmProviderInbox.create_inbox_message (
966 staff = patient.staff_id,
967 message_type = _('Privacy notice'),
968 subject = _('Your chart has been accessed by %s.') % prov,
969 patient = patient.ID
970 )
971 # notify /me about the staff member notification
972 gmProviderInbox.create_inbox_message (
973 staff = curr_prov['pk_staff'],
974 message_type = _('Privacy notice'),
975 subject = _('Staff member %s has been notified of your chart access.') % pat
976 #, patient = patient.ID
977 )
978
979 return proceed
980 #------------------------------------------------------------
982
983 _check_dob(patient = patient)
984
985 if not _check_for_provider_chart_access(patient = patient):
986 return False
987
988 success = gmPerson.set_active_patient(patient = patient, forced_reload = forced_reload)
989
990 if not success:
991 return False
992
993 if patient['dob'] is None:
994 return True
995
996 dbcfg = gmCfg.cCfgSQL()
997 dob_distance = dbcfg.get2 (
998 option = u'patient_search.dob_warn_interval',
999 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1000 bias = u'user',
1001 default = u'1 week'
1002 )
1003
1004 if patient.dob_in_range(dob_distance, dob_distance):
1005 now = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone)
1006 enc = gmI18N.get_encoding()
1007 gmDispatcher.send(signal = 'statustext', msg = _(
1008 '%(pat)s turns %(age)s on %(month)s %(day)s ! (today is %(month_now)s %(day_now)s)') % {
1009 'pat': patient.get_description_gender(),
1010 'age': patient.get_medical_age().strip('y'),
1011 'month': patient.get_formatted_dob(format = '%B', encoding = enc),
1012 'day': patient.get_formatted_dob(format = '%d', encoding = enc),
1013 'month_now': now.strftime('%B').decode(enc),
1014 'day_now': now.strftime('%d')
1015 }
1016 )
1017
1018 return True
1019 #------------------------------------------------------------
1021
1023
1024 cPersonSearchCtrl.__init__(self, *args, **kwargs)
1025
1026 # get configuration
1027 cfg = gmCfg.cCfgSQL()
1028
1029 self.__always_dismiss_on_search = bool (
1030 cfg.get2 (
1031 option = 'patient_search.always_dismiss_previous_patient',
1032 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1033 bias = 'user',
1034 default = 0
1035 )
1036 )
1037
1038 self.__always_reload_after_search = bool (
1039 cfg.get2 (
1040 option = 'patient_search.always_reload_new_patient',
1041 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1042 bias = 'user',
1043 default = 0
1044 )
1045 )
1046
1047 self.__register_events()
1048 #--------------------------------------------------------
1049 # utility methods
1050 #--------------------------------------------------------
1052
1053 curr_pat = gmPerson.gmCurrentPatient()
1054 if curr_pat.connected:
1055 name = curr_pat['description']
1056 if curr_pat.locked:
1057 name = _('%(name)s (locked)') % {'name': name}
1058 else:
1059 if curr_pat.locked:
1060 name = _('<patient search locked>')
1061 else:
1062 name = _('<type here to search patient>')
1063
1064 self.SetValue(name)
1065
1066 # adjust tooltip
1067 if self.person is None:
1068 self.SetToolTipString(self._tt_search_hints)
1069 return
1070
1071 if (self.person['emergency_contact'] is None) and (self.person['comment'] is None):
1072 separator = u''
1073 else:
1074 separator = u'%s\n' % (gmTools.u_box_horiz_single * 40)
1075
1076 tt = u'%s%s%s%s' % (
1077 gmTools.coalesce(self.person['emergency_contact'], u'', u'%s\n %%s\n' % _('In case of emergency contact:')),
1078 gmTools.coalesce(self.person['comment'], u'', u'\n%s\n'),
1079 separator,
1080 self._tt_search_hints
1081 )
1082 self.SetToolTipString(tt)
1083 #--------------------------------------------------------
1085 if not set_active_patient(patient=pat, forced_reload = self.__always_reload_after_search):
1086 _log.error('cannot change active patient')
1087 return None
1088
1089 self._remember_ident(pat)
1090
1091 return True
1092 #--------------------------------------------------------
1093 # event handling
1094 #--------------------------------------------------------
1096 # client internal signals
1097 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1098 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_name_identity_change)
1099 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_name_identity_change)
1100
1101 gmDispatcher.connect(signal = 'patient_locked', receiver = self._on_post_patient_selection)
1102 gmDispatcher.connect(signal = 'patient_unlocked', receiver = self._on_post_patient_selection)
1103 #----------------------------------------------
1106 #----------------------------------------------
1108 if gmPerson.gmCurrentPatient().connected:
1109 self.person = gmPerson.gmCurrentPatient().patient
1110 else:
1111 self.person = None
1112 #----------------------------------------------
1114
1115 if self.__always_dismiss_on_search:
1116 _log.warning("dismissing patient before patient search")
1117 self._set_person_as_active_patient(-1)
1118
1119 super(self.__class__, self)._on_enter(search_term=search_term)
1120
1121 if self.person is None:
1122 return
1123
1124 self._set_person_as_active_patient(self.person)
1125 #----------------------------------------------
1127
1128 success = super(self.__class__, self)._on_char(evt)
1129 if success:
1130 self._set_person_as_active_patient(self.person)
1131
1132 #============================================================
1133 # main
1134 #------------------------------------------------------------
1135 if __name__ == "__main__":
1136
1137 if len(sys.argv) > 1:
1138 if sys.argv[1] == 'test':
1139 gmI18N.activate_locale()
1140 gmI18N.install_domain()
1141
1142 app = wx.PyWidgetTester(size = (200, 40))
1143 # app.SetWidget(cSelectPersonFromListDlg, -1)
1144 app.SetWidget(cPersonSearchCtrl, -1)
1145 # app.SetWidget(cActivePatientSelector, -1)
1146 app.MainLoop()
1147
1148 #============================================================
1149 # docs
1150 #------------------------------------------------------------
1151 # functionality
1152 # -------------
1153 # - hitting ENTER on non-empty field (and more than threshold chars)
1154 # - start search
1155 # - display results in a list, prefixed with numbers
1156 # - last name
1157 # - first name
1158 # - gender
1159 # - age
1160 # - city + street (no ZIP, no number)
1161 # - last visit (highlighted if within a certain interval)
1162 # - arbitrary marker (e.g. office attendance this quartal, missing KVK, appointments, due dates)
1163 # - if none found -> go to entry of new patient
1164 # - scrolling in this list
1165 # - ENTER selects patient
1166 # - ESC cancels selection
1167 # - number selects patient
1168 #
1169 # - hitting cursor-up/-down
1170 # - cycle through history of last 10 search fragments
1171 #
1172 # - hitting alt-L = List, alt-P = previous
1173 # - show list of previous ten patients prefixed with numbers
1174 # - scrolling in list
1175 # - ENTER selects patient
1176 # - ESC cancels selection
1177 # - number selects patient
1178 #
1179 # - hitting ALT-N
1180 # - immediately goes to entry of new patient
1181 #
1182 # - hitting cursor-right in a patient selection list
1183 # - pops up more detail about the patient
1184 # - ESC/cursor-left goes back to list
1185 #
1186 # - hitting TAB
1187 # - makes sure the currently active patient is displayed
1188
1189 #------------------------------------------------------------
1190 # samples
1191 # -------
1192 # working:
1193 # Ian Haywood
1194 # Haywood Ian
1195 # Haywood
1196 # Amador Jimenez (yes, two last names but no hyphen: Spain, for example)
1197 # Ian Haywood 19/12/1977
1198 # 19/12/1977
1199 # 19-12-1977
1200 # 19.12.1977
1201 # 19771219
1202 # $dob
1203 # *dob
1204 # #ID
1205 # ID
1206 # HIlbert, karsten
1207 # karsten, hilbert
1208 # kars, hilb
1209 #
1210 # non-working:
1211 # Haywood, Ian <40
1212 # ?, Ian 1977
1213 # Ian Haywood, 19/12/77
1214 # PUPIC
1215 # "hilb; karsten, 23.10.74"
1216
1217 #------------------------------------------------------------
1218 # notes
1219 # -----
1220 # >> 3. There are countries in which people have more than one
1221 # >> (significant) lastname (spanish-speaking countries are one case :), some
1222 # >> asian countries might be another one).
1223 # -> we need per-country query generators ...
1224
1225 # search case sensitive by default, switch to insensitive if not found ?
1226
1227 # accent insensitive search:
1228 # select * from * where to_ascii(column, 'encoding') like '%test%';
1229 # may not work with Unicode
1230
1231 # phrase wheel is most likely too slow
1232
1233 # extend search fragment history
1234
1235 # ask user whether to send off level 3 queries - or thread them
1236
1237 # we don't expect patient IDs in complicated patterns, hence any digits signify a date
1238
1239 # FIXME: make list window fit list size ...
1240
1241 # clear search field upon get-focus ?
1242
1243 # F1 -> context help with hotkey listing
1244
1245 # th -> th|t
1246 # v/f/ph -> f|v|ph
1247 # maybe don't do umlaut translation in the first 2-3 letters
1248 # such that not to defeat index use for the first level query ?
1249
1250 # user defined function key to start search
1251
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Oct 18 04:00:16 2011 | http://epydoc.sourceforge.net |