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