| 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 __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
52
54 wxgMergePatientsDlg.wxgMergePatientsDlg.__init__(self, *args, **kwargs)
55
56 curr_pat = gmPerson.gmCurrentPatient()
57 if curr_pat.connected:
58 self._TCTRL_patient1.person = curr_pat
59 self._TCTRL_patient1._display_name()
60 self._RBTN_patient1.SetValue(True)
61 #--------------------------------------------------------
156 #============================================================
157 from Gnumed.wxGladeWidgets import wxgSelectPersonFromListDlg
158
160
162 wxgSelectPersonFromListDlg.wxgSelectPersonFromListDlg.__init__(self, *args, **kwargs)
163
164 self.__cols = [
165 _('Title'),
166 _('Lastname'),
167 _('Firstname'),
168 _('Nickname'),
169 _('DOB'),
170 _('Gender'),
171 _('last visit'),
172 _('found via')
173 ]
174 self.__init_ui()
175 #--------------------------------------------------------
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 # event handlers
218 #--------------------------------------------------------
222 #--------------------------------------------------------
229 #============================================================
230 from Gnumed.wxGladeWidgets import wxgSelectPersonDTOFromListDlg
231
232 -class cSelectPersonDTOFromListDlg(wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg):
233
235 wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg.__init__(self, *args, **kwargs)
236
237 self.__cols = [
238 _('Source'),
239 _('Lastname'),
240 _('Firstname'),
241 _('DOB'),
242 _('Gender')
243 ]
244 self.__init_ui()
245 #--------------------------------------------------------
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 # event handlers
281 #--------------------------------------------------------
285 #--------------------------------------------------------
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 # FIXME: potentially return several persons per file
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 #dtos.extend([ {'dto': dto} for dto in msva_dtos ])
331
332 return dtos
333
334 #============================================================
335
337
338 bdt_files = []
339
340 # some can be auto-detected
341 # MCS/Isynet: $DRIVE:\Winacs\TEMP\BDTxx.tmp where xx is the workplace
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 # FIXME: add encoding !
350 bdt_files.append({'file': candidate, 'source': 'MCS/Isynet %s' % filename[-6:-4]})
351
352 # some need to be configured
353 # aggregate sources
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 # first come first serve
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 # FIXME: potentially return several persons per file
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 # try detecting PATIENTS.IN files
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 # add configured one(s)
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 # and parse them
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 #============================================================
509
510 dbcfg = gmCfg.cCfgSQL()
511 kvk_dir = os.path.abspath(os.path.expanduser(dbcfg.get2 (
512 option = 'DE.KVK.spool_dir',
513 workplace = gmSurgery.gmCurrentPractice().active_workplace,
514 bias = 'workplace',
515 default = u'/var/spool/kvkd/'
516 )))
517 dtos = []
518 for dto in gmKVK.get_available_kvks_as_dtos(spool_dir = kvk_dir):
519 dtos.append({'dto': dto, 'source': 'KVK'})
520
521 return dtos
522 #============================================================
523 -def get_person_from_external_sources(parent=None, search_immediately=False, activate_immediately=False):
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 # get DTOs from interfaces
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 # no external persons
545 if len(dtos) == 0:
546 gmDispatcher.send(signal='statustext', msg=_('No patients found in external sources.'))
547 return None
548
549 # one external patient with DOB - already active ?
550 if (len(dtos) == 1) and (dtos[0]['dto'].dob is not None):
551 dto = dtos[0]['dto']
552 # is it already the current patient ?
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 # one external person - look for internal match immediately ?
565 if (len(dtos) == 1) and search_immediately:
566 dto = dtos[0]['dto']
567
568 # several external persons
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 # search
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 # need to explicitly process ENTER events to avoid
633 # them being handed over to the next control
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 # FIXME: set query generator
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 # properties
668 #--------------------------------------------------------
672
675
676 person = property(_get_person, _set_person)
677 #--------------------------------------------------------
678 # utility methods
679 #--------------------------------------------------------
681 name = u''
682
683 if self.person is not None:
684 name = self.person['description']
685
686 self.SetValue(name)
687 #--------------------------------------------------------
689
690 if not isinstance(ident, gmPerson.cIdentity):
691 return False
692
693 # only unique identities
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 # and only 10 of them
701 if len(self.__prev_idents) > 10:
702 self.__prev_idents.pop(0)
703
704 return True
705 #--------------------------------------------------------
706 # event handling
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 # - redraw the currently active name upon losing focus
725
726 # if we use wx.EVT_KILL_FOCUS we will also receive this event
727 # when closing our application or loosing focus to another
728 # application which is NOT what we intend to achieve,
729 # however, this is the least ugly way of doing this due to
730 # certain vagaries of wxPython (see the Wiki)
731
732 # just for good measure
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 # list of previously active patients
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 # recall previous search fragment
769 if keycode == wx.WXK_UP:
770 evt.Skip()
771 # FIXME: cycling through previous fragments
772 if self._prev_search_term is not None:
773 self.SetValue(self._prev_search_term)
774 return False
775
776 # invoke external patient sources
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 # FIXME: invoke add new person
796 # FIXME: add popup menu apart from system one
797
798 evt.Skip()
799 #--------------------------------------------------------
801 """This is called from the ENTER handler."""
802
803 # ENTER but no search term ?
804 curr_search_term = self.GetValue().strip()
805 if curr_search_term == '':
806 return None
807
808 # same person anywys ?
809 if self.person is not None:
810 if curr_search_term == self.person['description']:
811 return None
812
813 # remember search fragment
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 # get list of matching ids
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 # only one matching identity
867 if len(idents) == 1:
868 self.person = idents[0]
869 wx.EndBusyCursor()
870 return None
871
872 # more than one matching identity: let user select from pick list
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 # can view my own chart
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 # notify the staff member
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 # notify /me about the staff member notification
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 #, patient = patient.ID
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
1018
1019 cPersonSearchCtrl.__init__(self, *args, **kwargs)
1020
1021 # get configuration
1022 cfg = gmCfg.cCfgSQL()
1023
1024 self.__always_dismiss_on_search = bool (
1025 cfg.get2 (
1026 option = 'patient_search.always_dismiss_previous_patient',
1027 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1028 bias = 'user',
1029 default = 0
1030 )
1031 )
1032
1033 self.__always_reload_after_search = bool (
1034 cfg.get2 (
1035 option = 'patient_search.always_reload_new_patient',
1036 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1037 bias = 'user',
1038 default = 0
1039 )
1040 )
1041
1042 self.__register_events()
1043 #--------------------------------------------------------
1044 # utility methods
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 # adjust tooltip
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 # event handling
1089 #--------------------------------------------------------
1091 # client internal signals
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 #----------------------------------------------
1101 #----------------------------------------------
1103 if gmPerson.gmCurrentPatient().connected:
1104 self.person = gmPerson.gmCurrentPatient().patient
1105 else:
1106 self.person = None
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 # main
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 # app.SetWidget(cSelectPersonFromListDlg, -1)
1139 app.SetWidget(cPersonSearchCtrl, -1)
1140 # app.SetWidget(cActivePatientSelector, -1)
1141 app.MainLoop()
1142
1143 #============================================================
1144 # docs
1145 #------------------------------------------------------------
1146 # functionality
1147 # -------------
1148 # - hitting ENTER on non-empty field (and more than threshold chars)
1149 # - start search
1150 # - display results in a list, prefixed with numbers
1151 # - last name
1152 # - first name
1153 # - gender
1154 # - age
1155 # - city + street (no ZIP, no number)
1156 # - last visit (highlighted if within a certain interval)
1157 # - arbitrary marker (e.g. office attendance this quartal, missing KVK, appointments, due dates)
1158 # - if none found -> go to entry of new patient
1159 # - scrolling in this list
1160 # - ENTER selects patient
1161 # - ESC cancels selection
1162 # - number selects patient
1163 #
1164 # - hitting cursor-up/-down
1165 # - cycle through history of last 10 search fragments
1166 #
1167 # - hitting alt-L = List, alt-P = previous
1168 # - show list of previous ten patients prefixed with numbers
1169 # - scrolling in list
1170 # - ENTER selects patient
1171 # - ESC cancels selection
1172 # - number selects patient
1173 #
1174 # - hitting ALT-N
1175 # - immediately goes to entry of new patient
1176 #
1177 # - hitting cursor-right in a patient selection list
1178 # - pops up more detail about the patient
1179 # - ESC/cursor-left goes back to list
1180 #
1181 # - hitting TAB
1182 # - makes sure the currently active patient is displayed
1183
1184 #------------------------------------------------------------
1185 # samples
1186 # -------
1187 # working:
1188 # Ian Haywood
1189 # Haywood Ian
1190 # Haywood
1191 # Amador Jimenez (yes, two last names but no hyphen: Spain, for example)
1192 # Ian Haywood 19/12/1977
1193 # 19/12/1977
1194 # 19-12-1977
1195 # 19.12.1977
1196 # 19771219
1197 # $dob
1198 # *dob
1199 # #ID
1200 # ID
1201 # HIlbert, karsten
1202 # karsten, hilbert
1203 # kars, hilb
1204 #
1205 # non-working:
1206 # Haywood, Ian <40
1207 # ?, Ian 1977
1208 # Ian Haywood, 19/12/77
1209 # PUPIC
1210 # "hilb; karsten, 23.10.74"
1211
1212 #------------------------------------------------------------
1213 # notes
1214 # -----
1215 # >> 3. There are countries in which people have more than one
1216 # >> (significant) lastname (spanish-speaking countries are one case :), some
1217 # >> asian countries might be another one).
1218 # -> we need per-country query generators ...
1219
1220 # search case sensitive by default, switch to insensitive if not found ?
1221
1222 # accent insensitive search:
1223 # select * from * where to_ascii(column, 'encoding') like '%test%';
1224 # may not work with Unicode
1225
1226 # phrase wheel is most likely too slow
1227
1228 # extend search fragment history
1229
1230 # ask user whether to send off level 3 queries - or thread them
1231
1232 # we don't expect patient IDs in complicated patterns, hence any digits signify a date
1233
1234 # FIXME: make list window fit list size ...
1235
1236 # clear search field upon get-focus ?
1237
1238 # F1 -> context help with hotkey listing
1239
1240 # th -> th|t
1241 # v/f/ph -> f|v|ph
1242 # maybe don't do umlaut translation in the first 2-3 letters
1243 # such that not to defeat index use for the first level query ?
1244
1245 # user defined function key to start search
1246
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Dec 5 04:00:26 2011 | http://epydoc.sourceforge.net |