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, gmDateTime, gmMatchProvider, gmCfg2
26 from Gnumed.business import gmPerson, gmKVK, gmSurgery
27 from Gnumed.wxpython import gmGuiHelpers, gmDemographicsWidgets, gmAuthWidgets, gmRegetMixin, gmPhraseWheel, gmEditArea
28 from Gnumed.wxGladeWidgets import wxgSelectPersonFromListDlg, wxgSelectPersonDTOFromListDlg, wxgMergePatientsDlg
29
30
31 _log = logging.getLogger('gm.person')
32 _log.info(__version__)
33
34 _cfg = gmCfg2.gmCfgData()
35
36 ID_PatPickList = wx.NewId()
37 ID_BTN_AddNew = wx.NewId()
38
39
43
153
155
170
172 for col in range(len(self.__cols)):
173 self._LCTRL_persons.InsertColumn(col, self.__cols[col])
174
176 self._LCTRL_persons.DeleteAllItems()
177
178 pos = len(persons) + 1
179 if pos == 1:
180 return False
181
182 for person in persons:
183 row_num = self._LCTRL_persons.InsertStringItem(pos, label = gmTools.coalesce(person['title'], ''))
184 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = person['lastnames'])
185 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = person['firstnames'])
186 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = gmTools.coalesce(person['preferred'], ''))
187 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = person.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()))
188 self._LCTRL_persons.SetStringItem(index = row_num, col = 5, label = gmTools.coalesce(person['l10n_gender'], '?'))
189 label = u''
190 if person.is_patient:
191 enc = person.get_last_encounter()
192 if enc is not None:
193 label = u'%s (%s)' % (enc['started'].strftime('%x').decode(gmI18N.get_encoding()), enc['l10n_type'])
194 self._LCTRL_persons.SetStringItem(index = row_num, col = 6, label = label)
195 try: self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = person['match_type'])
196 except:
197 _log.exception('cannot set match_type field')
198 self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = u'??')
199
200 for col in range(len(self.__cols)):
201 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE)
202
203 self._BTN_select.Enable(False)
204 self._LCTRL_persons.SetFocus()
205 self._LCTRL_persons.Select(0)
206
207 self._LCTRL_persons.set_data(data=persons)
208
210 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
211
212
213
215 self._BTN_select.Enable(True)
216 return
217
219 self._BTN_select.Enable(True)
220 if self.IsModal():
221 self.EndModal(wx.ID_OK)
222 else:
223 self.Close()
224
226
238
240 for col in range(len(self.__cols)):
241 self._LCTRL_persons.InsertColumn(col, self.__cols[col])
242
244 self._LCTRL_persons.DeleteAllItems()
245
246 pos = len(dtos) + 1
247 if pos == 1:
248 return False
249
250 for rec in dtos:
251 row_num = self._LCTRL_persons.InsertStringItem(pos, label = rec['source'])
252 dto = rec['dto']
253 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = dto.lastnames)
254 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = dto.firstnames)
255 if dto.dob is None:
256 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = u'')
257 else:
258 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = dto.dob.strftime('%x').decode(gmI18N.get_encoding()))
259 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = gmTools.coalesce(dto.gender, ''))
260
261 for col in range(len(self.__cols)):
262 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE)
263
264 self._BTN_select.Enable(False)
265 self._LCTRL_persons.SetFocus()
266 self._LCTRL_persons.Select(0)
267
268 self._LCTRL_persons.set_data(data=dtos)
269
271 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
272
273
274
276 self._BTN_select.Enable(True)
277 return
278
280 self._BTN_select.Enable(True)
281 if self.IsModal():
282 self.EndModal(wx.ID_OK)
283 else:
284 self.Close()
285
287
288 bdt_files = []
289
290
291
292 candidates = []
293 drives = 'cdefghijklmnopqrstuvwxyz'
294 for drive in drives:
295 candidate = drive + ':\Winacs\TEMP\BDT*.tmp'
296 candidates.extend(glob.glob(candidate))
297 for candidate in candidates:
298 path, filename = os.path.split(candidate)
299
300 bdt_files.append({'file': candidate, 'source': 'MCS/Isynet %s' % filename[-6:-4]})
301
302
303
304 src_order = [
305 ('explicit', 'return'),
306 ('workbase', 'append'),
307 ('local', 'append'),
308 ('user', 'append'),
309 ('system', 'append')
310 ]
311 xdt_profiles = _cfg.get (
312 group = 'workplace',
313 option = 'XDT profiles',
314 source_order = src_order
315 )
316 if xdt_profiles is None:
317 return []
318
319
320 src_order = [
321 ('explicit', 'return'),
322 ('workbase', 'return'),
323 ('local', 'return'),
324 ('user', 'return'),
325 ('system', 'return')
326 ]
327 for profile in xdt_profiles:
328 name = _cfg.get (
329 group = 'XDT profile %s' % profile,
330 option = 'filename',
331 source_order = src_order
332 )
333 if name is None:
334 _log.error('XDT profile [%s] does not define a <filename>' % profile)
335 continue
336 encoding = _cfg.get (
337 group = 'XDT profile %s' % profile,
338 option = 'encoding',
339 source_order = src_order
340 )
341 if encoding is None:
342 _log.warning('xDT source profile [%s] does not specify an <encoding> for BDT file [%s]' % (profile, name))
343 source = _cfg.get (
344 group = 'XDT profile %s' % profile,
345 option = 'source',
346 source_order = src_order
347 )
348 dob_format = _cfg.get (
349 group = 'XDT profile %s' % profile,
350 option = 'DOB format',
351 source_order = src_order
352 )
353 if dob_format is None:
354 _log.warning('XDT profile [%s] does not define a date of birth format in <DOB format>' % profile)
355 bdt_files.append({'file': name, 'source': source, 'encoding': encoding, 'dob_format': dob_format})
356
357 dtos = []
358 for bdt_file in bdt_files:
359 try:
360
361 dto = gmPerson.get_person_from_xdt (
362 filename = bdt_file['file'],
363 encoding = bdt_file['encoding'],
364 dob_format = bdt_file['dob_format']
365 )
366
367 except IOError:
368 gmGuiHelpers.gm_show_info (
369 _(
370 'Cannot access BDT file\n\n'
371 ' [%s]\n\n'
372 'to import patient.\n\n'
373 'Please check your configuration.'
374 ) % bdt_file,
375 _('Activating xDT patient')
376 )
377 _log.exception('cannot access xDT file [%s]' % bdt_file['file'])
378 continue
379 except:
380 gmGuiHelpers.gm_show_error (
381 _(
382 'Cannot load patient from BDT file\n\n'
383 ' [%s]'
384 ) % bdt_file,
385 _('Activating xDT patient')
386 )
387 _log.exception('cannot read patient from xDT file [%s]' % bdt_file['file'])
388 continue
389
390 dtos.append({'dto': dto, 'source': gmTools.coalesce(bdt_file['source'], dto.source)})
391
392 return dtos
393
395
396 pracsoft_files = []
397
398
399 candidates = []
400 drives = 'cdefghijklmnopqrstuvwxyz'
401 for drive in drives:
402 candidate = drive + ':\MDW2\PATIENTS.IN'
403 candidates.extend(glob.glob(candidate))
404 for candidate in candidates:
405 drive, filename = os.path.splitdrive(candidate)
406 pracsoft_files.append({'file': candidate, 'source': 'PracSoft (AU): drive %s' % drive})
407
408
409 src_order = [
410 ('explicit', 'append'),
411 ('workbase', 'append'),
412 ('local', 'append'),
413 ('user', 'append'),
414 ('system', 'append')
415 ]
416 fnames = _cfg.get (
417 group = 'AU PracSoft PATIENTS.IN',
418 option = 'filename',
419 source_order = src_order
420 )
421
422 src_order = [
423 ('explicit', 'return'),
424 ('user', 'return'),
425 ('system', 'return'),
426 ('local', 'return'),
427 ('workbase', 'return')
428 ]
429 source = _cfg.get (
430 group = 'AU PracSoft PATIENTS.IN',
431 option = 'source',
432 source_order = src_order
433 )
434
435 if source is not None:
436 for fname in fnames:
437 fname = os.path.abspath(os.path.expanduser(fname))
438 if os.access(fname, os.R_OK):
439 pracsoft_files.append({'file': os.path.expanduser(fname), 'source': source})
440 else:
441 _log.error('cannot read [%s] in AU PracSoft profile' % fname)
442
443
444 dtos = []
445 for pracsoft_file in pracsoft_files:
446 try:
447 tmp = gmPerson.get_persons_from_pracsoft_file(filename = pracsoft_file['file'])
448 except:
449 _log.exception('cannot parse PracSoft file [%s]' % pracsoft_file['file'])
450 continue
451 for dto in tmp:
452 dtos.append({'dto': dto, 'source': pracsoft_file['source']})
453
454 return dtos
455
470
472 """Load patient from external source.
473
474 - scan external sources for candidates
475 - let user select source
476 - if > 1 available: always
477 - if only 1 available: depending on search_immediately
478 - search for patients matching info from external source
479 - if more than one match:
480 - let user select patient
481 - if no match:
482 - create patient
483 - activate patient
484 """
485
486 dtos = []
487 dtos.extend(load_persons_from_xdt())
488 dtos.extend(load_persons_from_pracsoft_au())
489 dtos.extend(load_persons_from_kvks())
490
491
492 if len(dtos) == 0:
493 gmDispatcher.send(signal='statustext', msg=_('No patients found in external sources.'))
494 return None
495
496
497 if (len(dtos) == 1) and (dtos[0]['dto'].dob is not None):
498 dto = dtos[0]['dto']
499
500 curr_pat = gmPerson.gmCurrentPatient()
501 if curr_pat.connected:
502 key_dto = dto.firstnames + dto.lastnames + dto.dob.strftime('%Y-%m-%d') + dto.gender
503 names = curr_pat.get_active_name()
504 key_pat = names['firstnames'] + names['lastnames'] + curr_pat.get_formatted_dob(format = '%Y-%m-%d') + curr_pat['gender']
505 _log.debug('current patient: %s' % key_pat)
506 _log.debug('dto patient : %s' % key_dto)
507 if key_dto == key_pat:
508 gmDispatcher.send(signal='statustext', msg=_('The only external patient is already active in GNUmed.'), beep=False)
509 return None
510
511
512 if (len(dtos) == 1) and search_immediately:
513 dto = dtos[0]['dto']
514
515
516 else:
517 if parent is None:
518 parent = wx.GetApp().GetTopWindow()
519 dlg = cSelectPersonDTOFromListDlg(parent=parent, id=-1)
520 dlg.set_dtos(dtos=dtos)
521 result = dlg.ShowModal()
522 if result == wx.ID_CANCEL:
523 return None
524 dto = dlg.get_selected_dto()['dto']
525 dlg.Destroy()
526
527
528 idents = dto.get_candidate_identities(can_create=True)
529 if idents is None:
530 gmGuiHelpers.gm_show_info (_(
531 'Cannot create new patient:\n\n'
532 ' [%s %s (%s), %s]'
533 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())),
534 _('Activating external patient')
535 )
536 return None
537
538 if len(idents) == 1:
539 ident = idents[0]
540
541 if len(idents) > 1:
542 if parent is None:
543 parent = wx.GetApp().GetTopWindow()
544 dlg = cSelectPersonFromListDlg(parent=parent, id=-1)
545 dlg.set_persons(persons=idents)
546 result = dlg.ShowModal()
547 if result == wx.ID_CANCEL:
548 return None
549 ident = dlg.get_selected_person()
550 dlg.Destroy()
551
552 if activate_immediately:
553 if not set_active_patient(patient = ident):
554 gmGuiHelpers.gm_show_info (
555 _(
556 'Cannot activate patient:\n\n'
557 '%s %s (%s)\n'
558 '%s'
559 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())),
560 _('Activating external patient')
561 )
562 return None
563
564 dto.import_extra_data(identity = ident)
565 dto.delete_from_source()
566
567 return ident
568
570 """Widget for smart search for persons."""
571
573
574 try:
575 kwargs['style'] = kwargs['style'] | wx.TE_PROCESS_ENTER
576 except KeyError:
577 kwargs['style'] = wx.TE_PROCESS_ENTER
578
579
580
581 wx.TextCtrl.__init__(self, *args, **kwargs)
582
583 self.person = None
584
585 self.SetToolTipString (_(
586 'To search for a person type any of: \n'
587 '\n'
588 ' - fragment of last or first name\n'
589 " - date of birth (can start with '$' or '*')\n"
590 " - GNUmed ID of person (can start with '#')\n"
591 ' - exterenal ID of person\n'
592 '\n'
593 'and hit <ENTER>.\n'
594 '\n'
595 'Shortcuts:\n'
596 ' <F2>\n'
597 ' - scan external sources for persons\n'
598 ' <CURSOR-UP>\n'
599 ' - recall most recently used search term\n'
600 ' <CURSOR-DOWN>\n'
601 ' - list 10 most recently found persons\n'
602 ))
603
604
605 self.__person_searcher = gmPerson.cPatientSearcher_SQL()
606
607 self._prev_search_term = None
608 self.__prev_idents = []
609 self._lclick_count = 0
610
611 self.__register_events()
612
613
614
616 self.__person = person
617 wx.CallAfter(self._display_name)
618
621
622 person = property(_get_person, _set_person)
623
624
625
633
635
636 if not isinstance(ident, gmPerson.cIdentity):
637 return False
638
639
640 for known_ident in self.__prev_idents:
641 if known_ident['pk_identity'] == ident['pk_identity']:
642 return True
643
644 self.__prev_idents.append(ident)
645
646
647 if len(self.__prev_idents) > 10:
648 self.__prev_idents.pop(0)
649
650 return True
651
652
653
655 wx.EVT_CHAR(self, self.__on_char)
656 wx.EVT_SET_FOCUS(self, self._on_get_focus)
657 wx.EVT_KILL_FOCUS (self, self._on_loose_focus)
658 wx.EVT_TEXT_ENTER (self, self.GetId(), self.__on_enter)
659
661 """upon tabbing in
662
663 - select all text in the field so that the next
664 character typed will delete it
665 """
666 wx.CallAfter(self.SetSelection, -1, -1)
667 evt.Skip()
668
670
671
672
673
674
675
676
677
678
679 wx.CallAfter(self.SetSelection, 0, 0)
680
681 self._display_name()
682 self._remember_ident(self.person)
683
684 evt.Skip()
685
688
690 """True: patient was selected.
691 False: no patient was selected.
692 """
693 keycode = evt.GetKeyCode()
694
695
696 if keycode == wx.WXK_DOWN:
697 evt.Skip()
698 if len(self.__prev_idents) == 0:
699 return False
700
701 dlg = cSelectPersonFromListDlg(parent = wx.GetTopLevelParent(self), id = -1)
702 dlg.set_persons(persons = self.__prev_idents)
703 result = dlg.ShowModal()
704 if result == wx.ID_OK:
705 wx.BeginBusyCursor()
706 self.person = dlg.get_selected_person()
707 dlg.Destroy()
708 wx.EndBusyCursor()
709 return True
710
711 dlg.Destroy()
712 return False
713
714
715 if keycode == wx.WXK_UP:
716 evt.Skip()
717
718 if self._prev_search_term is not None:
719 self.SetValue(self._prev_search_term)
720 return False
721
722
723 if keycode == wx.WXK_F2:
724 evt.Skip()
725 dbcfg = gmCfg.cCfgSQL()
726 search_immediately = bool(dbcfg.get2 (
727 option = 'patient_search.external_sources.immediately_search_if_single_source',
728 workplace = gmSurgery.gmCurrentPractice().active_workplace,
729 bias = 'user',
730 default = 0
731 ))
732 p = get_person_from_external_sources (
733 parent = wx.GetTopLevelParent(self),
734 search_immediately = search_immediately
735 )
736 if p is not None:
737 self.person = p
738 return True
739 return False
740
741
742
743
744 evt.Skip()
745
747 """This is called from the ENTER handler."""
748
749
750 curr_search_term = self.GetValue().strip()
751 if curr_search_term == '':
752 return None
753
754
755 if self.person is not None:
756 if curr_search_term == self.person['description']:
757 return None
758
759
760 if self.IsModified():
761 self._prev_search_term = curr_search_term
762
763 self._on_enter(search_term = curr_search_term)
764
766 """This can be overridden in child classes."""
767
768 wx.BeginBusyCursor()
769
770
771 idents = self.__person_searcher.get_identities(search_term)
772
773 if idents is None:
774 wx.EndBusyCursor()
775 gmGuiHelpers.gm_show_info (
776 _('Error searching for matching persons.\n\n'
777 'Search term: "%s"'
778 ) % search_term,
779 _('selecting person')
780 )
781 return None
782
783 _log.info("%s matching person(s) found", len(idents))
784
785 if len(idents) == 0:
786 wx.EndBusyCursor()
787
788 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
789 wx.GetTopLevelParent(self),
790 -1,
791 caption = _('Selecting patient'),
792 question = _(
793 'Cannot find any matching patients for the search term\n\n'
794 ' "%s"\n\n'
795 'You may want to try a shorter search term.\n'
796 ) % search_term,
797 button_defs = [
798 {'label': _('Go back'), 'tooltip': _('Go back and search again.'), 'default': True},
799 {'label': _('Create new'), 'tooltip': _('Create new patient.')}
800 ]
801 )
802 if dlg.ShowModal() != wx.ID_NO:
803 return
804
805 success = gmDemographicsWidgets.create_new_person(activate = True)
806 if success:
807 self.person = gmPerson.gmCurrentPatient()
808 else:
809 self.person = None
810 return None
811
812
813 if len(idents) == 1:
814 self.person = idents[0]
815 wx.EndBusyCursor()
816 return None
817
818
819 dlg = cSelectPersonFromListDlg(parent=wx.GetTopLevelParent(self), id=-1)
820 dlg.set_persons(persons=idents)
821 wx.EndBusyCursor()
822 result = dlg.ShowModal()
823 if result == wx.ID_CANCEL:
824 dlg.Destroy()
825 return None
826
827 wx.BeginBusyCursor()
828 self.person = dlg.get_selected_person()
829 dlg.Destroy()
830 wx.EndBusyCursor()
831
832 return None
833
835
836
837 try:
838 patient['dob']
839 check_dob = True
840 except TypeError:
841 check_dob = False
842
843 if check_dob:
844 if patient['dob'] is None:
845 gmGuiHelpers.gm_show_warning (
846 aTitle = _('Checking date of birth'),
847 aMessage = _(
848 '\n'
849 ' %s\n'
850 '\n'
851 'The date of birth for this patient is not known !\n'
852 '\n'
853 'You can proceed to work on the patient but\n'
854 'GNUmed will be unable to assist you with\n'
855 'age-related decisions.\n'
856 ) % patient['description_gender']
857 )
858
859 success = gmPerson.set_active_patient(patient = patient, forced_reload = forced_reload)
860
861 if success:
862 if patient['dob'] is not None:
863 dbcfg = gmCfg.cCfgSQL()
864 dob_distance = dbcfg.get2 (
865 option = u'patient_search.dob_warn_interval',
866 workplace = gmSurgery.gmCurrentPractice().active_workplace,
867 bias = u'user',
868 default = u'1 week'
869 )
870
871 if patient.dob_in_range(dob_distance, dob_distance):
872 now = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone)
873 enc = gmI18N.get_encoding()
874 gmDispatcher.send(signal = 'statustext', msg = _(
875 '%(pat)s turns %(age)s on %(month)s %(day)s ! (today is %(month_now)s %(day_now)s)') % {
876 'pat': patient.get_description_gender(),
877 'age': patient.get_medical_age().strip('y'),
878 'month': patient.get_formatted_dob(format = '%B', encoding = enc),
879 'day': patient.get_formatted_dob(format = '%d', encoding = enc),
880 'month_now': now.strftime('%B').decode(enc),
881 'day_now': now.strftime('%d')
882 }
883 )
884
885 return success
886
888
890
891 cPersonSearchCtrl.__init__(self, *args, **kwargs)
892
893 selector_tooltip = _(
894 'Patient search field. \n'
895 '\n'
896 'To search, type any of:\n'
897 ' - fragment of last or first name\n'
898 " - date of birth (can start with '$' or '*')\n"
899 " - patient ID (can start with '#')\n"
900 'and hit <ENTER>.\n'
901 '\n'
902 '<CURSOR-UP>\n'
903 ' - recall most recently used search term\n'
904 '<CURSOR-DOWN>\n'
905 ' - list 10 most recently activated patients\n'
906 '<F2>\n'
907 ' - scan external sources for patients to import and activate\n'
908 )
909 self.SetToolTip(wx.ToolTip(selector_tooltip))
910
911
912 cfg = gmCfg.cCfgSQL()
913
914 self.__always_dismiss_on_search = bool (
915 cfg.get2 (
916 option = 'patient_search.always_dismiss_previous_patient',
917 workplace = gmSurgery.gmCurrentPractice().active_workplace,
918 bias = 'user',
919 default = 0
920 )
921 )
922
923 self.__always_reload_after_search = bool (
924 cfg.get2 (
925 option = 'patient_search.always_reload_new_patient',
926 workplace = gmSurgery.gmCurrentPractice().active_workplace,
927 bias = 'user',
928 default = 0
929 )
930 )
931
932 self.__register_events()
933
934
935
946
948 if not set_active_patient(patient=pat, forced_reload = self.__always_reload_after_search):
949 _log.error('cannot change active patient')
950 return None
951
952 self._remember_ident(pat)
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976 return True
977
978
979
981
982 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
983 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_name_identity_change)
984 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_name_identity_change)
985
986 gmDispatcher.connect(signal = 'patient_locked', receiver = self._on_post_patient_selection)
987 gmDispatcher.connect(signal = 'patient_unlocked', receiver = self._on_post_patient_selection)
988
990 wx.CallAfter(self._display_name)
991
997
999
1000 if self.__always_dismiss_on_search:
1001 _log.warning("dismissing patient before patient search")
1002 self._set_person_as_active_patient(-1)
1003
1004 super(self.__class__, self)._on_enter(search_term=search_term)
1005
1006 if self.person is None:
1007 return
1008
1009 self._set_person_as_active_patient(self.person)
1010
1012
1013 success = super(self.__class__, self)._on_char(evt)
1014 if success:
1015 self._set_person_as_active_patient(self.person)
1016
1017
1018
1020
1029
1030
1032 self.matcher.set_items([ {'data': i, 'label': i, 'weight': 1} for i in items ])
1033
1034
1035 from Gnumed.wxGladeWidgets import wxgWaitingListEntryEditAreaPnl
1036
1037 -class cWaitingListEntryEditAreaPnl(wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
1038
1039 - def __init__ (self, *args, **kwargs):
1040
1041 try:
1042 self.patient = kwargs['patient']
1043 del kwargs['patient']
1044 except KeyError:
1045 self.patient = None
1046
1047 try:
1048 data = kwargs['entry']
1049 del kwargs['entry']
1050 except KeyError:
1051 data = None
1052
1053 wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl.__init__(self, *args, **kwargs)
1054 gmEditArea.cGenericEditAreaMixin.__init__(self)
1055
1056 if data is None:
1057 self.mode = 'new'
1058 else:
1059 self.data = data
1060 self.mode = 'edit'
1061
1062 praxis = gmSurgery.gmCurrentPractice()
1063 pats = praxis.waiting_list_patients
1064 zones = {}
1065 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ])
1066 self._PRW_zone.update_matcher(items = zones.keys())
1067
1068
1069
1070 - def _refresh_as_new(self):
1071 if self.patient is None:
1072 self._PRW_patient.person = None
1073 self._PRW_patient.Enable(True)
1074 self._PRW_patient.SetFocus()
1075 else:
1076 self._PRW_patient.person = self.patient
1077 self._PRW_patient.Enable(False)
1078 self._PRW_comment.SetFocus()
1079 self._PRW_patient._display_name()
1080
1081 self._PRW_comment.SetValue(u'')
1082 self._PRW_zone.SetValue(u'')
1083 self._SPCTRL_urgency.SetValue(0)
1084
1086 self._PRW_patient.person = gmPerson.cIdentity(aPK_obj = self.data['pk_identity'])
1087 self._PRW_patient.Enable(False)
1088 self._PRW_patient._display_name()
1089
1090 self._PRW_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
1091 self._PRW_zone.SetValue(gmTools.coalesce(self.data['waiting_zone'], u''))
1092 self._SPCTRL_urgency.SetValue(self.data['urgency'])
1093
1094 self._PRW_comment.SetFocus()
1095
1096 - def _valid_for_save(self):
1097 validity = True
1098
1099 self.display_tctrl_as_valid(tctrl = self._PRW_patient, valid = (self._PRW_patient.person is not None))
1100 validity = (self._PRW_patient.person is not None)
1101
1102 if validity is False:
1103 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add to waiting list. Missing essential input.'))
1104
1105 return validity
1106
1107 - def _save_as_new(self):
1108
1109 self._PRW_patient.person.put_on_waiting_list (
1110 urgency = self._SPCTRL_urgency.GetValue(),
1111 comment = gmTools.none_if(self._PRW_comment.GetValue().strip(), u''),
1112 zone = gmTools.none_if(self._PRW_zone.GetValue().strip(), u'')
1113 )
1114
1115 self.data = {'pk_identity': None, 'comment': None, 'waiting_zone': None, 'urgency': 0}
1116 return True
1117
1118 - def _save_as_update(self):
1119 gmSurgery.gmCurrentPractice().update_in_waiting_list (
1120 pk = self.data['pk_waiting_list'],
1121 urgency = self._SPCTRL_urgency.GetValue(),
1122 comment = self._PRW_comment.GetValue().strip(),
1123 zone = self._PRW_zone.GetValue().strip()
1124 )
1125 return True
1126
1127 from Gnumed.wxGladeWidgets import wxgWaitingListPnl
1128
1129 -class cWaitingListPnl(wxgWaitingListPnl.wxgWaitingListPnl, gmRegetMixin.cRegetOnPaintMixin):
1130
1140
1141
1142
1144 self._LCTRL_patients.set_columns ([
1145 _('Zone'),
1146 _('Urgency'),
1147
1148 _('Waiting time'),
1149 _('Patient'),
1150 _('Born'),
1151 _('Comment')
1152 ])
1153 self._LCTRL_patients.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1154 self._PRW_zone.add_callback_on_selection(callback = self._on_zone_selected)
1155 self._PRW_zone.add_callback_on_lose_focus(callback = self._on_zone_selected)
1156
1158 gmDispatcher.connect(signal = u'waiting_list_generic_mod_db', receiver = self._on_waiting_list_modified)
1159
1161
1162 praxis = gmSurgery.gmCurrentPractice()
1163 pats = praxis.waiting_list_patients
1164
1165
1166 zones = {}
1167 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ])
1168 self._PRW_zone.update_matcher(items = zones.keys())
1169 del zones
1170
1171
1172 self.__current_zone = self._PRW_zone.GetValue().strip()
1173 if self.__current_zone == u'':
1174 pats = [ p for p in pats ]
1175 else:
1176 pats = [ p for p in pats if p['waiting_zone'] == self.__current_zone ]
1177
1178 self._LCTRL_patients.set_string_items (
1179 [ [
1180 gmTools.coalesce(p['waiting_zone'], u''),
1181 p['urgency'],
1182 p['waiting_time_formatted'].replace(u'00 ', u'', 1).replace('00:', u'').lstrip('0'),
1183 u'%s, %s (%s)' % (p['lastnames'], p['firstnames'], p['l10n_gender']),
1184 p['dob'],
1185 gmTools.coalesce(p['comment'], u'')
1186 ] for p in pats
1187 ]
1188 )
1189 self._LCTRL_patients.set_column_widths()
1190 self._LCTRL_patients.set_data(pats)
1191 self._LCTRL_patients.Refresh()
1192 self._LCTRL_patients.SetToolTipString ( _(
1193 '%s patients are waiting.\n'
1194 '\n'
1195 'Doubleclick to activate (entry will stay in list).'
1196 ) % len(pats))
1197
1198 self._LBL_no_of_patients.SetLabel(_('(%s patients)') % len(pats))
1199
1200 if len(pats) == 0:
1201 self._BTN_activate.Enable(False)
1202 self._BTN_activateplus.Enable(False)
1203 self._BTN_remove.Enable(False)
1204 self._BTN_edit.Enable(False)
1205 self._BTN_up.Enable(False)
1206 self._BTN_down.Enable(False)
1207 else:
1208 self._BTN_activate.Enable(True)
1209 self._BTN_activateplus.Enable(True)
1210 self._BTN_remove.Enable(True)
1211 self._BTN_edit.Enable(True)
1212 if len(pats) > 1:
1213 self._BTN_up.Enable(True)
1214 self._BTN_down.Enable(True)
1215
1216
1217
1219 if self.__current_zone == self._PRW_zone.GetValue().strip():
1220 return True
1221 wx.CallAfter(self.__refresh_waiting_list)
1222 return True
1223
1225 wx.CallAfter(self._schedule_data_reget)
1226
1233
1240
1248
1260
1269
1275
1281
1287
1288
1289
1290
1291
1293 self.__refresh_waiting_list()
1294 return True
1295
1296
1297
1298 if __name__ == "__main__":
1299
1300 if len(sys.argv) > 1:
1301 if sys.argv[1] == 'test':
1302 gmI18N.activate_locale()
1303 gmI18N.install_domain()
1304
1305 app = wx.PyWidgetTester(size = (200, 40))
1306
1307
1308
1309 app.SetWidget(cWaitingListPnl, -1)
1310 app.MainLoop()
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
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