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'dem.message_inbox_mod_db', receiver = self._on_message_inbox_mod_db)
526
527 gmDispatcher.connect(signal = u'clin.reviewed_test_results_mod_db', receiver = self._on_results_mod_db)
528 gmDispatcher.connect(signal = u'dem.identity_mod_db', receiver = self._on_identity_mod_db)
529 gmDispatcher.connect(signal = u'blobs.doc_med_mod_db', receiver = self._on_doc_mod_db)
530 gmDispatcher.connect(signal = u'blobs.reviewed_doc_objs_mod_db', receiver = self._on_doc_obj_review_mod_db)
531 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
532
534 self._LCTRL_provider_inbox.debug = 'provider inbox list'
535
536 self._LCTRL_provider_inbox.set_columns([u'', _('Sent'), _('Status'), _('Category - Type'), _('Message')])
537 self._LCTRL_provider_inbox.searchable_columns = [2, 3, 4]
538 self._LCTRL_provider_inbox.item_tooltip_callback = self._get_msg_tooltip
539
540 self.__update_greeting()
541
542 if gmPerson.gmCurrentPatient().connected:
543 self._CHBOX_active_patient.Enable()
544
555
557 _log.debug('populating provider inbox')
558
559
560 pk_patient = None
561 if self._CHBOX_active_patient.IsChecked():
562 _log.debug('restricting to active patient')
563 curr_pat = gmPerson.gmCurrentPatient()
564 if curr_pat.connected:
565 pk_patient = curr_pat.ID
566
567 include_without_provider = True
568 if self._CHBOX_active_provider.IsChecked():
569 _log.debug('restricting to active provider directly')
570 include_without_provider = False
571
572
573 if self._RBTN_all_messages.GetValue():
574 _log.debug('loading all but expired messages')
575 self.__msgs = self.provider.inbox.get_messages (
576 pk_patient = pk_patient,
577 include_without_provider = include_without_provider,
578 exclude_expired = True,
579 expired_only = False,
580 overdue_only = False,
581 unscheduled_only = False,
582 exclude_unscheduled = False
583 )
584 elif self._RBTN_overdue_messages.GetValue():
585 _log.debug('loading overdue messages only')
586 self.__msgs = self.provider.inbox.get_messages (
587 pk_patient = pk_patient,
588 include_without_provider = include_without_provider,
589 exclude_expired = True,
590 expired_only = False,
591 overdue_only = True,
592 unscheduled_only = False,
593 exclude_unscheduled = True,
594 order_by = u'due_date, importance DESC, received_when DESC'
595 )
596 elif self._RBTN_scheduled_messages.GetValue():
597 _log.debug('loading overdue messages only')
598 self.__msgs = self.provider.inbox.get_messages (
599 pk_patient = pk_patient,
600 include_without_provider = include_without_provider,
601 exclude_expired = True,
602 expired_only = False,
603 overdue_only = False,
604 unscheduled_only = False,
605 exclude_unscheduled = True,
606 order_by = u'due_date, importance DESC, received_when DESC'
607 )
608 elif self._RBTN_unscheduled_messages.GetValue():
609 _log.debug('loading unscheduled messages only')
610 self.__msgs = self.provider.inbox.get_messages (
611 pk_patient = pk_patient,
612 include_without_provider = include_without_provider,
613 exclude_expired = True,
614 expired_only = False,
615 overdue_only = False,
616 unscheduled_only = True,
617 exclude_unscheduled = False
618 )
619 elif self._RBTN_expired_messages.GetValue():
620 _log.debug('loading expired messages only')
621 self.__msgs = self.provider.inbox.get_messages (
622 pk_patient = pk_patient,
623 include_without_provider = include_without_provider,
624 exclude_expired = False,
625 expired_only = True,
626 overdue_only = False,
627 unscheduled_only = False,
628 exclude_unscheduled = True,
629 order_by = u'expiry_date DESC, importance DESC, received_when DESC'
630 )
631
632 _log.debug('total # of inbox msgs: %s', len(self.__msgs))
633
634 items = []
635 for m in self.__msgs:
636 item = [_indicator[m['importance']], gmDateTime.pydt_strftime(m['received_when'], '%Y-%m-%d')]
637 if m['due_date'] is None:
638 item.append(u'')
639 else:
640 if m['is_expired'] is True:
641 item.append(_('expired'))
642 else:
643 if m['is_overdue'] is True:
644 item.append(_('%s overdue') % gmDateTime.format_interval_medically(m['interval_due']))
645 else:
646 item.append(_('due in %s') % gmDateTime.format_interval_medically(m['interval_due']))
647 item.append(u'%s - %s' % (m['l10n_category'], m['l10n_type']))
648 item.append(m['comment'])
649 items.append(item)
650
651 _log.debug('# of list items created from msgs: %s', len(items))
652 self._LCTRL_provider_inbox.set_string_items(items = items)
653 self._LCTRL_provider_inbox.set_data(data = self.__msgs)
654 self._LCTRL_provider_inbox.set_column_widths()
655 self._TXT_inbox_item_comment.SetValue(u'')
656 self.__update_greeting(len(items))
657
658
659
661 _log.debug('reviewed_test_results_mod_db')
662 wx.CallAfter(self.__on_message_inbox_mod_db)
663
665 _log.debug('identity_mod_db')
666 wx.CallAfter(self.__on_message_inbox_mod_db)
667
669 _log.debug('doc_obj_review_mod_db')
670 wx.CallAfter(self.__on_message_inbox_mod_db)
671
673 _log.debug('doc_mod_db')
674 wx.CallAfter(self.__on_message_inbox_mod_db)
675
677 _log.debug('message_inbox_mod_db')
678 wx.CallAfter(self.__on_message_inbox_mod_db)
679
681 self._schedule_data_reget()
682 gmDispatcher.send(signal = u'request_user_attention', msg = _('Please check your GNUmed Inbox !'))
683
685 _log.debug('post_patient_selection')
686 wx.CallAfter(self.__on_post_patient_selection)
687
689 self._CHBOX_active_patient.Enable()
690 self._schedule_data_reget()
691
693
694 try:
695 msg = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
696 except IndexError:
697 _log.exception('problem with provider inbox item data access')
698 gmGuiHelpers.gm_show_error (
699 aTitle = _('handling provider inbox item'),
700 aMessage = _('There was a problem accessing the message data.')
701 )
702 _log.debug('effecting inbox reload')
703 wx.CallAfter(self.__populate_inbox)
704 return False
705
706 if msg is None:
707 return
708
709 handler_key = '%s.%s' % (msg['category'], msg['type'])
710 try:
711 handle_item = cProviderInboxPnl._item_handlers[handler_key]
712 except KeyError:
713 if msg['pk_patient'] is None:
714 gmGuiHelpers.gm_show_warning (
715 _('No double-click action pre-programmed into\n'
716 'GNUmed for message category and type:\n'
717 '\n'
718 ' [%s]\n'
719 ) % handler_key,
720 _('handling provider inbox item')
721 )
722 return False
723 handle_item = self._goto_patient
724
725 if not handle_item(pk_context = msg['pk_context'], pk_patient = msg['pk_patient']):
726 _log.error('item handler returned <False>')
727 _log.error('handler key: [%s]', handler_key)
728 _log.error('message: %s', str(msg))
729 return False
730
731 return True
732
735
737 msg = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
738 if msg is None:
739 return
740
741 if msg['data'] is None:
742 tmp = _('Message: %s') % msg['comment']
743 else:
744 tmp = _('Message: %s\nData: %s') % (msg['comment'], msg['data'])
745
746 self._TXT_inbox_item_comment.SetValue(tmp)
747
749 tmp = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
750 if tmp is None:
751 return
752 self.__focussed_msg = tmp
753
754
755 menu = wx.Menu(title = _('Inbox Message Actions:'))
756
757 if self.__focussed_msg['pk_patient'] is not None:
758 ID = wx.NewId()
759 menu.AppendItem(wx.MenuItem(menu, ID, _('Activate patient')))
760 wx.EVT_MENU(menu, ID, self._on_goto_patient)
761
762 if not self.__focussed_msg['is_virtual']:
763
764 ID = wx.NewId()
765 menu.AppendItem(wx.MenuItem(menu, ID, _('Delete')))
766 wx.EVT_MENU(menu, ID, self._on_delete_focussed_msg)
767
768 ID = wx.NewId()
769 menu.AppendItem(wx.MenuItem(menu, ID, _('Edit')))
770 wx.EVT_MENU(menu, ID, self._on_edit_focussed_msg)
771
772
773
774
775
776
777
778
779 self.PopupMenu(menu, wx.DefaultPosition)
780 menu.Destroy()
781
786
788 self._TXT_inbox_item_comment.SetValue(u'')
789 _log.debug('_on_active_patient_checkbox_ticked')
790 self.__populate_inbox()
791
793 self._TXT_inbox_item_comment.SetValue(u'')
794 _log.debug('_on_active_provider_checkbox_ticked')
795 self.__populate_inbox()
796
799
802
803
804
806 return self._goto_patient(pk_patient = self.__focussed_msg['pk_patient'])
807
809 if self.__focussed_msg['is_virtual']:
810 gmDispatcher.send(signal = 'statustext', msg = _('You must deal with the reason for this message to remove it from your inbox.'), beep = True)
811 return False
812
813 pk_patient = self.__focussed_msg['pk_patient']
814 if pk_patient is not None:
815 emr = gmClinicalRecord.cClinicalRecord(aPKey = pk_patient, allow_user_interaction = False)
816 epi = emr.add_episode(episode_name = 'administration', is_open = False)
817 soap_cat = gmTools.bool2subst (
818 (self.__focussed_msg['category'] == u'clinical'),
819 u'U',
820 None
821 )
822 narr = _('Deleted inbox message:\n%s') % self.__focussed_msg.format(with_patient = False)
823 emr.add_clin_narrative(note = narr, soap_cat = soap_cat, episode = epi)
824 gmDispatcher.send(signal = 'statustext', msg = _('Recorded deletion of inbox message in EMR.'), beep = False)
825
826 if not self.provider.inbox.delete_message(self.__focussed_msg['pk_inbox_message']):
827 gmDispatcher.send(signal='statustext', msg=_('Problem removing message from Inbox.'))
828 return False
829 return True
830
832 if self.__focussed_msg['is_virtual']:
833 gmDispatcher.send(signal = 'statustext', msg = _('This message cannot be edited because it is virtual.'))
834 return False
835 edit_inbox_message(parent = self, message = self.__focussed_msg, single_entry = True)
836 return True
837
839 if self.__focussed_msg['pk_staff'] is None:
840 gmDispatcher.send(signal = 'statustext', msg = _('This message is already visible to all providers.'))
841 return False
842 print "now distributing"
843 return True
844
846
847 wx.BeginBusyCursor()
848
849 msg = _('There is a message about patient [%s].\n\n'
850 'However, I cannot find that\n'
851 'patient in the GNUmed database.'
852 ) % pk_patient
853
854 try:
855 pat = gmPerson.cIdentity(aPK_obj = pk_patient)
856 except gmExceptions.ConstructorError:
857 wx.EndBusyCursor()
858 _log.exception('patient [%s] not found', pk_patient)
859 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
860 return False
861 except:
862 wx.EndBusyCursor()
863 raise
864
865 success = set_active_patient(patient = pat)
866
867 wx.EndBusyCursor()
868
869 if not success:
870 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
871 return False
872
873 return True
874
876
877 msg = _('Supposedly there are unreviewed documents\n'
878 'for patient [%s]. However, I cannot find\n'
879 'that patient in the GNUmed database.'
880 ) % pk_patient
881
882 wx.BeginBusyCursor()
883
884 try:
885 pat = gmPerson.cIdentity(aPK_obj = pk_patient)
886 except gmExceptions.ConstructorError:
887 wx.EndBusyCursor()
888 _log.exception('patient [%s] not found', pk_patient)
889 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
890 return False
891
892 success = set_active_patient(patient = pat)
893
894 wx.EndBusyCursor()
895
896 if not success:
897 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
898 return False
899
900 wx.CallAfter(gmDispatcher.send, signal = 'display_widget', name = 'gmShowMedDocs', sort_mode = 'review')
901 return True
902
904
905 msg = _('Supposedly there are unreviewed results\n'
906 'for patient [%s]. However, I cannot find\n'
907 'that patient in the GNUmed database.'
908 ) % pk_patient
909
910 wx.BeginBusyCursor()
911
912 try:
913 pat = gmPerson.cIdentity(aPK_obj = pk_patient)
914 except gmExceptions.ConstructorError:
915 wx.EndBusyCursor()
916 _log.exception('patient [%s] not found', pk_patient)
917 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
918 return False
919
920 success = set_active_patient(patient = pat)
921
922 wx.EndBusyCursor()
923
924 if not success:
925 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
926 return False
927
928 wx.CallAfter(gmDispatcher.send, signal = 'display_widget', name = 'gmMeasurementsGridPlugin')
929 return True
930
959
961
962 if parent is None:
963 parent = wx.GetApp().GetTopWindow()
964
965 def get_tooltip(item):
966 if item is None:
967 return None
968 return item.format()
969
970 def switch_activation(item):
971 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Switching clinical hint activation'))
972 if conn is None:
973 return False
974 item['is_active'] = not item['is_active']
975 return item.save(conn = conn)
976
977 def manage_data_packs(item):
978 gmDataPackWidgets.manage_data_packs(parent = parent)
979 return True
980
981 def refresh(lctrl):
982 hints = gmProviderInbox.get_dynamic_hints(order_by = u'is_active DESC, source, hint')
983 items = [ [
984 gmTools.bool2subst(h['is_active'], gmTools.u_checkmark_thin, u''),
985 h['title'],
986 h['source'][:30],
987 h['hint'][:60],
988 gmTools.coalesce(h['url'], u'')[:60],
989 h['lang'],
990 h['pk']
991 ] for h in hints ]
992 lctrl.set_string_items(items)
993 lctrl.set_data(hints)
994
995 gmListWidgets.get_choices_from_list (
996 parent = parent,
997 msg = _('\nDynamic hints registered with GNUmed.\n'),
998 caption = _('Showing dynamic hints.'),
999 columns = [ _('Active'), _('Title'), _('Source'), _('Hint'), u'URL', _('Language'), u'#' ],
1000 single_selection = True,
1001 refresh_callback = refresh,
1002 left_extra_button = (
1003 _('(De)-Activate'),
1004 _('Switch activation of the selected hint'),
1005 switch_activation
1006 ),
1007 right_extra_button = (
1008 _('Data packs'),
1009 _('Browse and install clinical hints data packs'),
1010 manage_data_packs
1011 ),
1012 list_tooltip_callback = get_tooltip
1013 )
1014
1015
1016 if __name__ == '__main__':
1017
1018 if len(sys.argv) < 2:
1019 sys.exit()
1020
1021 if sys.argv[1] != 'test':
1022 sys.exit()
1023
1024 gmI18N.activate_locale()
1025 gmI18N.install_domain(domain = 'gnumed')
1026
1028 app = wx.PyWidgetTester(size = (800, 600))
1029 app.SetWidget(cProviderInboxPnl, -1)
1030 app.MainLoop()
1031
1033 app = wx.PyWidgetTester(size = (800, 600))
1034 app.SetWidget(cInboxMessageEAPnl, -1)
1035 app.MainLoop()
1036
1037
1038
1039 test_msg_ea()
1040
1041
1042