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 (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, gmKVK, gmSurgery, gmCA_MSVA, gmPersonSearch
28 from Gnumed.wxpython import gmGuiHelpers, gmDemographicsWidgets, gmAuthWidgets
29 from Gnumed.wxpython import gmRegetMixin, gmPhraseWheel, gmEditArea
30
31
32 _log = logging.getLogger('gm.person')
33 _log.info(__version__)
34
35 _cfg = gmCfg2.gmCfgData()
36
37 ID_PatPickList = wx.NewId()
38 ID_BTN_AddNew = wx.NewId()
39
40
44
45 from Gnumed.wxGladeWidgets import wxgMergePatientsDlg
46
156
157 from Gnumed.wxGladeWidgets import wxgSelectPersonFromListDlg
158
160
175
177 for col in range(len(self.__cols)):
178 self._LCTRL_persons.InsertColumn(col, self.__cols[col])
179
181 self._LCTRL_persons.DeleteAllItems()
182
183 pos = len(persons) + 1
184 if pos == 1:
185 return False
186
187 for person in persons:
188 row_num = self._LCTRL_persons.InsertStringItem(pos, label = gmTools.coalesce(person['title'], ''))
189 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = person['lastnames'])
190 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = person['firstnames'])
191 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = gmTools.coalesce(person['preferred'], ''))
192 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = person.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()))
193 self._LCTRL_persons.SetStringItem(index = row_num, col = 5, label = gmTools.coalesce(person['l10n_gender'], '?'))
194 label = u''
195 if person.is_patient:
196 enc = person.get_last_encounter()
197 if enc is not None:
198 label = u'%s (%s)' % (enc['started'].strftime('%x').decode(gmI18N.get_encoding()), enc['l10n_type'])
199 self._LCTRL_persons.SetStringItem(index = row_num, col = 6, label = label)
200 try: self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = person['match_type'])
201 except:
202 _log.exception('cannot set match_type field')
203 self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = u'??')
204
205 for col in range(len(self.__cols)):
206 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE)
207
208 self._BTN_select.Enable(False)
209 self._LCTRL_persons.SetFocus()
210 self._LCTRL_persons.Select(0)
211
212 self._LCTRL_persons.set_data(data=persons)
213
215 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
216
217
218
220 self._BTN_select.Enable(True)
221 return
222
224 self._BTN_select.Enable(True)
225 if self.IsModal():
226 self.EndModal(wx.ID_OK)
227 else:
228 self.Close()
229
230 from Gnumed.wxGladeWidgets import wxgSelectPersonDTOFromListDlg
231
233
245
247 for col in range(len(self.__cols)):
248 self._LCTRL_persons.InsertColumn(col, self.__cols[col])
249
251 self._LCTRL_persons.DeleteAllItems()
252
253 pos = len(dtos) + 1
254 if pos == 1:
255 return False
256
257 for rec in dtos:
258 row_num = self._LCTRL_persons.InsertStringItem(pos, label = rec['source'])
259 dto = rec['dto']
260 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = dto.lastnames)
261 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = dto.firstnames)
262 if dto.dob is None:
263 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = u'')
264 else:
265 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = dto.dob.strftime('%x').decode(gmI18N.get_encoding()))
266 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = gmTools.coalesce(dto.gender, ''))
267
268 for col in range(len(self.__cols)):
269 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE)
270
271 self._BTN_select.Enable(False)
272 self._LCTRL_persons.SetFocus()
273 self._LCTRL_persons.Select(0)
274
275 self._LCTRL_persons.set_data(data=dtos)
276
278 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
279
280
281
283 self._BTN_select.Enable(True)
284 return
285
287 self._BTN_select.Enable(True)
288 if self.IsModal():
289 self.EndModal(wx.ID_OK)
290 else:
291 self.Close()
292
293
295
296 group = u'CA Medical Manager MSVA'
297
298 src_order = [
299 ('explicit', 'append'),
300 ('workbase', 'append'),
301 ('local', 'append'),
302 ('user', 'append'),
303 ('system', 'append')
304 ]
305 msva_files = _cfg.get (
306 group = group,
307 option = 'filename',
308 source_order = src_order
309 )
310 if msva_files is None:
311 return []
312
313 dtos = []
314 for msva_file in msva_files:
315 try:
316
317 msva_dtos = gmCA_MSVA.read_persons_from_msva_file(filename = msva_file)
318 except StandardError:
319 gmGuiHelpers.gm_show_error (
320 _(
321 'Cannot load patient from Medical Manager MSVA file\n\n'
322 ' [%s]'
323 ) % msva_file,
324 _('Activating MSVA patient')
325 )
326 _log.exception('cannot read patient from MSVA file [%s]' % msva_file)
327 continue
328
329 dtos.extend([ {'dto': dto, 'source': dto.source} for dto in msva_dtos ])
330
331
332 return dtos
333
334
335
337
338 bdt_files = []
339
340
341
342 candidates = []
343 drives = 'cdefghijklmnopqrstuvwxyz'
344 for drive in drives:
345 candidate = drive + ':\Winacs\TEMP\BDT*.tmp'
346 candidates.extend(glob.glob(candidate))
347 for candidate in candidates:
348 path, filename = os.path.split(candidate)
349
350 bdt_files.append({'file': candidate, 'source': 'MCS/Isynet %s' % filename[-6:-4]})
351
352
353
354 src_order = [
355 ('explicit', 'return'),
356 ('workbase', 'append'),
357 ('local', 'append'),
358 ('user', 'append'),
359 ('system', 'append')
360 ]
361 xdt_profiles = _cfg.get (
362 group = 'workplace',
363 option = 'XDT profiles',
364 source_order = src_order
365 )
366 if xdt_profiles is None:
367 return []
368
369
370 src_order = [
371 ('explicit', 'return'),
372 ('workbase', 'return'),
373 ('local', 'return'),
374 ('user', 'return'),
375 ('system', 'return')
376 ]
377 for profile in xdt_profiles:
378 name = _cfg.get (
379 group = 'XDT profile %s' % profile,
380 option = 'filename',
381 source_order = src_order
382 )
383 if name is None:
384 _log.error('XDT profile [%s] does not define a <filename>' % profile)
385 continue
386 encoding = _cfg.get (
387 group = 'XDT profile %s' % profile,
388 option = 'encoding',
389 source_order = src_order
390 )
391 if encoding is None:
392 _log.warning('xDT source profile [%s] does not specify an <encoding> for BDT file [%s]' % (profile, name))
393 source = _cfg.get (
394 group = 'XDT profile %s' % profile,
395 option = 'source',
396 source_order = src_order
397 )
398 dob_format = _cfg.get (
399 group = 'XDT profile %s' % profile,
400 option = 'DOB format',
401 source_order = src_order
402 )
403 if dob_format is None:
404 _log.warning('XDT profile [%s] does not define a date of birth format in <DOB format>' % profile)
405 bdt_files.append({'file': name, 'source': source, 'encoding': encoding, 'dob_format': dob_format})
406
407 dtos = []
408 for bdt_file in bdt_files:
409 try:
410
411 dto = gmPerson.get_person_from_xdt (
412 filename = bdt_file['file'],
413 encoding = bdt_file['encoding'],
414 dob_format = bdt_file['dob_format']
415 )
416
417 except IOError:
418 gmGuiHelpers.gm_show_info (
419 _(
420 'Cannot access BDT file\n\n'
421 ' [%s]\n\n'
422 'to import patient.\n\n'
423 'Please check your configuration.'
424 ) % bdt_file,
425 _('Activating xDT patient')
426 )
427 _log.exception('cannot access xDT file [%s]' % bdt_file['file'])
428 continue
429 except:
430 gmGuiHelpers.gm_show_error (
431 _(
432 'Cannot load patient from BDT file\n\n'
433 ' [%s]'
434 ) % bdt_file,
435 _('Activating xDT patient')
436 )
437 _log.exception('cannot read patient from xDT file [%s]' % bdt_file['file'])
438 continue
439
440 dtos.append({'dto': dto, 'source': gmTools.coalesce(bdt_file['source'], dto.source)})
441
442 return dtos
443
444
445
447
448 pracsoft_files = []
449
450
451 candidates = []
452 drives = 'cdefghijklmnopqrstuvwxyz'
453 for drive in drives:
454 candidate = drive + ':\MDW2\PATIENTS.IN'
455 candidates.extend(glob.glob(candidate))
456 for candidate in candidates:
457 drive, filename = os.path.splitdrive(candidate)
458 pracsoft_files.append({'file': candidate, 'source': 'PracSoft (AU): drive %s' % drive})
459
460
461 src_order = [
462 ('explicit', 'append'),
463 ('workbase', 'append'),
464 ('local', 'append'),
465 ('user', 'append'),
466 ('system', 'append')
467 ]
468 fnames = _cfg.get (
469 group = 'AU PracSoft PATIENTS.IN',
470 option = 'filename',
471 source_order = src_order
472 )
473
474 src_order = [
475 ('explicit', 'return'),
476 ('user', 'return'),
477 ('system', 'return'),
478 ('local', 'return'),
479 ('workbase', 'return')
480 ]
481 source = _cfg.get (
482 group = 'AU PracSoft PATIENTS.IN',
483 option = 'source',
484 source_order = src_order
485 )
486
487 if source is not None:
488 for fname in fnames:
489 fname = os.path.abspath(os.path.expanduser(fname))
490 if os.access(fname, os.R_OK):
491 pracsoft_files.append({'file': os.path.expanduser(fname), 'source': source})
492 else:
493 _log.error('cannot read [%s] in AU PracSoft profile' % fname)
494
495
496 dtos = []
497 for pracsoft_file in pracsoft_files:
498 try:
499 tmp = gmPerson.get_persons_from_pracsoft_file(filename = pracsoft_file['file'])
500 except:
501 _log.exception('cannot parse PracSoft file [%s]' % pracsoft_file['file'])
502 continue
503 for dto in tmp:
504 dtos.append({'dto': dto, 'source': pracsoft_file['source']})
505
506 return dtos
507
522
524 """Load patient from external source.
525
526 - scan external sources for candidates
527 - let user select source
528 - if > 1 available: always
529 - if only 1 available: depending on search_immediately
530 - search for patients matching info from external source
531 - if more than one match:
532 - let user select patient
533 - if no match:
534 - create patient
535 - activate patient
536 """
537
538 dtos = []
539 dtos.extend(load_persons_from_xdt())
540 dtos.extend(load_persons_from_pracsoft_au())
541 dtos.extend(load_persons_from_kvks())
542 dtos.extend(load_persons_from_ca_msva())
543
544
545 if len(dtos) == 0:
546 gmDispatcher.send(signal='statustext', msg=_('No patients found in external sources.'))
547 return None
548
549
550 if (len(dtos) == 1) and (dtos[0]['dto'].dob is not None):
551 dto = dtos[0]['dto']
552
553 curr_pat = gmPerson.gmCurrentPatient()
554 if curr_pat.connected:
555 key_dto = dto.firstnames + dto.lastnames + dto.dob.strftime('%Y-%m-%d') + dto.gender
556 names = curr_pat.get_active_name()
557 key_pat = names['firstnames'] + names['lastnames'] + curr_pat.get_formatted_dob(format = '%Y-%m-%d') + curr_pat['gender']
558 _log.debug('current patient: %s' % key_pat)
559 _log.debug('dto patient : %s' % key_dto)
560 if key_dto == key_pat:
561 gmDispatcher.send(signal='statustext', msg=_('The only external patient is already active in GNUmed.'), beep=False)
562 return None
563
564
565 if (len(dtos) == 1) and search_immediately:
566 dto = dtos[0]['dto']
567
568
569 else:
570 if parent is None:
571 parent = wx.GetApp().GetTopWindow()
572 dlg = cSelectPersonDTOFromListDlg(parent=parent, id=-1)
573 dlg.set_dtos(dtos=dtos)
574 result = dlg.ShowModal()
575 if result == wx.ID_CANCEL:
576 return None
577 dto = dlg.get_selected_dto()['dto']
578 dlg.Destroy()
579
580
581 idents = dto.get_candidate_identities(can_create=True)
582 if idents is None:
583 gmGuiHelpers.gm_show_info (_(
584 'Cannot create new patient:\n\n'
585 ' [%s %s (%s), %s]'
586 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())),
587 _('Activating external patient')
588 )
589 return None
590
591 if len(idents) == 1:
592 ident = idents[0]
593
594 if len(idents) > 1:
595 if parent is None:
596 parent = wx.GetApp().GetTopWindow()
597 dlg = cSelectPersonFromListDlg(parent=parent, id=-1)
598 dlg.set_persons(persons=idents)
599 result = dlg.ShowModal()
600 if result == wx.ID_CANCEL:
601 return None
602 ident = dlg.get_selected_person()
603 dlg.Destroy()
604
605 if activate_immediately:
606 if not set_active_patient(patient = ident):
607 gmGuiHelpers.gm_show_info (
608 _(
609 'Cannot activate patient:\n\n'
610 '%s %s (%s)\n'
611 '%s'
612 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())),
613 _('Activating external patient')
614 )
615 return None
616
617 dto.import_extra_data(identity = ident)
618 dto.delete_from_source()
619
620 return ident
621
623 """Widget for smart search for persons."""
624
626
627 try:
628 kwargs['style'] = kwargs['style'] | wx.TE_PROCESS_ENTER
629 except KeyError:
630 kwargs['style'] = wx.TE_PROCESS_ENTER
631
632
633
634 wx.TextCtrl.__init__(self, *args, **kwargs)
635
636 self.person = None
637
638 self._tt_search_hints = _(
639 'To search for a person type any of: \n'
640 '\n'
641 ' - fragment of last or first name\n'
642 " - date of birth (can start with '$' or '*')\n"
643 " - GNUmed ID of person (can start with '#')\n"
644 ' - exterenal ID of person\n'
645 '\n'
646 'and hit <ENTER>.\n'
647 '\n'
648 'Shortcuts:\n'
649 ' <F2>\n'
650 ' - scan external sources for persons\n'
651 ' <CURSOR-UP>\n'
652 ' - recall most recently used search term\n'
653 ' <CURSOR-DOWN>\n'
654 ' - list 10 most recently found persons\n'
655 )
656 self.SetToolTipString(self._tt_search_hints)
657
658
659 self.__person_searcher = gmPersonSearch.cPatientSearcher_SQL()
660
661 self._prev_search_term = None
662 self.__prev_idents = []
663 self._lclick_count = 0
664
665 self.__register_events()
666
667
668
670 self.__person = person
671 wx.CallAfter(self._display_name)
672
675
676 person = property(_get_person, _set_person)
677
678
679
687
689
690 if not isinstance(ident, gmPerson.cIdentity):
691 return False
692
693
694 for known_ident in self.__prev_idents:
695 if known_ident['pk_identity'] == ident['pk_identity']:
696 return True
697
698 self.__prev_idents.append(ident)
699
700
701 if len(self.__prev_idents) > 10:
702 self.__prev_idents.pop(0)
703
704 return True
705
706
707
709 wx.EVT_CHAR(self, self.__on_char)
710 wx.EVT_SET_FOCUS(self, self._on_get_focus)
711 wx.EVT_KILL_FOCUS (self, self._on_loose_focus)
712 wx.EVT_TEXT_ENTER (self, self.GetId(), self.__on_enter)
713
715 """upon tabbing in
716
717 - select all text in the field so that the next
718 character typed will delete it
719 """
720 wx.CallAfter(self.SetSelection, -1, -1)
721 evt.Skip()
722
724
725
726
727
728
729
730
731
732
733 wx.CallAfter(self.SetSelection, 0, 0)
734
735 self._display_name()
736 self._remember_ident(self.person)
737
738 evt.Skip()
739
742
744 """True: patient was selected.
745 False: no patient was selected.
746 """
747 keycode = evt.GetKeyCode()
748
749
750 if keycode == wx.WXK_DOWN:
751 evt.Skip()
752 if len(self.__prev_idents) == 0:
753 return False
754
755 dlg = cSelectPersonFromListDlg(parent = wx.GetTopLevelParent(self), id = -1)
756 dlg.set_persons(persons = self.__prev_idents)
757 result = dlg.ShowModal()
758 if result == wx.ID_OK:
759 wx.BeginBusyCursor()
760 self.person = dlg.get_selected_person()
761 dlg.Destroy()
762 wx.EndBusyCursor()
763 return True
764
765 dlg.Destroy()
766 return False
767
768
769 if keycode == wx.WXK_UP:
770 evt.Skip()
771
772 if self._prev_search_term is not None:
773 self.SetValue(self._prev_search_term)
774 return False
775
776
777 if keycode == wx.WXK_F2:
778 evt.Skip()
779 dbcfg = gmCfg.cCfgSQL()
780 search_immediately = bool(dbcfg.get2 (
781 option = 'patient_search.external_sources.immediately_search_if_single_source',
782 workplace = gmSurgery.gmCurrentPractice().active_workplace,
783 bias = 'user',
784 default = 0
785 ))
786 p = get_person_from_external_sources (
787 parent = wx.GetTopLevelParent(self),
788 search_immediately = search_immediately
789 )
790 if p is not None:
791 self.person = p
792 return True
793 return False
794
795
796
797
798 evt.Skip()
799
801 """This is called from the ENTER handler."""
802
803
804 curr_search_term = self.GetValue().strip()
805 if curr_search_term == '':
806 return None
807
808
809 if self.person is not None:
810 if curr_search_term == self.person['description']:
811 return None
812
813
814 if self.IsModified():
815 self._prev_search_term = curr_search_term
816
817 self._on_enter(search_term = curr_search_term)
818
820 """This can be overridden in child classes."""
821
822 wx.BeginBusyCursor()
823
824
825 idents = self.__person_searcher.get_identities(search_term)
826
827 if idents is None:
828 wx.EndBusyCursor()
829 gmGuiHelpers.gm_show_info (
830 _('Error searching for matching persons.\n\n'
831 'Search term: "%s"'
832 ) % search_term,
833 _('selecting person')
834 )
835 return None
836
837 _log.info("%s matching person(s) found", len(idents))
838
839 if len(idents) == 0:
840 wx.EndBusyCursor()
841
842 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
843 wx.GetTopLevelParent(self),
844 -1,
845 caption = _('Selecting patient'),
846 question = _(
847 'Cannot find any matching patients for the search term\n\n'
848 ' "%s"\n\n'
849 'You may want to try a shorter search term.\n'
850 ) % search_term,
851 button_defs = [
852 {'label': _('Go back'), 'tooltip': _('Go back and search again.'), 'default': True},
853 {'label': _('Create new'), 'tooltip': _('Create new patient.')}
854 ]
855 )
856 if dlg.ShowModal() != wx.ID_NO:
857 return
858
859 success = gmDemographicsWidgets.create_new_person(activate = True)
860 if success:
861 self.person = gmPerson.gmCurrentPatient()
862 else:
863 self.person = None
864 return None
865
866
867 if len(idents) == 1:
868 self.person = idents[0]
869 wx.EndBusyCursor()
870 return None
871
872
873 dlg = cSelectPersonFromListDlg(parent=wx.GetTopLevelParent(self), id=-1)
874 dlg.set_persons(persons=idents)
875 wx.EndBusyCursor()
876 result = dlg.ShowModal()
877 if result == wx.ID_CANCEL:
878 dlg.Destroy()
879 return None
880
881 wx.BeginBusyCursor()
882 self.person = dlg.get_selected_person()
883 dlg.Destroy()
884 wx.EndBusyCursor()
885
886 return None
887
889
890
891 try:
892 patient['dob']
893 check_dob = True
894 except TypeError:
895 check_dob = False
896
897 if check_dob:
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 success = gmPerson.set_active_patient(patient = patient, forced_reload = forced_reload)
914
915 if success:
916 if patient['dob'] is not None:
917 dbcfg = gmCfg.cCfgSQL()
918 dob_distance = dbcfg.get2 (
919 option = u'patient_search.dob_warn_interval',
920 workplace = gmSurgery.gmCurrentPractice().active_workplace,
921 bias = u'user',
922 default = u'1 week'
923 )
924
925 if patient.dob_in_range(dob_distance, dob_distance):
926 now = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone)
927 enc = gmI18N.get_encoding()
928 gmDispatcher.send(signal = 'statustext', msg = _(
929 '%(pat)s turns %(age)s on %(month)s %(day)s ! (today is %(month_now)s %(day_now)s)') % {
930 'pat': patient.get_description_gender(),
931 'age': patient.get_medical_age().strip('y'),
932 'month': patient.get_formatted_dob(format = '%B', encoding = enc),
933 'day': patient.get_formatted_dob(format = '%d', encoding = enc),
934 'month_now': now.strftime('%B').decode(enc),
935 'day_now': now.strftime('%d')
936 }
937 )
938
939 return success
940
942
987
988
989
991 name = _('<type here to search patient>')
992
993 curr_pat = gmPerson.gmCurrentPatient()
994 if curr_pat.connected:
995 name = curr_pat['description']
996 if curr_pat.locked:
997 name = _('%(name)s (locked)') % {'name': name}
998
999 self.SetValue(name)
1000
1001 if self.person is None:
1002 self.SetToolTipString(self._tt_search_hints)
1003 return
1004
1005 tt = u'%s%s-----------------------------------\n%s' % (
1006 gmTools.coalesce(self.person['emergency_contact'], u'', _('In case of emergency contact:') + u'\n %s\n'),
1007 gmTools.coalesce(self.person['comment'], u'', u'\n%s\n'),
1008 self._tt_search_hints
1009 )
1010 self.SetToolTipString(tt)
1011
1013 if not set_active_patient(patient=pat, forced_reload = self.__always_reload_after_search):
1014 _log.error('cannot change active patient')
1015 return None
1016
1017 self._remember_ident(pat)
1018
1019 return True
1020
1021
1022
1024
1025 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1026 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_name_identity_change)
1027 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_name_identity_change)
1028
1029 gmDispatcher.connect(signal = 'patient_locked', receiver = self._on_post_patient_selection)
1030 gmDispatcher.connect(signal = 'patient_unlocked', receiver = self._on_post_patient_selection)
1031
1033 wx.CallAfter(self._display_name)
1034
1035 - def _on_post_patient_selection(self, **kwargs):
1040
1042
1043 if self.__always_dismiss_on_search:
1044 _log.warning("dismissing patient before patient search")
1045 self._set_person_as_active_patient(-1)
1046
1047 super(self.__class__, self)._on_enter(search_term=search_term)
1048
1049 if self.person is None:
1050 return
1051
1052 self._set_person_as_active_patient(self.person)
1053
1055
1056 success = super(self.__class__, self)._on_char(evt)
1057 if success:
1058 self._set_person_as_active_patient(self.person)
1059
1060
1061
1063
1072
1073
1075 self.matcher.set_items([ {'data': i, 'label': i, 'weight': 1} for i in items ])
1076
1077
1078 from Gnumed.wxGladeWidgets import wxgWaitingListEntryEditAreaPnl
1079
1080 -class cWaitingListEntryEditAreaPnl(wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
1081
1082 - def __init__ (self, *args, **kwargs):
1083
1084 try:
1085 self.patient = kwargs['patient']
1086 del kwargs['patient']
1087 except KeyError:
1088 self.patient = None
1089
1090 try:
1091 data = kwargs['entry']
1092 del kwargs['entry']
1093 except KeyError:
1094 data = None
1095
1096 wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl.__init__(self, *args, **kwargs)
1097 gmEditArea.cGenericEditAreaMixin.__init__(self)
1098
1099 if data is None:
1100 self.mode = 'new'
1101 else:
1102 self.data = data
1103 self.mode = 'edit'
1104
1105 praxis = gmSurgery.gmCurrentPractice()
1106 pats = praxis.waiting_list_patients
1107 zones = {}
1108 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ])
1109 self._PRW_zone.update_matcher(items = zones.keys())
1110
1111
1112
1113 - def _refresh_as_new(self):
1114 if self.patient is None:
1115 self._PRW_patient.person = None
1116 self._PRW_patient.Enable(True)
1117 self._PRW_patient.SetFocus()
1118 else:
1119 self._PRW_patient.person = self.patient
1120 self._PRW_patient.Enable(False)
1121 self._PRW_comment.SetFocus()
1122 self._PRW_patient._display_name()
1123
1124 self._PRW_comment.SetValue(u'')
1125 self._PRW_zone.SetValue(u'')
1126 self._SPCTRL_urgency.SetValue(0)
1127
1129 self._PRW_patient.person = gmPerson.cIdentity(aPK_obj = self.data['pk_identity'])
1130 self._PRW_patient.Enable(False)
1131 self._PRW_patient._display_name()
1132
1133 self._PRW_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
1134 self._PRW_zone.SetValue(gmTools.coalesce(self.data['waiting_zone'], u''))
1135 self._SPCTRL_urgency.SetValue(self.data['urgency'])
1136
1137 self._PRW_comment.SetFocus()
1138
1139 - def _valid_for_save(self):
1140 validity = True
1141
1142 self.display_tctrl_as_valid(tctrl = self._PRW_patient, valid = (self._PRW_patient.person is not None))
1143 validity = (self._PRW_patient.person is not None)
1144
1145 if validity is False:
1146 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add to waiting list. Missing essential input.'))
1147
1148 return validity
1149
1150 - def _save_as_new(self):
1151
1152 self._PRW_patient.person.put_on_waiting_list (
1153 urgency = self._SPCTRL_urgency.GetValue(),
1154 comment = gmTools.none_if(self._PRW_comment.GetValue().strip(), u''),
1155 zone = gmTools.none_if(self._PRW_zone.GetValue().strip(), u'')
1156 )
1157
1158 self.data = {'pk_identity': self._PRW_patient.person.ID, 'comment': None, 'waiting_zone': None, 'urgency': 0}
1159 return True
1160
1161 - def _save_as_update(self):
1162 gmSurgery.gmCurrentPractice().update_in_waiting_list (
1163 pk = self.data['pk_waiting_list'],
1164 urgency = self._SPCTRL_urgency.GetValue(),
1165 comment = self._PRW_comment.GetValue().strip(),
1166 zone = self._PRW_zone.GetValue().strip()
1167 )
1168 return True
1169
1170 from Gnumed.wxGladeWidgets import wxgWaitingListPnl
1171
1172 -class cWaitingListPnl(wxgWaitingListPnl.wxgWaitingListPnl, gmRegetMixin.cRegetOnPaintMixin):
1173
1183
1184
1185
1187 self._LCTRL_patients.set_columns ([
1188 _('Zone'),
1189 _('Urgency'),
1190
1191 _('Waiting time'),
1192 _('Patient'),
1193 _('Born'),
1194 _('Comment')
1195 ])
1196 self._LCTRL_patients.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1197 self._PRW_zone.add_callback_on_selection(callback = self._on_zone_selected)
1198 self._PRW_zone.add_callback_on_lose_focus(callback = self._on_zone_selected)
1199
1201 gmDispatcher.connect(signal = u'waiting_list_generic_mod_db', receiver = self._on_waiting_list_modified)
1202
1204
1205 praxis = gmSurgery.gmCurrentPractice()
1206 pats = praxis.waiting_list_patients
1207
1208
1209 zones = {}
1210 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ])
1211 self._PRW_zone.update_matcher(items = zones.keys())
1212 del zones
1213
1214
1215 self.__current_zone = self._PRW_zone.GetValue().strip()
1216 if self.__current_zone == u'':
1217 pats = [ p for p in pats ]
1218 else:
1219 pats = [ p for p in pats if p['waiting_zone'] == self.__current_zone ]
1220
1221 self._LCTRL_patients.set_string_items (
1222 [ [
1223 gmTools.coalesce(p['waiting_zone'], u''),
1224 p['urgency'],
1225 p['waiting_time_formatted'].replace(u'00 ', u'', 1).replace('00:', u'').lstrip('0'),
1226 u'%s, %s (%s)' % (p['lastnames'], p['firstnames'], p['l10n_gender']),
1227 gmTools.coalesce (
1228 gmTools.coalesce (
1229 p['dob'],
1230 u'',
1231 function_initial = ('strftime', u'%d %b %Y')
1232 ),
1233 u'',
1234 function_initial = ('decode', gmI18N.get_encoding())
1235 ),
1236 gmTools.coalesce(p['comment'], u'')
1237 ] for p in pats
1238 ]
1239 )
1240 self._LCTRL_patients.set_column_widths()
1241 self._LCTRL_patients.set_data(pats)
1242 self._LCTRL_patients.Refresh()
1243 self._LCTRL_patients.SetToolTipString ( _(
1244 '%s patients are waiting.\n'
1245 '\n'
1246 'Doubleclick to activate (entry will stay in list).'
1247 ) % len(pats))
1248
1249 self._LBL_no_of_patients.SetLabel(_('(%s patients)') % len(pats))
1250
1251 if len(pats) == 0:
1252 self._BTN_activate.Enable(False)
1253 self._BTN_activateplus.Enable(False)
1254 self._BTN_remove.Enable(False)
1255 self._BTN_edit.Enable(False)
1256 self._BTN_up.Enable(False)
1257 self._BTN_down.Enable(False)
1258 else:
1259 self._BTN_activate.Enable(True)
1260 self._BTN_activateplus.Enable(True)
1261 self._BTN_remove.Enable(True)
1262 self._BTN_edit.Enable(True)
1263 if len(pats) > 1:
1264 self._BTN_up.Enable(True)
1265 self._BTN_down.Enable(True)
1266
1267
1268
1270 if self.__current_zone == self._PRW_zone.GetValue().strip():
1271 return True
1272 wx.CallAfter(self.__refresh_waiting_list)
1273 return True
1274
1276 wx.CallAfter(self._schedule_data_reget)
1277
1284
1291
1299
1311
1320
1326
1332
1338
1339
1340
1341
1342
1344 self.__refresh_waiting_list()
1345 return True
1346
1347
1348
1349 if __name__ == "__main__":
1350
1351 if len(sys.argv) > 1:
1352 if sys.argv[1] == 'test':
1353 gmI18N.activate_locale()
1354 gmI18N.install_domain()
1355
1356 app = wx.PyWidgetTester(size = (200, 40))
1357
1358
1359
1360 app.SetWidget(cWaitingListPnl, -1)
1361 app.MainLoop()
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466