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