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