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