| Home | Trees | Indices | Help |
|
|---|
|
|
1 # coding: latin-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
53
55 wxgMergePatientsDlg.wxgMergePatientsDlg.__init__(self, *args, **kwargs)
56
57 curr_pat = gmPerson.gmCurrentPatient()
58 if curr_pat.connected:
59 self._TCTRL_patient1.person = curr_pat
60 self._TCTRL_patient1._display_name()
61 self._RBTN_patient1.SetValue(True)
62 #--------------------------------------------------------
161 #============================================================
162 from Gnumed.wxGladeWidgets import wxgSelectPersonFromListDlg
163
165
167 wxgSelectPersonFromListDlg.wxgSelectPersonFromListDlg.__init__(self, *args, **kwargs)
168
169 self.__cols = [
170 _('Title'),
171 _('Lastname'),
172 _('Firstname'),
173 _('Nickname'),
174 _('DOB'),
175 _('Gender'),
176 _('last visit'),
177 _('found via')
178 ]
179 self.__init_ui()
180 #--------------------------------------------------------
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 # event handlers
223 #--------------------------------------------------------
227 #--------------------------------------------------------
234 #============================================================
235 from Gnumed.wxGladeWidgets import wxgSelectPersonDTOFromListDlg
236
237 -class cSelectPersonDTOFromListDlg(wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg):
238
240 wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg.__init__(self, *args, **kwargs)
241
242 self.__cols = [
243 _('Source'),
244 _('Lastname'),
245 _('Firstname'),
246 _('DOB'),
247 _('Gender')
248 ]
249 self.__init_ui()
250 #--------------------------------------------------------
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 # event handlers
286 #--------------------------------------------------------
290 #--------------------------------------------------------
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 # FIXME: potentially return several persons per file
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 #dtos.extend([ {'dto': dto} for dto in msva_dtos ])
336
337 return dtos
338
339 #============================================================
340
342
343 bdt_files = []
344
345 # some can be auto-detected
346 # MCS/Isynet: $DRIVE:\Winacs\TEMP\BDTxx.tmp where xx is the workplace
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 # FIXME: add encoding !
355 bdt_files.append({'file': candidate, 'source': 'MCS/Isynet %s' % filename[-6:-4]})
356
357 # some need to be configured
358 # aggregate sources
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 # first come first serve
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 # FIXME: potentially return several persons per file
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 # try detecting PATIENTS.IN files
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 # add configured one(s)
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 # and parse them
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 #============================================================
514
515 dbcfg = gmCfg.cCfgSQL()
516 kvk_dir = os.path.abspath(os.path.expanduser(dbcfg.get2 (
517 option = 'DE.KVK.spool_dir',
518 workplace = gmSurgery.gmCurrentPractice().active_workplace,
519 bias = 'workplace',
520 default = u'/var/spool/kvkd/'
521 )))
522 dtos = []
523 for dto in gmKVK.get_available_kvks_as_dtos(spool_dir = kvk_dir):
524 dtos.append({'dto': dto, 'source': 'KVK'})
525
526 return dtos
527 #============================================================
528 -def get_person_from_external_sources(parent=None, search_immediately=False, activate_immediately=False):
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 # get DTOs from interfaces
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 # no external persons
550 if len(dtos) == 0:
551 gmDispatcher.send(signal='statustext', msg=_('No patients found in external sources.'))
552 return None
553
554 # one external patient with DOB - already active ?
555 if (len(dtos) == 1) and (dtos[0]['dto'].dob is not None):
556 dto = dtos[0]['dto']
557 # is it already the current patient ?
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 # one external person - look for internal match immediately ?
570 if (len(dtos) == 1) and search_immediately:
571 dto = dtos[0]['dto']
572
573 # several external persons
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 # search
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 # need to explicitly process ENTER events to avoid
638 # them being handed over to the next control
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 # FIXME: set query generator
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 # properties
673 #--------------------------------------------------------
677
680
681 person = property(_get_person, _set_person)
682 #--------------------------------------------------------
683 # utility methods
684 #--------------------------------------------------------
686 name = u''
687
688 if self.person is not None:
689 name = self.person['description']
690
691 self.SetValue(name)
692 #--------------------------------------------------------
694
695 if not isinstance(ident, gmPerson.cIdentity):
696 return False
697
698 # only unique identities
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 # and only 10 of them
706 if len(self.__prev_idents) > 10:
707 self.__prev_idents.pop(0)
708
709 return True
710 #--------------------------------------------------------
711 # event handling
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 # - redraw the currently active name upon losing focus
730
731 # if we use wx.EVT_KILL_FOCUS we will also receive this event
732 # when closing our application or loosing focus to another
733 # application which is NOT what we intend to achieve,
734 # however, this is the least ugly way of doing this due to
735 # certain vagaries of wxPython (see the Wiki)
736
737 # just for good measure
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 # list of previously active patients
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 # recall previous search fragment
774 if keycode == wx.WXK_UP:
775 evt.Skip()
776 # FIXME: cycling through previous fragments
777 if self._prev_search_term is not None:
778 self.SetValue(self._prev_search_term)
779 return False
780
781 # invoke external patient sources
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 # FIXME: invoke add new person
801 # FIXME: add popup menu apart from system one
802
803 evt.Skip()
804 #--------------------------------------------------------
806 """This is called from the ENTER handler."""
807
808 # ENTER but no search term ?
809 curr_search_term = self.GetValue().strip()
810 if curr_search_term == '':
811 return None
812
813 # same person anywys ?
814 if self.person is not None:
815 if curr_search_term == self.person['description']:
816 return None
817
818 # remember search fragment
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 # get list of matching ids
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 # only one matching identity
872 if len(idents) == 1:
873 self.person = idents[0]
874 wx.EndBusyCursor()
875 return None
876
877 # more than one matching identity: let user select from pick list
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 # can view my own chart
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
1021
1022 cPersonSearchCtrl.__init__(self, *args, **kwargs)
1023
1024 # get configuration
1025 cfg = gmCfg.cCfgSQL()
1026
1027 self.__always_dismiss_on_search = bool (
1028 cfg.get2 (
1029 option = 'patient_search.always_dismiss_previous_patient',
1030 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1031 bias = 'user',
1032 default = 0
1033 )
1034 )
1035
1036 self.__always_reload_after_search = bool (
1037 cfg.get2 (
1038 option = 'patient_search.always_reload_new_patient',
1039 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1040 bias = 'user',
1041 default = 0
1042 )
1043 )
1044
1045 self.__register_events()
1046 #--------------------------------------------------------
1047 # utility methods
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 # event handling
1081 #--------------------------------------------------------
1083 # client internal signals
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 #----------------------------------------------
1093 #----------------------------------------------
1095 if gmPerson.gmCurrentPatient().connected:
1096 self.person = gmPerson.gmCurrentPatient().patient
1097 else:
1098 self.person = None
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 # main
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 # app.SetWidget(cSelectPersonFromListDlg, -1)
1131 app.SetWidget(cPersonSearchCtrl, -1)
1132 # app.SetWidget(cActivePatientSelector, -1)
1133 app.MainLoop()
1134
1135 #============================================================
1136 # docs
1137 #------------------------------------------------------------
1138 # functionality
1139 # -------------
1140 # - hitting ENTER on non-empty field (and more than threshold chars)
1141 # - start search
1142 # - display results in a list, prefixed with numbers
1143 # - last name
1144 # - first name
1145 # - gender
1146 # - age
1147 # - city + street (no ZIP, no number)
1148 # - last visit (highlighted if within a certain interval)
1149 # - arbitrary marker (e.g. office attendance this quartal, missing KVK, appointments, due dates)
1150 # - if none found -> go to entry of new patient
1151 # - scrolling in this list
1152 # - ENTER selects patient
1153 # - ESC cancels selection
1154 # - number selects patient
1155 #
1156 # - hitting cursor-up/-down
1157 # - cycle through history of last 10 search fragments
1158 #
1159 # - hitting alt-L = List, alt-P = previous
1160 # - show list of previous ten patients prefixed with numbers
1161 # - scrolling in list
1162 # - ENTER selects patient
1163 # - ESC cancels selection
1164 # - number selects patient
1165 #
1166 # - hitting ALT-N
1167 # - immediately goes to entry of new patient
1168 #
1169 # - hitting cursor-right in a patient selection list
1170 # - pops up more detail about the patient
1171 # - ESC/cursor-left goes back to list
1172 #
1173 # - hitting TAB
1174 # - makes sure the currently active patient is displayed
1175
1176 #------------------------------------------------------------
1177 # samples
1178 # -------
1179 # working:
1180 # Ian Haywood
1181 # Haywood Ian
1182 # Haywood
1183 # Amador Jimenez (yes, two last names but no hyphen: Spain, for example)
1184 # Ian Haywood 19/12/1977
1185 # 19/12/1977
1186 # 19-12-1977
1187 # 19.12.1977
1188 # 19771219
1189 # $dob
1190 # *dob
1191 # #ID
1192 # ID
1193 # HIlbert, karsten
1194 # karsten, hilbert
1195 # kars, hilb
1196 #
1197 # non-working:
1198 # Haywood, Ian <40
1199 # ?, Ian 1977
1200 # Ian Haywood, 19/12/77
1201 # PUPIC
1202 # "hilb; karsten, 23.10.74"
1203
1204 #------------------------------------------------------------
1205 # notes
1206 # -----
1207 # >> 3. There are countries in which people have more than one
1208 # >> (significant) lastname (spanish-speaking countries are one case :), some
1209 # >> asian countries might be another one).
1210 # -> we need per-country query generators ...
1211
1212 # search case sensitive by default, switch to insensitive if not found ?
1213
1214 # accent insensitive search:
1215 # select * from * where to_ascii(column, 'encoding') like '%test%';
1216 # may not work with Unicode
1217
1218 # phrase wheel is most likely too slow
1219
1220 # extend search fragment history
1221
1222 # ask user whether to send off level 3 queries - or thread them
1223
1224 # we don't expect patient IDs in complicated patterns, hence any digits signify a date
1225
1226 # FIXME: make list window fit list size ...
1227
1228 # clear search field upon get-focus ?
1229
1230 # F1 -> context help with hotkey listing
1231
1232 # th -> th|t
1233 # v/f/ph -> f|v|ph
1234 # maybe don't do umlaut translation in the first 2-3 letters
1235 # such that not to defeat index use for the first level query ?
1236
1237 # user defined function key to start search
1238
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu Jul 28 03:57:24 2011 | http://epydoc.sourceforge.net |