1 """GNUmed provider inbox handling widgets.
2 """
3
4 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
5
6 import sys
7 import logging
8
9
10 import wx
11
12
13 if __name__ == '__main__':
14 sys.path.insert(0, '../../')
15 from Gnumed.pycommon import gmI18N
16 from Gnumed.pycommon import gmExceptions
17 from Gnumed.pycommon import gmPG2
18 from Gnumed.pycommon import gmTools
19 from Gnumed.pycommon import gmDispatcher
20 from Gnumed.pycommon import gmMatchProvider
21 from Gnumed.pycommon import gmDateTime
22 from Gnumed.pycommon import gmNetworkTools
23
24 from Gnumed.business import gmPerson
25 from Gnumed.business import gmStaff
26 from Gnumed.business import gmProviderInbox
27 from Gnumed.business import gmClinicalRecord
28
29 from Gnumed.wxpython import gmGuiHelpers
30 from Gnumed.wxpython import gmListWidgets
31 from Gnumed.wxpython import gmPlugin
32 from Gnumed.wxpython import gmRegetMixin
33 from Gnumed.wxpython import gmPhraseWheel
34 from Gnumed.wxpython import gmEditArea
35 from Gnumed.wxpython import gmAuthWidgets
36 from Gnumed.wxpython import gmDataPackWidgets
37 from Gnumed.wxpython.gmPatSearchWidgets import set_active_patient
38 from Gnumed.wxpython.gmVaccWidgets import manage_vaccinations
39
40
41 _log = logging.getLogger('gm.ui')
42
43 _indicator = {
44 -1: '',
45 0: '',
46 1: '*!!*'
47 }
48
49
51
53
54 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
55
56 query = u"""
57 SELECT DISTINCT ON (label)
58 pk_type,
59 (l10n_type || ' (' || l10n_category || ')')
60 AS label
61 FROM
62 dem.v_inbox_item_type
63 WHERE
64 l10n_type %(fragment_condition)s
65 OR
66 type %(fragment_condition)s
67 OR
68 l10n_category %(fragment_condition)s
69 OR
70 category %(fragment_condition)s
71 ORDER BY label
72 LIMIT 50"""
73
74 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
75 mp.setThresholds(1, 2, 4)
76 self.matcher = mp
77 self.SetToolTipString(_('Select a message type.'))
78
91
93 wx.CallAfter(__display_clinical_reminders)
94
95 gmDispatcher.connect(signal = u'post_patient_selection', receiver = _display_clinical_reminders)
96
98 pat = gmPerson.gmCurrentPatient()
99 if not pat.connected:
100 return
101 for msg in pat.overdue_messages:
102 if msg['expiry_date'] is None:
103 exp = u''
104 else:
105 exp = _(' - expires %s') % gmDateTime.pydt_strftime (
106 msg['expiry_date'],
107 '%Y %b %d',
108 accuracy = gmDateTime.acc_days
109 )
110 txt = _(
111 'Due for %s (since %s%s):\n'
112 '%s'
113 '%s'
114 '\n'
115 'Patient: %s\n'
116 'Reminder by: %s'
117 ) % (
118 gmDateTime.format_interval_medically(msg['interval_due']),
119 gmDateTime.pydt_strftime(msg['due_date'], '%Y %b %d', accuracy = gmDateTime.acc_days),
120 exp,
121 gmTools.coalesce(msg['comment'], u'', u'\n%s\n'),
122 gmTools.coalesce(msg['data'], u'', u'\n%s\n'),
123 pat['description_gender'],
124 msg['modified_by']
125 )
126 gmGuiHelpers.gm_show_warning (
127 aTitle = _('Clinical reminder'),
128 aMessage = txt
129 )
130 for hint in pat.dynamic_hints:
131 txt = u'%s\n\n%s\n\n %s' % (
132 hint['title'],
133 gmTools.wrap(hint['hint'], width = 50, initial_indent = u' ', subsequent_indent = u' '),
134 hint['source']
135 )
136 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
137 None,
138 -1,
139 caption = _('Clinical hint'),
140 question = txt,
141 button_defs = [
142 {'label': _('OK'), 'tooltip': _('OK'), 'default': True},
143 {'label': _('More info'), 'tooltip': _('Go to [%s]') % hint['url']}
144 ]
145 )
146 button = dlg.ShowModal()
147 dlg.Destroy()
148 if button == wx.ID_NO:
149 gmNetworkTools.open_url_in_browser(hint['url'], autoraise = False)
150
151 return
152
153 from Gnumed.wxGladeWidgets import wxgInboxMessageEAPnl
154
155 -class cInboxMessageEAPnl(wxgInboxMessageEAPnl.wxgInboxMessageEAPnl, gmEditArea.cGenericEditAreaMixin):
156
176
182
183
184
249
251
252 pat_id = None
253 if self._CHBOX_active_patient.GetValue() is True:
254 pat_id = gmPerson.gmCurrentPatient().ID
255 else:
256 if self._PRW_patient.person is not None:
257 pat_id = self._PRW_patient.person.ID
258
259 receiver = None
260 if self._CHBOX_send_to_me.IsChecked():
261 receiver = gmStaff.gmCurrentProvider()['pk_staff']
262 else:
263 if self._PRW_receiver.GetData() is not None:
264 receiver = self._PRW_receiver.GetData()
265
266 msg = gmProviderInbox.create_inbox_message (
267 patient = pat_id,
268 staff = receiver,
269 message_type = self._PRW_type.GetData(can_create = True),
270 subject = self._TCTRL_subject.GetValue().strip()
271 )
272
273 msg['data'] = self._TCTRL_message.GetValue().strip()
274
275 if self._PRW_due.is_valid_timestamp():
276 msg['due_date'] = self._PRW_due.date
277
278 if self._PRW_expiry.is_valid_timestamp():
279 msg['expiry_date'] = self._PRW_expiry.date
280
281 if self._RBTN_normal.GetValue() is True:
282 msg['importance'] = 0
283 elif self._RBTN_high.GetValue() is True:
284 msg['importance'] = 1
285 else:
286 msg['importance'] = -1
287
288 msg.save()
289 self.data = msg
290 return True
291
327
353
355 self._refresh_as_new()
356
410
411
412
414 if self._CHBOX_active_patient.IsChecked():
415 self._PRW_patient.Enable(False)
416 self._PRW_patient.person = None
417 else:
418 self._PRW_patient.Enable(True)
419
421 if self._CHBOX_send_to_me.IsChecked():
422 self._PRW_receiver.Enable(False)
423 self._PRW_receiver.SetData(data = gmStaff.gmCurrentProvider()['pk_staff'])
424 else:
425 self._PRW_receiver.Enable(True)
426 self._PRW_receiver.SetText(value = u'', data = None)
427
443
444
463
464 return gmListWidgets.get_choices_from_list (
465 parent = parent,
466 msg = None,
467 caption = _('Reminders for the current patient'),
468 columns = [ _('Status'), _('Subject'), '#' ],
469 single_selection = False,
470 can_return_empty = True,
471 ignore_OK_button = False,
472 refresh_callback = refresh
473
474
475
476
477
478
479 )
480
481
482 from Gnumed.wxGladeWidgets import wxgProviderInboxPnl
483
484 -class cProviderInboxPnl(wxgProviderInboxPnl.wxgProviderInboxPnl, gmRegetMixin.cRegetOnPaintMixin):
485
486 _item_handlers = {}
487
488
503
504
505
509
511 _log.debug('_populate_with_data() (after _schedule_data_reget ?)')
512 self.__populate_inbox()
513 return True
514
515
516
518 _log.debug('called by notebook plugin API, skipping inbox loading')
519
520 return True
521
522
523
525 gmDispatcher.connect(signal = u'message_inbox_generic_mod_db', receiver = self._on_message_inbox_generic_mod_db)
526 gmDispatcher.connect(signal = u'message_inbox_mod_db', receiver = self._on_message_inbox_mod_db)
527
528 gmDispatcher.connect(signal = u'reviewed_test_results_mod_db', receiver = self._on_results_mod_db)
529 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_identity_mod_db)
530 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db)
531 gmDispatcher.connect(signal = u'doc_obj_review_mod_db', receiver = self._on_doc_obj_review_mod_db)
532 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
533
535 self._LCTRL_provider_inbox.debug = 'provider inbox list'
536
537 self._LCTRL_provider_inbox.set_columns([u'', _('Sent'), _('Status'), _('Category - Type'), _('Message')])
538 self._LCTRL_provider_inbox.searchable_columns = [2, 3, 4]
539 self._LCTRL_provider_inbox.item_tooltip_callback = self._get_msg_tooltip
540
541 self.__update_greeting()
542
543 if gmPerson.gmCurrentPatient().connected:
544 self._CHBOX_active_patient.Enable()
545
556
558 _log.debug('populating provider inbox')
559
560
561 pk_patient = None
562 if self._CHBOX_active_patient.IsChecked():
563 _log.debug('restricting to active patient')
564 curr_pat = gmPerson.gmCurrentPatient()
565 if curr_pat.connected:
566 pk_patient = curr_pat.ID
567
568 include_without_provider = True
569 if self._CHBOX_active_provider.IsChecked():
570 _log.debug('restricting to active provider directly')
571 include_without_provider = False
572
573
574 if self._RBTN_all_messages.GetValue():
575 _log.debug('loading all but expired messages')
576 self.__msgs = self.provider.inbox.get_messages (
577 pk_patient = pk_patient,
578 include_without_provider = include_without_provider,
579 exclude_expired = True,
580 expired_only = False,
581 overdue_only = False,
582 unscheduled_only = False,
583 exclude_unscheduled = False
584 )
585 elif self._RBTN_overdue_messages.GetValue():
586 _log.debug('loading overdue messages only')
587 self.__msgs = self.provider.inbox.get_messages (
588 pk_patient = pk_patient,
589 include_without_provider = include_without_provider,
590 exclude_expired = True,
591 expired_only = False,
592 overdue_only = True,
593 unscheduled_only = False,
594 exclude_unscheduled = True,
595 order_by = u'due_date, importance DESC, received_when DESC'
596 )
597 elif self._RBTN_scheduled_messages.GetValue():
598 _log.debug('loading overdue messages only')
599 self.__msgs = self.provider.inbox.get_messages (
600 pk_patient = pk_patient,
601 include_without_provider = include_without_provider,
602 exclude_expired = True,
603 expired_only = False,
604 overdue_only = False,
605 unscheduled_only = False,
606 exclude_unscheduled = True,
607 order_by = u'due_date, importance DESC, received_when DESC'
608 )
609 elif self._RBTN_unscheduled_messages.GetValue():
610 _log.debug('loading unscheduled messages only')
611 self.__msgs = self.provider.inbox.get_messages (
612 pk_patient = pk_patient,
613 include_without_provider = include_without_provider,
614 exclude_expired = True,
615 expired_only = False,
616 overdue_only = False,
617 unscheduled_only = True,
618 exclude_unscheduled = False
619 )
620 elif self._RBTN_expired_messages.GetValue():
621 _log.debug('loading expired messages only')
622 self.__msgs = self.provider.inbox.get_messages (
623 pk_patient = pk_patient,
624 include_without_provider = include_without_provider,
625 exclude_expired = False,
626 expired_only = True,
627 overdue_only = False,
628 unscheduled_only = False,
629 exclude_unscheduled = True,
630 order_by = u'expiry_date DESC, importance DESC, received_when DESC'
631 )
632
633 _log.debug('total # of inbox msgs: %s', len(self.__msgs))
634
635 items = []
636 for m in self.__msgs:
637 item = [_indicator[m['importance']], gmDateTime.pydt_strftime(m['received_when'], '%Y-%m-%d')]
638 if m['due_date'] is None:
639 item.append(u'')
640 else:
641 if m['is_expired'] is True:
642 item.append(_('expired'))
643 else:
644 if m['is_overdue'] is True:
645 item.append(_('%s overdue') % gmDateTime.format_interval_medically(m['interval_due']))
646 else:
647 item.append(_('due in %s') % gmDateTime.format_interval_medically(m['interval_due']))
648 item.append(u'%s - %s' % (m['l10n_category'], m['l10n_type']))
649 item.append(m['comment'])
650 items.append(item)
651
652 _log.debug('# of list items created from msgs: %s', len(items))
653 self._LCTRL_provider_inbox.set_string_items(items = items)
654 self._LCTRL_provider_inbox.set_data(data = self.__msgs)
655 self._LCTRL_provider_inbox.set_column_widths()
656 self._TXT_inbox_item_comment.SetValue(u'')
657 self.__update_greeting(len(items))
658
659
660
662 _log.debug('reviewed_test_results_mod_db')
663 wx.CallAfter(self.__on_message_inbox_mod_db)
664
666 _log.debug('identity_mod_db')
667 wx.CallAfter(self.__on_message_inbox_mod_db)
668
670 _log.debug('doc_obj_review_mod_db')
671 wx.CallAfter(self.__on_message_inbox_mod_db)
672
674 _log.debug('doc_mod_db')
675 wx.CallAfter(self.__on_message_inbox_mod_db)
676
678 _log.debug('message_inbox_generic_mod_db')
679 wx.CallAfter(self.__on_message_inbox_mod_db)
680
682 _log.debug('message_inbox_mod_db')
683 wx.CallAfter(self.__on_message_inbox_mod_db)
684
686 self._schedule_data_reget()
687 gmDispatcher.send(signal = u'request_user_attention', msg = _('Please check your GNUmed Inbox !'))
688
690 _log.debug('post_patient_selection')
691 wx.CallAfter(self.__on_post_patient_selection)
692
694 self._CHBOX_active_patient.Enable()
695 self._schedule_data_reget()
696
698
699 try:
700 msg = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
701 except IndexError:
702 _log.exception('problem with provider inbox item data access')
703 gmGuiHelpers.gm_show_error (
704 aTitle = _('handling provider inbox item'),
705 aMessage = _('There was a problem accessing the message data.')
706 )
707 _log.debug('effecting inbox reload')
708 wx.CallAfter(self.__populate_inbox)
709 return False
710
711 if msg is None:
712 return
713
714 handler_key = '%s.%s' % (msg['category'], msg['type'])
715 try:
716 handle_item = cProviderInboxPnl._item_handlers[handler_key]
717 except KeyError:
718 if msg['pk_patient'] is None:
719 gmGuiHelpers.gm_show_warning (
720 _('No double-click action pre-programmed into\n'
721 'GNUmed for message category and type:\n'
722 '\n'
723 ' [%s]\n'
724 ) % handler_key,
725 _('handling provider inbox item')
726 )
727 return False
728 handle_item = self._goto_patient
729
730 if not handle_item(pk_context = msg['pk_context'], pk_patient = msg['pk_patient']):
731 _log.error('item handler returned <False>')
732 _log.error('handler key: [%s]', handler_key)
733 _log.error('message: %s', str(msg))
734 return False
735
736 return True
737
740
742 msg = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
743 if msg is None:
744 return
745
746 if msg['data'] is None:
747 tmp = _('Message: %s') % msg['comment']
748 else:
749 tmp = _('Message: %s\nData: %s') % (msg['comment'], msg['data'])
750
751 self._TXT_inbox_item_comment.SetValue(tmp)
752
754 tmp = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
755 if tmp is None:
756 return
757 self.__focussed_msg = tmp
758
759
760 menu = wx.Menu(title = _('Inbox Message Actions:'))
761
762 if self.__focussed_msg['pk_patient'] is not None:
763 ID = wx.NewId()
764 menu.AppendItem(wx.MenuItem(menu, ID, _('Activate patient')))
765 wx.EVT_MENU(menu, ID, self._on_goto_patient)
766
767 if not self.__focussed_msg['is_virtual']:
768
769 ID = wx.NewId()
770 menu.AppendItem(wx.MenuItem(menu, ID, _('Delete')))
771 wx.EVT_MENU(menu, ID, self._on_delete_focussed_msg)
772
773 ID = wx.NewId()
774 menu.AppendItem(wx.MenuItem(menu, ID, _('Edit')))
775 wx.EVT_MENU(menu, ID, self._on_edit_focussed_msg)
776
777
778
779
780
781
782
783
784 self.PopupMenu(menu, wx.DefaultPosition)
785 menu.Destroy()
786
791
793 self._TXT_inbox_item_comment.SetValue(u'')
794 _log.debug('_on_active_patient_checkbox_ticked')
795 self.__populate_inbox()
796
798 self._TXT_inbox_item_comment.SetValue(u'')
799 _log.debug('_on_active_provider_checkbox_ticked')
800 self.__populate_inbox()
801
804
807
808
809
811 return self._goto_patient(pk_patient = self.__focussed_msg['pk_patient'])
812
814 if self.__focussed_msg['is_virtual']:
815 gmDispatcher.send(signal = 'statustext', msg = _('You must deal with the reason for this message to remove it from your inbox.'), beep = True)
816 return False
817
818 pk_patient = self.__focussed_msg['pk_patient']
819 if pk_patient is not None:
820 emr = gmClinicalRecord.cClinicalRecord(aPKey = pk_patient, allow_user_interaction = False)
821 epi = emr.add_episode(episode_name = 'administration', is_open = False)
822 soap_cat = gmTools.bool2subst (
823 (self.__focussed_msg['category'] == u'clinical'),
824 u'U',
825 None
826 )
827 narr = _('Deleted inbox message:\n%s') % self.__focussed_msg.format(with_patient = False)
828 emr.add_clin_narrative(note = narr, soap_cat = soap_cat, episode = epi)
829 gmDispatcher.send(signal = 'statustext', msg = _('Recorded deletion of inbox message in EMR.'), beep = False)
830
831 if not self.provider.inbox.delete_message(self.__focussed_msg['pk_inbox_message']):
832 gmDispatcher.send(signal='statustext', msg=_('Problem removing message from Inbox.'))
833 return False
834 return True
835
837 if self.__focussed_msg['is_virtual']:
838 gmDispatcher.send(signal = 'statustext', msg = _('This message cannot be edited because it is virtual.'))
839 return False
840 edit_inbox_message(parent = self, message = self.__focussed_msg, single_entry = True)
841 return True
842
844 if self.__focussed_msg['pk_staff'] is None:
845 gmDispatcher.send(signal = 'statustext', msg = _('This message is already visible to all providers.'))
846 return False
847 print "now distributing"
848 return True
849
851
852 wx.BeginBusyCursor()
853
854 msg = _('There is a message about patient [%s].\n\n'
855 'However, I cannot find that\n'
856 'patient in the GNUmed database.'
857 ) % pk_patient
858
859 try:
860 pat = gmPerson.cIdentity(aPK_obj = pk_patient)
861 except gmExceptions.ConstructorError:
862 wx.EndBusyCursor()
863 _log.exception('patient [%s] not found', pk_patient)
864 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
865 return False
866 except:
867 wx.EndBusyCursor()
868 raise
869
870 success = set_active_patient(patient = pat)
871
872 wx.EndBusyCursor()
873
874 if not success:
875 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
876 return False
877
878 return True
879
881
882 msg = _('Supposedly there are unreviewed documents\n'
883 'for patient [%s]. However, I cannot find\n'
884 'that patient in the GNUmed database.'
885 ) % pk_patient
886
887 wx.BeginBusyCursor()
888
889 try:
890 pat = gmPerson.cIdentity(aPK_obj = pk_patient)
891 except gmExceptions.ConstructorError:
892 wx.EndBusyCursor()
893 _log.exception('patient [%s] not found', pk_patient)
894 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
895 return False
896
897 success = set_active_patient(patient = pat)
898
899 wx.EndBusyCursor()
900
901 if not success:
902 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
903 return False
904
905 wx.CallAfter(gmDispatcher.send, signal = 'display_widget', name = 'gmShowMedDocs', sort_mode = 'review')
906 return True
907
909
910 msg = _('Supposedly there are unreviewed results\n'
911 'for patient [%s]. However, I cannot find\n'
912 'that patient in the GNUmed database.'
913 ) % pk_patient
914
915 wx.BeginBusyCursor()
916
917 try:
918 pat = gmPerson.cIdentity(aPK_obj = pk_patient)
919 except gmExceptions.ConstructorError:
920 wx.EndBusyCursor()
921 _log.exception('patient [%s] not found', pk_patient)
922 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
923 return False
924
925 success = set_active_patient(patient = pat)
926
927 wx.EndBusyCursor()
928
929 if not success:
930 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
931 return False
932
933 wx.CallAfter(gmDispatcher.send, signal = 'display_widget', name = 'gmMeasurementsGridPlugin')
934 return True
935
964
966
967 if parent is None:
968 parent = wx.GetApp().GetTopWindow()
969
970 def get_tooltip(item):
971 if item is None:
972 return None
973 return item.format()
974
975 def switch_activation(item):
976 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Switching clinical hint activation'))
977 if conn is None:
978 return False
979 item['is_active'] = not item['is_active']
980 return item.save(conn = conn)
981
982 def manage_data_packs(item):
983 gmDataPackWidgets.manage_data_packs(parent = parent)
984 return True
985
986 def refresh(lctrl):
987 hints = gmProviderInbox.get_dynamic_hints(order_by = u'is_active DESC, source, hint')
988 items = [ [
989 gmTools.bool2subst(h['is_active'], gmTools.u_checkmark_thin, u''),
990 h['title'],
991 h['source'][:30],
992 h['hint'][:60],
993 gmTools.coalesce(h['url'], u'')[:60],
994 h['lang'],
995 h['pk']
996 ] for h in hints ]
997 lctrl.set_string_items(items)
998 lctrl.set_data(hints)
999
1000 gmListWidgets.get_choices_from_list (
1001 parent = parent,
1002 msg = _('\nDynamic hints registered with GNUmed.\n'),
1003 caption = _('Showing dynamic hints.'),
1004 columns = [ _('Active'), _('Title'), _('Source'), _('Hint'), u'URL', _('Language'), u'#' ],
1005 single_selection = True,
1006 refresh_callback = refresh,
1007 left_extra_button = (
1008 _('(De)-Activate'),
1009 _('Switch activation of the selected hint'),
1010 switch_activation
1011 ),
1012 right_extra_button = (
1013 _('Data packs'),
1014 _('Browse and install clinical hints data packs'),
1015 manage_data_packs
1016 ),
1017 list_tooltip_callback = get_tooltip
1018 )
1019
1020
1021 if __name__ == '__main__':
1022
1023 if len(sys.argv) < 2:
1024 sys.exit()
1025
1026 if sys.argv[1] != 'test':
1027 sys.exit()
1028
1029 gmI18N.activate_locale()
1030 gmI18N.install_domain(domain = 'gnumed')
1031
1033 app = wx.PyWidgetTester(size = (800, 600))
1034 app.SetWidget(cProviderInboxPnl, -1)
1035 app.MainLoop()
1036
1038 app = wx.PyWidgetTester(size = (800, 600))
1039 app.SetWidget(cInboxMessageEAPnl, -1)
1040 app.MainLoop()
1041
1042
1043
1044 test_msg_ea()
1045
1046
1047