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 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
13 __license__ = 'GPL v2 or later (for details see http://www.gnu.org/)'
14
15 import sys, os.path, glob, re as regex, logging
16
17
18 import wx
19
20
21 if __name__ == '__main__':
22 sys.path.insert(0, '../../')
23 from Gnumed.pycommon import gmLog2
24 from Gnumed.pycommon import gmDispatcher
25 from Gnumed.pycommon import gmDateTime
26 from Gnumed.pycommon import gmTools
27 from Gnumed.pycommon import gmPG2
28 from Gnumed.pycommon import gmI18N
29 from Gnumed.pycommon import gmCfg
30 from Gnumed.pycommon import gmMatchProvider
31 from Gnumed.pycommon import gmCfg2
32 from Gnumed.pycommon import gmNetworkTools
33
34 from Gnumed.business import gmPerson
35 from Gnumed.business import gmStaff
36 from Gnumed.business import gmKVK
37 from Gnumed.business import gmPraxis
38 from Gnumed.business import gmCA_MSVA
39 from Gnumed.business import gmPersonSearch
40 from Gnumed.business import gmProviderInbox
41
42 from Gnumed.wxpython import gmGuiHelpers
43 from Gnumed.wxpython import gmAuthWidgets
44 from Gnumed.wxpython import gmRegetMixin
45 from Gnumed.wxpython import gmEditArea
46 from Gnumed.wxpython import gmPhraseWheel
47 from Gnumed.wxpython.gmPersonCreationWidgets import create_new_person
48
49
50 _log = logging.getLogger('gm.person')
51
52 _cfg = gmCfg2.gmCfgData()
53
54 ID_PatPickList = wx.NewId()
55 ID_BTN_AddNew = wx.NewId()
56
57
61
62 from Gnumed.wxGladeWidgets import wxgMergePatientsDlg
63
169
170 from Gnumed.wxGladeWidgets import wxgSelectPersonFromListDlg
171
173
188
190 for col in range(len(self.__cols)):
191 self._LCTRL_persons.InsertColumn(col, self.__cols[col])
192
194 self._LCTRL_persons.DeleteAllItems()
195
196 pos = len(persons) + 1
197 if pos == 1:
198 return False
199
200 for person in persons:
201 row_num = self._LCTRL_persons.InsertStringItem(pos, label = gmTools.coalesce(person['title'], ''))
202 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = person['lastnames'])
203 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = person['firstnames'])
204 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = gmTools.coalesce(person['preferred'], ''))
205 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = person.get_formatted_dob(format = '%Y %b %d', encoding = gmI18N.get_encoding()))
206 self._LCTRL_persons.SetStringItem(index = row_num, col = 5, label = gmTools.coalesce(person['l10n_gender'], '?'))
207 label = u''
208 if person.is_patient:
209 enc = person.get_last_encounter()
210 if enc is not None:
211 label = u'%s (%s)' % (gmDateTime.pydt_strftime(enc['started'], '%Y %b %d'), enc['l10n_type'])
212 self._LCTRL_persons.SetStringItem(index = row_num, col = 6, label = label)
213 try:
214 self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = person['match_type'])
215 except KeyError:
216 _log.warning('cannot set match_type field')
217 self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = u'??')
218
219 for col in range(len(self.__cols)):
220 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE)
221
222 self._BTN_select.Enable(False)
223 self._LCTRL_persons.SetFocus()
224 self._LCTRL_persons.Select(0)
225
226 self._LCTRL_persons.set_data(data=persons)
227
229 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
230
231
232
234 self._BTN_select.Enable(True)
235 return
236
238 self._BTN_select.Enable(True)
239 if self.IsModal():
240 self.EndModal(wx.ID_OK)
241 else:
242 self.Close()
243
244 from Gnumed.wxGladeWidgets import wxgSelectPersonDTOFromListDlg
245
247
259
261 for col in range(len(self.__cols)):
262 self._LCTRL_persons.InsertColumn(col, self.__cols[col])
263
265 self._LCTRL_persons.DeleteAllItems()
266
267 pos = len(dtos) + 1
268 if pos == 1:
269 return False
270
271 for rec in dtos:
272 row_num = self._LCTRL_persons.InsertStringItem(pos, label = rec['source'])
273 dto = rec['dto']
274 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = dto.lastnames)
275 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = dto.firstnames)
276 if dto.dob is None:
277 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = u'')
278 else:
279 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = gmDateTime.pydt_strftime(dto.dob, '%Y %b %d'))
280 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = gmTools.coalesce(dto.gender, ''))
281
282 for col in range(len(self.__cols)):
283 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE)
284
285 self._BTN_select.Enable(False)
286 self._LCTRL_persons.SetFocus()
287 self._LCTRL_persons.Select(0)
288
289 self._LCTRL_persons.set_data(data=dtos)
290
292 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
293
294
295
297 self._BTN_select.Enable(True)
298 return
299
301 self._BTN_select.Enable(True)
302 if self.IsModal():
303 self.EndModal(wx.ID_OK)
304 else:
305 self.Close()
306
307
309
310 group = u'CA Medical Manager MSVA'
311
312 src_order = [
313 ('explicit', 'append'),
314 ('workbase', 'append'),
315 ('local', 'append'),
316 ('user', 'append'),
317 ('system', 'append')
318 ]
319 msva_files = _cfg.get (
320 group = group,
321 option = 'filename',
322 source_order = src_order
323 )
324 if msva_files is None:
325 return []
326
327 dtos = []
328 for msva_file in msva_files:
329 try:
330
331 msva_dtos = gmCA_MSVA.read_persons_from_msva_file(filename = msva_file)
332 except StandardError:
333 gmGuiHelpers.gm_show_error (
334 _(
335 'Cannot load patient from Medical Manager MSVA file\n\n'
336 ' [%s]'
337 ) % msva_file,
338 _('Activating MSVA patient')
339 )
340 _log.exception('cannot read patient from MSVA file [%s]' % msva_file)
341 continue
342
343 dtos.extend([ {'dto': dto, 'source': dto.source} for dto in msva_dtos ])
344
345
346 return dtos
347
348
349
351
352 bdt_files = []
353
354
355
356 candidates = []
357 drives = 'cdefghijklmnopqrstuvwxyz'
358 for drive in drives:
359 candidate = drive + ':\Winacs\TEMP\BDT*.tmp'
360 candidates.extend(glob.glob(candidate))
361 for candidate in candidates:
362 path, filename = os.path.split(candidate)
363
364 bdt_files.append({'file': candidate, 'source': 'MCS/Isynet %s' % filename[-6:-4]})
365
366
367
368 src_order = [
369 ('explicit', 'return'),
370 ('workbase', 'append'),
371 ('local', 'append'),
372 ('user', 'append'),
373 ('system', 'append')
374 ]
375 xdt_profiles = _cfg.get (
376 group = 'workplace',
377 option = 'XDT profiles',
378 source_order = src_order
379 )
380 if xdt_profiles is None:
381 return []
382
383
384 src_order = [
385 ('explicit', 'return'),
386 ('workbase', 'return'),
387 ('local', 'return'),
388 ('user', 'return'),
389 ('system', 'return')
390 ]
391 for profile in xdt_profiles:
392 name = _cfg.get (
393 group = 'XDT profile %s' % profile,
394 option = 'filename',
395 source_order = src_order
396 )
397 if name is None:
398 _log.error('XDT profile [%s] does not define a <filename>' % profile)
399 continue
400 encoding = _cfg.get (
401 group = 'XDT profile %s' % profile,
402 option = 'encoding',
403 source_order = src_order
404 )
405 if encoding is None:
406 _log.warning('xDT source profile [%s] does not specify an <encoding> for BDT file [%s]' % (profile, name))
407 source = _cfg.get (
408 group = 'XDT profile %s' % profile,
409 option = 'source',
410 source_order = src_order
411 )
412 dob_format = _cfg.get (
413 group = 'XDT profile %s' % profile,
414 option = 'DOB format',
415 source_order = src_order
416 )
417 if dob_format is None:
418 _log.warning('XDT profile [%s] does not define a date of birth format in <DOB format>' % profile)
419 bdt_files.append({'file': name, 'source': source, 'encoding': encoding, 'dob_format': dob_format})
420
421 dtos = []
422 for bdt_file in bdt_files:
423 try:
424
425 dto = gmPerson.get_person_from_xdt (
426 filename = bdt_file['file'],
427 encoding = bdt_file['encoding'],
428 dob_format = bdt_file['dob_format']
429 )
430
431 except IOError:
432 gmGuiHelpers.gm_show_info (
433 _(
434 'Cannot access BDT file\n\n'
435 ' [%s]\n\n'
436 'to import patient.\n\n'
437 'Please check your configuration.'
438 ) % bdt_file,
439 _('Activating xDT patient')
440 )
441 _log.exception('cannot access xDT file [%s]' % bdt_file['file'])
442 continue
443 except:
444 gmGuiHelpers.gm_show_error (
445 _(
446 'Cannot load patient from BDT file\n\n'
447 ' [%s]'
448 ) % bdt_file,
449 _('Activating xDT patient')
450 )
451 _log.exception('cannot read patient from xDT file [%s]' % bdt_file['file'])
452 continue
453
454 dtos.append({'dto': dto, 'source': gmTools.coalesce(bdt_file['source'], dto.source)})
455
456 return dtos
457
458
459
461
462 pracsoft_files = []
463
464
465 candidates = []
466 drives = 'cdefghijklmnopqrstuvwxyz'
467 for drive in drives:
468 candidate = drive + ':\MDW2\PATIENTS.IN'
469 candidates.extend(glob.glob(candidate))
470 for candidate in candidates:
471 drive, filename = os.path.splitdrive(candidate)
472 pracsoft_files.append({'file': candidate, 'source': 'PracSoft (AU): drive %s' % drive})
473
474
475 src_order = [
476 ('explicit', 'append'),
477 ('workbase', 'append'),
478 ('local', 'append'),
479 ('user', 'append'),
480 ('system', 'append')
481 ]
482 fnames = _cfg.get (
483 group = 'AU PracSoft PATIENTS.IN',
484 option = 'filename',
485 source_order = src_order
486 )
487
488 src_order = [
489 ('explicit', 'return'),
490 ('user', 'return'),
491 ('system', 'return'),
492 ('local', 'return'),
493 ('workbase', 'return')
494 ]
495 source = _cfg.get (
496 group = 'AU PracSoft PATIENTS.IN',
497 option = 'source',
498 source_order = src_order
499 )
500
501 if source is not None:
502 for fname in fnames:
503 fname = os.path.abspath(os.path.expanduser(fname))
504 if os.access(fname, os.R_OK):
505 pracsoft_files.append({'file': os.path.expanduser(fname), 'source': source})
506 else:
507 _log.error('cannot read [%s] in AU PracSoft profile' % fname)
508
509
510 dtos = []
511 for pracsoft_file in pracsoft_files:
512 try:
513 tmp = gmPerson.get_persons_from_pracsoft_file(filename = pracsoft_file['file'])
514 except:
515 _log.exception('cannot parse PracSoft file [%s]' % pracsoft_file['file'])
516 continue
517 for dto in tmp:
518 dtos.append({'dto': dto, 'source': pracsoft_file['source']})
519
520 return dtos
521
536
538 """Load patient from external source.
539
540 - scan external sources for candidates
541 - let user select source
542 - if > 1 available: always
543 - if only 1 available: depending on search_immediately
544 - search for patients matching info from external source
545 - if more than one match:
546 - let user select patient
547 - if no match:
548 - create patient
549 - activate patient
550 """
551
552 dtos = []
553 dtos.extend(load_persons_from_xdt())
554 dtos.extend(load_persons_from_pracsoft_au())
555 dtos.extend(load_persons_from_kvks())
556 dtos.extend(load_persons_from_ca_msva())
557
558
559 if len(dtos) == 0:
560 gmDispatcher.send(signal='statustext', msg=_('No patients found in external sources.'))
561 return None
562
563
564 if (len(dtos) == 1) and (dtos[0]['dto'].dob is not None):
565 dto = dtos[0]['dto']
566
567 curr_pat = gmPerson.gmCurrentPatient()
568 if curr_pat.connected:
569 key_dto = dto.firstnames + dto.lastnames + dto.dob.strftime('%Y-%m-%d') + dto.gender
570 names = curr_pat.get_active_name()
571 key_pat = names['firstnames'] + names['lastnames'] + curr_pat.get_formatted_dob(format = '%Y-%m-%d') + curr_pat['gender']
572 _log.debug('current patient: %s' % key_pat)
573 _log.debug('dto patient : %s' % key_dto)
574 if key_dto == key_pat:
575 gmDispatcher.send(signal='statustext', msg=_('The only external patient is already active in GNUmed.'), beep=False)
576 return None
577
578
579 if (len(dtos) == 1) and search_immediately:
580 dto = dtos[0]['dto']
581
582
583 else:
584 if parent is None:
585 parent = wx.GetApp().GetTopWindow()
586 dlg = cSelectPersonDTOFromListDlg(parent=parent, id=-1)
587 dlg.set_dtos(dtos=dtos)
588 result = dlg.ShowModal()
589 if result == wx.ID_CANCEL:
590 return None
591 dto = dlg.get_selected_dto()['dto']
592 dlg.Destroy()
593
594
595 idents = dto.get_candidate_identities(can_create=True)
596 if idents is None:
597 gmGuiHelpers.gm_show_info (_(
598 'Cannot create new patient:\n\n'
599 ' [%s %s (%s), %s]'
600 ) % (
601 dto.firstnames, dto.lastnames, dto.gender, gmDateTime.pydt_strftime(dto.dob, '%Y %b %d')
602 ),
603 _('Activating external patient')
604 )
605 return None
606
607 if len(idents) == 1:
608 ident = idents[0]
609
610 if len(idents) > 1:
611 if parent is None:
612 parent = wx.GetApp().GetTopWindow()
613 dlg = cSelectPersonFromListDlg(parent=parent, id=-1)
614 dlg.set_persons(persons=idents)
615 result = dlg.ShowModal()
616 if result == wx.ID_CANCEL:
617 return None
618 ident = dlg.get_selected_person()
619 dlg.Destroy()
620
621 if activate_immediately:
622 if not set_active_patient(patient = ident):
623 gmGuiHelpers.gm_show_info (_(
624 'Cannot activate patient:\n\n'
625 '%s %s (%s)\n'
626 '%s'
627 ) % (
628 dto.firstnames, dto.lastnames, dto.gender, gmDateTime.pydt_strftime(dto.dob, '%Y %b %d')
629 ),
630 _('Activating external patient')
631 )
632 return None
633
634 dto.import_extra_data(identity = ident)
635 dto.delete_from_source()
636
637 return ident
638
640 """Widget for smart search for persons."""
641
643
644 try:
645 kwargs['style'] = kwargs['style'] | wx.TE_PROCESS_ENTER
646 except KeyError:
647 kwargs['style'] = wx.TE_PROCESS_ENTER
648
649
650
651 wx.TextCtrl.__init__(self, *args, **kwargs)
652
653 self.person = None
654
655 self._tt_search_hints = _(
656 'To search for a person, type any of: \n'
657 '\n'
658 ' - fragment(s) of last and/or first name(s)\n'
659 " - GNUmed ID of person (can start with '#')\n"
660 ' - any external ID of person\n'
661 " - date of birth (can start with '$' or '*')\n"
662 '\n'
663 'and hit <ENTER>.\n'
664 '\n'
665 'Shortcuts:\n'
666 ' <F2>\n'
667 ' - scan external sources for persons\n'
668 ' <CURSOR-UP>\n'
669 ' - recall most recently used search term\n'
670 ' <CURSOR-DOWN>\n'
671 ' - list 10 most recently found persons\n'
672 )
673 self.SetToolTipString(self._tt_search_hints)
674
675
676 self.__person_searcher = gmPersonSearch.cPatientSearcher_SQL()
677
678 self._prev_search_term = None
679 self.__prev_idents = []
680 self._lclick_count = 0
681
682 self.__register_events()
683
684
685
687 self.__person = person
688 wx.CallAfter(self._display_name)
689
692
693 person = property(_get_person, _set_person)
694
695
696
704
706
707 if not isinstance(ident, gmPerson.cIdentity):
708 return False
709
710
711 for known_ident in self.__prev_idents:
712 if known_ident['pk_identity'] == ident['pk_identity']:
713 return True
714
715 self.__prev_idents.append(ident)
716
717
718 if len(self.__prev_idents) > 10:
719 self.__prev_idents.pop(0)
720
721 return True
722
723
724
726 wx.EVT_CHAR(self, self.__on_char)
727 wx.EVT_SET_FOCUS(self, self._on_get_focus)
728 wx.EVT_KILL_FOCUS (self, self._on_loose_focus)
729 wx.EVT_TEXT_ENTER (self, self.GetId(), self.__on_enter)
730
732 """upon tabbing in
733
734 - select all text in the field so that the next
735 character typed will delete it
736 """
737 wx.CallAfter(self.SetSelection, -1, -1)
738 evt.Skip()
739
741
742
743
744
745
746
747
748 evt.Skip()
749 wx.CallAfter(self.__on_lost_focus)
750
752
753 self.SetSelection(0, 0)
754 self._display_name()
755 self._remember_ident(self.person)
756
759
761 """True: patient was selected.
762 False: no patient was selected.
763 """
764 keycode = evt.GetKeyCode()
765
766
767 if keycode == wx.WXK_DOWN:
768 evt.Skip()
769 if len(self.__prev_idents) == 0:
770 return False
771
772 dlg = cSelectPersonFromListDlg(parent = wx.GetTopLevelParent(self), id = -1)
773 dlg.set_persons(persons = self.__prev_idents)
774 result = dlg.ShowModal()
775 if result == wx.ID_OK:
776 wx.BeginBusyCursor()
777 self.person = dlg.get_selected_person()
778 dlg.Destroy()
779 wx.EndBusyCursor()
780 return True
781
782 dlg.Destroy()
783 return False
784
785
786 if keycode == wx.WXK_UP:
787 evt.Skip()
788
789 if self._prev_search_term is not None:
790 self.SetValue(self._prev_search_term)
791 return False
792
793
794 if keycode == wx.WXK_F2:
795 evt.Skip()
796 dbcfg = gmCfg.cCfgSQL()
797 search_immediately = bool(dbcfg.get2 (
798 option = 'patient_search.external_sources.immediately_search_if_single_source',
799 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
800 bias = 'user',
801 default = 0
802 ))
803 p = get_person_from_external_sources (
804 parent = wx.GetTopLevelParent(self),
805 search_immediately = search_immediately
806 )
807 if p is not None:
808 self.person = p
809 return True
810 return False
811
812
813
814
815 evt.Skip()
816
818 """This is called from the ENTER handler."""
819
820
821 curr_search_term = self.GetValue().strip()
822 if curr_search_term == '':
823 return None
824
825
826 if self.person is not None:
827 if curr_search_term == self.person['description']:
828 return None
829
830
831 if self.IsModified():
832 self._prev_search_term = curr_search_term
833
834 self._on_enter(search_term = curr_search_term)
835
837 """This can be overridden in child classes."""
838
839 wx.BeginBusyCursor()
840
841
842 idents = self.__person_searcher.get_identities(search_term)
843
844 if idents is None:
845 wx.EndBusyCursor()
846 gmGuiHelpers.gm_show_info (
847 _('Error searching for matching persons.\n\n'
848 'Search term: "%s"'
849 ) % search_term,
850 _('selecting person')
851 )
852 return None
853
854 _log.info("%s matching person(s) found", len(idents))
855
856 if len(idents) == 0:
857 wx.EndBusyCursor()
858
859 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
860 wx.GetTopLevelParent(self),
861 -1,
862 caption = _('Selecting patient'),
863 question = _(
864 'Cannot find any matching patients for the search term\n\n'
865 ' "%s"\n\n'
866 'You may want to try a shorter search term.\n'
867 ) % search_term,
868 button_defs = [
869 {'label': _('Go back'), 'tooltip': _('Go back and search again.'), 'default': True},
870 {'label': _('Create new'), 'tooltip': _('Create new patient.')}
871 ]
872 )
873 if dlg.ShowModal() != wx.ID_NO:
874 return
875
876 success = create_new_person(activate = True)
877 if success:
878 self.person = gmPerson.gmCurrentPatient()
879 else:
880 self.person = None
881 return None
882
883
884 if len(idents) == 1:
885 self.person = idents[0]
886 wx.EndBusyCursor()
887 return None
888
889
890 dlg = cSelectPersonFromListDlg(parent=wx.GetTopLevelParent(self), id=-1)
891 dlg.set_persons(persons=idents)
892 wx.EndBusyCursor()
893 result = dlg.ShowModal()
894 if result == wx.ID_CANCEL:
895 dlg.Destroy()
896 return None
897
898 wx.BeginBusyCursor()
899 self.person = dlg.get_selected_person()
900 dlg.Destroy()
901 wx.EndBusyCursor()
902
903 return None
904
906
907 if patient is None:
908 return
909
910 if patient['dob'] is None:
911 gmGuiHelpers.gm_show_warning (
912 aTitle = _('Checking date of birth'),
913 aMessage = _(
914 '\n'
915 ' %s\n'
916 '\n'
917 'The date of birth for this patient is not known !\n'
918 '\n'
919 'You can proceed to work on the patient but\n'
920 'GNUmed will be unable to assist you with\n'
921 'age-related decisions.\n'
922 ) % patient['description_gender']
923 )
924
925 return
926
928
929 if patient is None:
930 return True
931
932 curr_prov = gmStaff.gmCurrentProvider()
933
934
935 if patient.ID == curr_prov['pk_identity']:
936 return True
937
938 if patient.ID not in [ s['pk_identity'] for s in gmStaff.get_staff_list() ]:
939 return True
940
941 proceed = gmGuiHelpers.gm_show_question (
942 aTitle = _('Privacy check'),
943 aMessage = _(
944 'You have selected the chart of a member of staff,\n'
945 'for whom privacy is especially important:\n'
946 '\n'
947 ' %s, %s\n'
948 '\n'
949 'This may be OK depending on circumstances.\n'
950 '\n'
951 'Please be aware that accessing patient charts is\n'
952 'logged and that %s%s will be\n'
953 'notified of the access if you choose to proceed.\n'
954 '\n'
955 'Are you sure you want to draw this chart ?'
956 ) % (
957 patient.get_description_gender(),
958 patient.get_formatted_dob(),
959 gmTools.coalesce(patient['title'], u'', u'%s '),
960 patient['lastnames']
961 )
962 )
963
964 if proceed:
965 prov = u'%s (%s%s %s)' % (
966 curr_prov['short_alias'],
967 gmTools.coalesce(curr_prov['title'], u'', u'%s '),
968 curr_prov['firstnames'],
969 curr_prov['lastnames']
970 )
971 pat = u'%s%s %s' % (
972 gmTools.coalesce(patient['title'], u'', u'%s '),
973 patient['firstnames'],
974 patient['lastnames']
975 )
976
977 gmProviderInbox.create_inbox_message (
978 staff = patient.staff_id,
979 message_type = _('Privacy notice'),
980 message_category = u'administrative',
981 subject = _('Your chart has been accessed by %s.') % prov,
982 patient = patient.ID
983 )
984
985 gmProviderInbox.create_inbox_message (
986 staff = curr_prov['pk_staff'],
987 message_type = _('Privacy notice'),
988 message_category = u'administrative',
989 subject = _('Staff member %s has been notified of your chart access.') % pat
990 )
991
992 return proceed
993
995
996 if patient['dob'] is None:
997 return
998
999 dbcfg = gmCfg.cCfgSQL()
1000 dob_distance = dbcfg.get2 (
1001 option = u'patient_search.dob_warn_interval',
1002 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1003 bias = u'user',
1004 default = u'1 week'
1005 )
1006
1007 if not patient.dob_in_range(dob_distance, dob_distance):
1008 return
1009
1010 now = gmDateTime.pydt_now_here()
1011 enc = gmI18N.get_encoding()
1012 msg = _('%(pat)s turns %(age)s on %(month)s %(day)s ! (today is %(month_now)s %(day_now)s)') % {
1013 'pat': patient.get_description_gender(),
1014 'age': patient.get_medical_age().strip('y'),
1015 'month': patient.get_formatted_dob(format = '%B', encoding = enc),
1016 'day': patient.get_formatted_dob(format = '%d', encoding = enc),
1017 'month_now': gmDateTime.pydt_strftime(now, '%B', enc, gmDateTime.acc_months),
1018 'day_now': gmDateTime.pydt_strftime(now, '%d', enc, gmDateTime.acc_days)
1019 }
1020 gmDispatcher.send(signal = 'statustext', msg = msg)
1021
1023
1024 if isinstance(patient, gmPerson.cPatient):
1025 pass
1026 elif isinstance(patient, gmPerson.cIdentity):
1027 patient = gmPerson.cPatient(aPK_obj = patient['pk_identity'])
1028
1029
1030 elif isinstance(patient, gmPerson.gmCurrentPatient):
1031 patient = patient.patient
1032 elif patient == -1:
1033 pass
1034 else:
1035
1036 success, pk = gmTools.input2int(initial = patient, minval = 1)
1037 if not success:
1038 raise ValueError('<patient> must be either -1, >0, or a cPatient, cIdentity or gmCurrentPatient instance, is: %s' % patient)
1039
1040 try:
1041 patient = gmPerson.cPatient(aPK_obj = pk)
1042 except:
1043 _log.exception('error changing active patient to [%s]' % patient)
1044 return False
1045
1046 _check_has_dob(patient = patient)
1047
1048 if not _check_for_provider_chart_access(patient = patient):
1049 return False
1050
1051 success = gmPerson.set_active_patient(patient = patient, forced_reload = forced_reload)
1052
1053 if not success:
1054 return False
1055
1056 _check_birthday(patient = patient)
1057
1058 return True
1059
1061
1088
1089
1090
1092
1093 curr_pat = gmPerson.gmCurrentPatient()
1094 if curr_pat.connected:
1095 name = curr_pat['description']
1096 if curr_pat.locked:
1097 name = _('%(name)s (locked)') % {'name': name}
1098 else:
1099 if curr_pat.locked:
1100 name = _('<patient search locked>')
1101 else:
1102 name = _('<type here to search patient>')
1103
1104 self.SetValue(name)
1105
1106
1107 if self.person is None:
1108 self.SetToolTipString(self._tt_search_hints)
1109 return
1110
1111 if (self.person['emergency_contact'] is None) and (self.person['comment'] is None):
1112 separator = u''
1113 else:
1114 separator = u'%s\n' % (gmTools.u_box_horiz_single * 40)
1115
1116 tt = u'%s%s%s%s' % (
1117 gmTools.coalesce(self.person['emergency_contact'], u'', u'%s\n %%s\n' % _('In case of emergency contact:')),
1118 gmTools.coalesce(self.person['comment'], u'', u'\n%s\n'),
1119 separator,
1120 self._tt_search_hints
1121 )
1122 self.SetToolTipString(tt)
1123
1125 if not set_active_patient(patient=pat, forced_reload = self.__always_reload_after_search):
1126 _log.error('cannot change active patient')
1127 return None
1128
1129 self._remember_ident(pat)
1130
1131 return True
1132
1133
1134
1136
1137 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1138 gmDispatcher.connect(signal = u'dem.names_mod_db', receiver = self._on_name_identity_change)
1139 gmDispatcher.connect(signal = u'dem.identity_mod_db', receiver = self._on_name_identity_change)
1140
1141 gmDispatcher.connect(signal = 'patient_locked', receiver = self._on_post_patient_selection)
1142 gmDispatcher.connect(signal = 'patient_unlocked', receiver = self._on_post_patient_selection)
1143
1145 wx.CallAfter(self._display_name)
1146
1147 - def _on_post_patient_selection(self, **kwargs):
1152
1154
1155 if self.__always_dismiss_on_search:
1156 _log.warning("dismissing patient before patient search")
1157 self._set_person_as_active_patient(-1)
1158
1159 super(self.__class__, self)._on_enter(search_term=search_term)
1160
1161 if self.person is None:
1162 return
1163
1164 self._set_person_as_active_patient(self.person)
1165
1167
1168 success = super(self.__class__, self)._on_char(evt)
1169 if success:
1170 self._set_person_as_active_patient(self.person)
1171
1172
1173
1174
1175 if __name__ == "__main__":
1176
1177 if len(sys.argv) > 1:
1178 if sys.argv[1] == 'test':
1179 gmI18N.activate_locale()
1180 gmI18N.install_domain()
1181
1182 app = wx.PyWidgetTester(size = (200, 40))
1183
1184 app.SetWidget(cPersonSearchCtrl, -1)
1185
1186 app.MainLoop()
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291