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 gmCfg
19 from Gnumed.pycommon import gmTools
20 from Gnumed.pycommon import gmDispatcher
21 from Gnumed.pycommon import gmMatchProvider
22 from Gnumed.pycommon import gmDateTime
23 from Gnumed.pycommon import gmNetworkTools
24
25 from Gnumed.business import gmPerson
26 from Gnumed.business import gmStaff
27 from Gnumed.business import gmSurgery
28 from Gnumed.business import gmProviderInbox
29
30 from Gnumed.wxpython import gmGuiHelpers
31 from Gnumed.wxpython import gmListWidgets
32 from Gnumed.wxpython import gmPlugin
33 from Gnumed.wxpython import gmRegetMixin
34 from Gnumed.wxpython import gmPhraseWheel
35 from Gnumed.wxpython import gmEditArea
36 from Gnumed.wxpython import gmAuthWidgets
37 from Gnumed.wxpython import gmPatSearchWidgets
38 from Gnumed.wxpython import gmVaccWidgets
39 from Gnumed.wxpython import gmCfgWidgets
40 from Gnumed.wxpython import gmDataPackWidgets
41
42
43 _log = logging.getLogger('gm.ui')
44
45 _indicator = {
46 -1: '',
47 0: '',
48 1: '*!!*'
49 }
50
87
100
101
102
104
105 if parent is None:
106 parent = wx.GetApp().GetTopWindow()
107
108 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('showing audit trail'))
109 if conn is None:
110 return False
111
112
113 def refresh(lctrl):
114 cmd = u'SELECT * FROM audit.v_audit_trail ORDER BY audit_when_ts'
115 rows, idx = gmPG2.run_ro_queries(link_obj = conn, queries = [{'cmd': cmd}], get_col_idx = False)
116 lctrl.set_string_items (
117 [ [
118 r['event_when'],
119 r['event_by'],
120 u'%s %s %s' % (
121 gmTools.coalesce(r['row_version_before'], gmTools.u_diameter),
122 gmTools.u_right_arrow,
123 gmTools.coalesce(r['row_version_after'], gmTools.u_diameter)
124 ),
125 r['event_table'],
126 r['event'],
127 r['pk_audit']
128 ] for r in rows ]
129 )
130
131 gmListWidgets.get_choices_from_list (
132 parent = parent,
133 msg = u'',
134 caption = _('GNUmed database audit log ...'),
135 columns = [ _('When'), _('Who'), _('Revisions'), _('Table'), _('Event'), '#' ],
136 single_selection = True,
137 refresh_callback = refresh
138 )
139
140
141
142
187
188 def edit(workplace=None):
189
190 dbcfg = gmCfg.cCfgSQL()
191
192 if workplace is None:
193 dlg = wx.TextEntryDialog (
194 parent = parent,
195 message = _('Enter a descriptive name for the new workplace:'),
196 caption = _('Configuring GNUmed workplaces ...'),
197 defaultValue = u'',
198 style = wx.OK | wx.CENTRE
199 )
200 dlg.ShowModal()
201 workplace = dlg.GetValue().strip()
202 if workplace == u'':
203 gmGuiHelpers.gm_show_error(_('Cannot save a new workplace without a name.'), _('Configuring GNUmed workplaces ...'))
204 return False
205 curr_plugins = []
206 else:
207 curr_plugins = gmTools.coalesce(dbcfg.get2 (
208 option = u'horstspace.notebook.plugin_load_order',
209 workplace = workplace,
210 bias = 'workplace'
211 ), []
212 )
213
214 msg = _(
215 'Pick the plugin(s) to be loaded the next time the client is restarted under the workplace:\n'
216 '\n'
217 ' [%s]\n'
218 ) % workplace
219
220 picker = gmListWidgets.cItemPickerDlg (
221 parent,
222 -1,
223 title = _('Configuring workplace plugins ...'),
224 msg = msg
225 )
226 picker.set_columns(['Available plugins'], ['Active plugins'])
227 available_plugins = gmPlugin.get_installed_plugins(plugin_dir = 'gui')
228 picker.set_choices(available_plugins)
229 picker.set_picks(picks = curr_plugins[:])
230 btn_pressed = picker.ShowModal()
231 if btn_pressed != wx.ID_OK:
232 picker.Destroy()
233 return False
234
235 new_plugins = picker.get_picks()
236 picker.Destroy()
237 if new_plugins == curr_plugins:
238 return True
239
240 if new_plugins is None:
241 return True
242
243 dbcfg.set (
244 option = u'horstspace.notebook.plugin_load_order',
245 value = new_plugins,
246 workplace = workplace
247 )
248
249 return True
250
251 def edit_old(workplace=None):
252
253 available_plugins = gmPlugin.get_installed_plugins(plugin_dir='gui')
254
255 dbcfg = gmCfg.cCfgSQL()
256
257 if workplace is None:
258 dlg = wx.TextEntryDialog (
259 parent = parent,
260 message = _('Enter a descriptive name for the new workplace:'),
261 caption = _('Configuring GNUmed workplaces ...'),
262 defaultValue = u'',
263 style = wx.OK | wx.CENTRE
264 )
265 dlg.ShowModal()
266 workplace = dlg.GetValue().strip()
267 if workplace == u'':
268 gmGuiHelpers.gm_show_error(_('Cannot save a new workplace without a name.'), _('Configuring GNUmed workplaces ...'))
269 return False
270 curr_plugins = []
271 choices = available_plugins
272 else:
273 curr_plugins = gmTools.coalesce(dbcfg.get2 (
274 option = u'horstspace.notebook.plugin_load_order',
275 workplace = workplace,
276 bias = 'workplace'
277 ), []
278 )
279 choices = curr_plugins[:]
280 for p in available_plugins:
281 if p not in choices:
282 choices.append(p)
283
284 sels = range(len(curr_plugins))
285 new_plugins = gmListWidgets.get_choices_from_list (
286 parent = parent,
287 msg = _(
288 '\n'
289 'Select the plugin(s) to be loaded the next time\n'
290 'the client is restarted under the workplace:\n'
291 '\n'
292 ' [%s]'
293 '\n'
294 ) % workplace,
295 caption = _('Configuring GNUmed workplaces ...'),
296 choices = choices,
297 selections = sels,
298 columns = [_('Plugins')],
299 single_selection = False
300 )
301
302 if new_plugins == curr_plugins:
303 return True
304
305 if new_plugins is None:
306 return True
307
308 dbcfg.set (
309 option = u'horstspace.notebook.plugin_load_order',
310 value = new_plugins,
311 workplace = workplace
312 )
313
314 return True
315
316 def clone(workplace=None):
317 if workplace is None:
318 return False
319
320 new_name = wx.GetTextFromUser (
321 message = _('Enter a name for the new workplace !'),
322 caption = _('Cloning workplace'),
323 default_value = u'%s-2' % workplace,
324 parent = parent
325 ).strip()
326
327 if new_name == u'':
328 return False
329
330 dbcfg = gmCfg.cCfgSQL()
331 opt = u'horstspace.notebook.plugin_load_order'
332
333 plugins = dbcfg.get2 (
334 option = opt,
335 workplace = workplace,
336 bias = 'workplace'
337 )
338
339 dbcfg.set (
340 option = opt,
341 value = plugins,
342 workplace = new_name
343 )
344
345
346
347 return True
348
349 def refresh(lctrl):
350 workplaces = gmSurgery.gmCurrentPractice().workplaces
351 curr_workplace = gmSurgery.gmCurrentPractice().active_workplace
352 try:
353 sels = [workplaces.index(curr_workplace)]
354 except ValueError:
355 sels = []
356
357 lctrl.set_string_items(workplaces)
358 lctrl.set_selections(selections = sels)
359
360 gmListWidgets.get_choices_from_list (
361 parent = parent,
362 msg = _(
363 '\nSelect the workplace to configure below.\n'
364 '\n'
365 'The currently active workplace is preselected.\n'
366 ),
367 caption = _('Configuring GNUmed workplaces ...'),
368 columns = [_('Workplace')],
369 single_selection = True,
370 refresh_callback = refresh,
371 edit_callback = edit,
372 new_callback = edit,
373 delete_callback = delete,
374 left_extra_button = (_('Clone'), _('Clone the selected workplace'), clone)
375 )
376
378
380
381 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
382
383 query = u"""
384 SELECT DISTINCT ON (label)
385 pk_type,
386 (l10n_type || ' (' || l10n_category || ')')
387 AS label
388 FROM
389 dem.v_inbox_item_type
390 WHERE
391 l10n_type %(fragment_condition)s
392 OR
393 type %(fragment_condition)s
394 OR
395 l10n_category %(fragment_condition)s
396 OR
397 category %(fragment_condition)s
398 ORDER BY label
399 LIMIT 50"""
400
401 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
402 mp.setThresholds(1, 2, 4)
403 self.matcher = mp
404 self.SetToolTipString(_('Select a message type.'))
405
418
420 wx.CallAfter(__display_clinical_reminders)
421
422 gmDispatcher.connect(signal = u'post_patient_selection', receiver = _display_clinical_reminders)
423
425 pat = gmPerson.gmCurrentPatient()
426 if not pat.connected:
427 return
428 for msg in pat.due_messages:
429 if msg['expiry_date'] is None:
430 exp = u''
431 else:
432 exp = _(' - expires %s') % gmDateTime.pydt_strftime (
433 msg['expiry_date'],
434 '%Y %b %d',
435 accuracy = gmDateTime.acc_days
436 )
437 txt = _(
438 'Due for %s (since %s%s):\n'
439 '%s'
440 '%s'
441 '\n'
442 'Patient: %s\n'
443 'Reminder by: %s'
444 ) % (
445 gmDateTime.format_interval_medically(msg['interval_due']),
446 gmDateTime.pydt_strftime(msg['due_date'], '%Y %b %d', accuracy = gmDateTime.acc_days),
447 exp,
448 gmTools.coalesce(msg['comment'], u'', u'\n%s\n'),
449 gmTools.coalesce(msg['data'], u'', u'\n%s\n'),
450 pat['description_gender'],
451 msg['modified_by']
452 )
453 gmGuiHelpers.gm_show_warning (
454 aTitle = _('Clinical reminder'),
455 aMessage = txt
456 )
457 for hint in pat.dynamic_hints:
458 txt = u'%s\n\n%s\n\n %s' % (
459 hint['title'],
460 gmTools.wrap(hint['hint'], width = 50, initial_indent = u' ', subsequent_indent = u' '),
461 hint['source']
462 )
463 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
464 None,
465 -1,
466 caption = _('Clinical hint'),
467 question = txt,
468 button_defs = [
469 {'label': _('OK'), 'tooltip': _('OK'), 'default': True},
470 {'label': _('More info'), 'tooltip': _('Go to [%s]') % hint['url']}
471 ]
472 )
473 button = dlg.ShowModal()
474 dlg.Destroy()
475 if button == wx.ID_NO:
476 gmNetworkTools.open_url_in_browser(hint['url'], autoraise = False)
477
478 return
479
480 from Gnumed.wxGladeWidgets import wxgInboxMessageEAPnl
481
482 -class cInboxMessageEAPnl(wxgInboxMessageEAPnl.wxgInboxMessageEAPnl, gmEditArea.cGenericEditAreaMixin):
483
503
509
510
511
565
567
568 pat_id = None
569 if self._CHBOX_active_patient.GetValue() is True:
570 pat_id = gmPerson.gmCurrentPatient().ID
571 else:
572 if self._PRW_patient.person is not None:
573 pat_id = self._PRW_patient.person.ID
574
575 receiver = None
576 if self._CHBOX_send_to_me.IsChecked():
577 receiver = gmStaff.gmCurrentProvider()['pk_staff']
578 else:
579 if self._PRW_receiver.GetData() is not None:
580 receiver = self._PRW_receiver.GetData()
581
582 msg = gmProviderInbox.create_inbox_message (
583 patient = pat_id,
584 staff = receiver,
585 message_type = self._PRW_type.GetData(can_create = True),
586 subject = self._TCTRL_subject.GetValue().strip()
587 )
588
589 msg['data'] = self._TCTRL_message.GetValue().strip()
590
591 if self._PRW_due.is_valid_timestamp():
592 msg['due_date'] = self._PRW_due.date
593
594 if self._PRW_expiry.is_valid_timestamp():
595 msg['expiry_date'] = self._PRW_expiry.date
596
597 if self._RBTN_normal.GetValue() is True:
598 msg['importance'] = 0
599 elif self._RBTN_high.GetValue() is True:
600 msg['importance'] = 1
601 else:
602 msg['importance'] = -1
603
604 msg.save()
605 self.data = msg
606 return True
607
643
669
671 self._refresh_as_new()
672
726
727
728
730 if self._CHBOX_active_patient.IsChecked():
731 self._PRW_patient.Enable(False)
732 self._PRW_patient.person = None
733 else:
734 self._PRW_patient.Enable(True)
735
737 if self._CHBOX_send_to_me.IsChecked():
738 self._PRW_receiver.Enable(False)
739 self._PRW_receiver.SetData(data = gmStaff.gmCurrentProvider()['pk_staff'])
740 else:
741 self._PRW_receiver.Enable(True)
742 self._PRW_receiver.SetText(value = u'', data = None)
743
759
760 from Gnumed.wxGladeWidgets import wxgProviderInboxPnl
761
762 -class cProviderInboxPnl(wxgProviderInboxPnl.wxgProviderInboxPnl, gmRegetMixin.cRegetOnPaintMixin):
763
764 _item_handlers = {}
765
766
782
783
784
786 self.__populate_inbox()
787 return True
788
789
790
792 gmDispatcher.connect(signal = u'message_inbox_generic_mod_db', receiver = self._on_message_inbox_mod_db)
793 gmDispatcher.connect(signal = u'message_inbox_mod_db', receiver = self._on_message_inbox_mod_db)
794
795 gmDispatcher.connect(signal = u'reviewed_test_results_mod_db', receiver = self._on_message_inbox_mod_db)
796 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_message_inbox_mod_db)
797 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_message_inbox_mod_db)
798 gmDispatcher.connect(signal = u'doc_obj_review_mod_db', receiver = self._on_message_inbox_mod_db)
799 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
800
818
842
843
844
846 wx.CallAfter(self._schedule_data_reget)
847 wx.CallAfter(self._RBTN_active_patient.Enable)
848
850 wx.CallAfter(self._schedule_data_reget)
851 gmDispatcher.send(signal = u'request_user_attention', msg = _('Please check your GNUmed Inbox !'))
852
854 msg = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
855 if msg is None:
856 return
857
858 handler_key = '%s.%s' % (msg['category'], msg['type'])
859 try:
860 handle_item = cProviderInboxPnl._item_handlers[handler_key]
861 except KeyError:
862 if msg['pk_patient'] is None:
863 gmGuiHelpers.gm_show_warning (
864 _('No double-click action pre-programmed into\n'
865 'GNUmed for message category and type:\n'
866 '\n'
867 ' [%s]\n'
868 ) % handler_key,
869 _('handling provider inbox item')
870 )
871 return False
872 handle_item = self._goto_patient
873
874 if not handle_item(pk_context = msg['pk_context'], pk_patient = msg['pk_patient']):
875 _log.error('item handler returned <False>')
876 _log.error('handler key: [%s]', handler_key)
877 _log.error('message: %s', str(msg))
878 return False
879
880 return True
881
884
886 msg = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
887 if msg is None:
888 return
889
890 if msg['data'] is None:
891 tmp = _('Message: %s') % msg['comment']
892 else:
893 tmp = _('Message: %s\nData: %s') % (msg['comment'], msg['data'])
894
895 self._TXT_inbox_item_comment.SetValue(tmp)
896
898 tmp = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
899 if tmp is None:
900 return
901 self.__focussed_msg = tmp
902
903
904 menu = wx.Menu(title = _('Inbox Message Actions:'))
905
906 if self.__focussed_msg['pk_patient'] is not None:
907 ID = wx.NewId()
908 menu.AppendItem(wx.MenuItem(menu, ID, _('Activate patient')))
909 wx.EVT_MENU(menu, ID, self._on_goto_patient)
910
911 if not self.__focussed_msg['is_virtual']:
912
913 ID = wx.NewId()
914 menu.AppendItem(wx.MenuItem(menu, ID, _('Delete')))
915 wx.EVT_MENU(menu, ID, self._on_delete_focussed_msg)
916
917 ID = wx.NewId()
918 menu.AppendItem(wx.MenuItem(menu, ID, _('Edit')))
919 wx.EVT_MENU(menu, ID, self._on_edit_focussed_msg)
920
921
922
923
924
925
926
927
928 self.PopupMenu(menu, wx.DefaultPosition)
929 menu.Destroy()
930
935
940
943
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
985 return self._goto_patient(pk_patient = self.__focussed_msg['pk_patient'])
986
988 if self.__focussed_msg['is_virtual']:
989 gmDispatcher.send(signal = 'statustext', msg = _('You must deal with the reason for this message to remove it from your inbox.'), beep = True)
990 return False
991
992 if not self.provider.inbox.delete_message(self.__focussed_msg['pk_inbox_message']):
993 gmDispatcher.send(signal='statustext', msg=_('Problem removing message from Inbox.'))
994 return False
995 return True
996
998 if self.__focussed_msg['is_virtual']:
999 gmDispatcher.send(signal = 'statustext', msg = _('This message cannot be edited because it is virtual.'))
1000 return False
1001 edit_inbox_message(parent = self, message = self.__focussed_msg, single_entry = True)
1002 return True
1003
1005 if self.__focussed_msg['pk_staff'] is None:
1006 gmDispatcher.send(signal = 'statustext', msg = _('This message is already visible to all providers.'))
1007 return False
1008 print "now distributing"
1009 return True
1010
1012
1013 wx.BeginBusyCursor()
1014
1015 msg = _('There is a message about patient [%s].\n\n'
1016 'However, I cannot find that\n'
1017 'patient in the GNUmed database.'
1018 ) % pk_patient
1019
1020 try:
1021 pat = gmPerson.cIdentity(aPK_obj = pk_patient)
1022 except gmExceptions.ConstructorError:
1023 wx.EndBusyCursor()
1024 _log.exception('patient [%s] not found', pk_patient)
1025 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
1026 return False
1027 except:
1028 wx.EndBusyCursor()
1029 raise
1030
1031 success = gmPatSearchWidgets.set_active_patient(patient = pat)
1032
1033 wx.EndBusyCursor()
1034
1035 if not success:
1036 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
1037 return False
1038
1039 return True
1040
1068
1070
1071 msg = _('Supposedly there are unreviewed results\n'
1072 'for patient [%s]. However, I cannot find\n'
1073 'that patient in the GNUmed database.'
1074 ) % pk_patient
1075
1076 wx.BeginBusyCursor()
1077
1078 try:
1079 pat = gmPerson.cIdentity(aPK_obj = pk_patient)
1080 except gmExceptions.ConstructorError:
1081 wx.EndBusyCursor()
1082 _log.exception('patient [%s] not found', pk_patient)
1083 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
1084 return False
1085
1086 success = gmPatSearchWidgets.set_active_patient(patient = pat)
1087
1088 wx.EndBusyCursor()
1089
1090 if not success:
1091 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
1092 return False
1093
1094 wx.CallAfter(gmDispatcher.send, signal = 'display_widget', name = 'gmMeasurementsGridPlugin')
1095 return True
1096
1125
1127
1128 if parent is None:
1129 parent = wx.GetApp().GetTopWindow()
1130
1131 def get_tooltip(item):
1132 if item is None:
1133 return None
1134 return item.format()
1135
1136 def switch_activation(item):
1137 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Switching clinical hint activation'))
1138 if conn is None:
1139 return False
1140 item['is_active'] = not item['is_active']
1141 return item.save(conn = conn)
1142
1143 def manage_data_packs(item):
1144 gmDataPackWidgets.manage_data_packs(parent = parent)
1145 return True
1146
1147 def refresh(lctrl):
1148 hints = gmProviderInbox.get_dynamic_hints(order_by = u'is_active DESC, source, hint')
1149 items = [ [
1150 gmTools.bool2subst(h['is_active'], gmTools.u_checkmark_thin, u''),
1151 h['title'],
1152 h['source'][:30],
1153 h['hint'][:60],
1154 gmTools.coalesce(h['url'], u'')[:60],
1155 h['lang'],
1156 h['pk']
1157 ] for h in hints ]
1158 lctrl.set_string_items(items)
1159 lctrl.set_data(hints)
1160
1161 gmListWidgets.get_choices_from_list (
1162 parent = parent,
1163 msg = _('\nDynamic hints registered with GNUmed.\n'),
1164 caption = _('Showing dynamic hints.'),
1165 columns = [ _('Active'), _('Title'), _('Source'), _('Hint'), u'URL', _('Language'), u'#' ],
1166 single_selection = True,
1167 refresh_callback = refresh,
1168 left_extra_button = (
1169 _('(De)-Activate'),
1170 _('Switch activation of the selected hint'),
1171 switch_activation
1172 ),
1173 right_extra_button = (
1174 _('Data packs'),
1175 _('Browse and install clinical hints data packs'),
1176 manage_data_packs
1177 ),
1178 list_tooltip_callback = get_tooltip
1179 )
1180
1181
1182 if __name__ == '__main__':
1183
1184 if len(sys.argv) < 2:
1185 sys.exit()
1186
1187 if sys.argv[1] != 'test':
1188 sys.exit()
1189
1190 gmI18N.activate_locale()
1191 gmI18N.install_domain(domain = 'gnumed')
1192
1196
1198 app = wx.PyWidgetTester(size = (800, 600))
1199 app.SetWidget(cProviderInboxPnl, -1)
1200 app.MainLoop()
1201
1203 app = wx.PyWidgetTester(size = (800, 600))
1204 app.SetWidget(cInboxMessageEAPnl, -1)
1205 app.MainLoop()
1206
1207
1208
1209
1210 test_msg_ea()
1211
1212
1213