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
576
578
579 pat_id = None
580 if self._CHBOX_active_patient.GetValue() is True:
581 pat_id = gmPerson.gmCurrentPatient().ID
582 else:
583 if self._PRW_patient.person is not None:
584 pat_id = self._PRW_patient.person.ID
585
586 receiver = None
587 if self._CHBOX_send_to_me.IsChecked():
588 receiver = gmStaff.gmCurrentProvider()['pk_staff']
589 else:
590 if self._PRW_receiver.GetData() is not None:
591 receiver = self._PRW_receiver.GetData()
592
593 msg = gmProviderInbox.create_inbox_message (
594 patient = pat_id,
595 staff = receiver,
596 message_type = self._PRW_type.GetData(can_create = True),
597 subject = self._TCTRL_subject.GetValue().strip()
598 )
599
600 msg['data'] = self._TCTRL_message.GetValue().strip()
601
602 if self._PRW_due.is_valid_timestamp():
603 msg['due_date'] = self._PRW_due.date
604
605 if self._PRW_expiry.is_valid_timestamp():
606 msg['expiry_date'] = self._PRW_expiry.date
607
608 if self._RBTN_normal.GetValue() is True:
609 msg['importance'] = 0
610 elif self._RBTN_high.GetValue() is True:
611 msg['importance'] = 1
612 else:
613 msg['importance'] = -1
614
615 msg.save()
616 self.data = msg
617 return True
618
654
680
682 self._refresh_as_new()
683
737
738
739
741 if self._CHBOX_active_patient.IsChecked():
742 self._PRW_patient.Enable(False)
743 self._PRW_patient.person = None
744 else:
745 self._PRW_patient.Enable(True)
746
748 if self._CHBOX_send_to_me.IsChecked():
749 self._PRW_receiver.Enable(False)
750 self._PRW_receiver.SetData(data = gmStaff.gmCurrentProvider()['pk_staff'])
751 else:
752 self._PRW_receiver.Enable(True)
753 self._PRW_receiver.SetText(value = u'', data = None)
754
770
771 from Gnumed.wxGladeWidgets import wxgProviderInboxPnl
772
773 -class cProviderInboxPnl(wxgProviderInboxPnl.wxgProviderInboxPnl, gmRegetMixin.cRegetOnPaintMixin):
774
775 _item_handlers = {}
776
777
793
794
795
797 self.__populate_inbox()
798 return True
799
800
801
803 gmDispatcher.connect(signal = u'message_inbox_generic_mod_db', receiver = self._on_message_inbox_mod_db)
804 gmDispatcher.connect(signal = u'message_inbox_mod_db', receiver = self._on_message_inbox_mod_db)
805
806 gmDispatcher.connect(signal = u'reviewed_test_results_mod_db', receiver = self._on_message_inbox_mod_db)
807 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_message_inbox_mod_db)
808 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_message_inbox_mod_db)
809 gmDispatcher.connect(signal = u'doc_obj_review_mod_db', receiver = self._on_message_inbox_mod_db)
810 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
811
829
853
854
855
857 wx.CallAfter(self._schedule_data_reget)
858 wx.CallAfter(self._RBTN_active_patient.Enable)
859
861 wx.CallAfter(self._schedule_data_reget)
862 gmDispatcher.send(signal = u'request_user_attention', msg = _('Please check your GNUmed Inbox !'))
863
865 msg = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
866 if msg is None:
867 return
868
869 handler_key = '%s.%s' % (msg['category'], msg['type'])
870 try:
871 handle_item = cProviderInboxPnl._item_handlers[handler_key]
872 except KeyError:
873 if msg['pk_patient'] is None:
874 gmGuiHelpers.gm_show_warning (
875 _('No double-click action pre-programmed into\n'
876 'GNUmed for message category and type:\n'
877 '\n'
878 ' [%s]\n'
879 ) % handler_key,
880 _('handling provider inbox item')
881 )
882 return False
883 handle_item = self._goto_patient
884
885 if not handle_item(pk_context = msg['pk_context'], pk_patient = msg['pk_patient']):
886 _log.error('item handler returned <False>')
887 _log.error('handler key: [%s]', handler_key)
888 _log.error('message: %s', str(msg))
889 return False
890
891 return True
892
895
897 msg = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
898 if msg is None:
899 return
900
901 if msg['data'] is None:
902 tmp = _('Message: %s') % msg['comment']
903 else:
904 tmp = _('Message: %s\nData: %s') % (msg['comment'], msg['data'])
905
906 self._TXT_inbox_item_comment.SetValue(tmp)
907
909 tmp = self._LCTRL_provider_inbox.get_selected_item_data(only_one = True)
910 if tmp is None:
911 return
912 self.__focussed_msg = tmp
913
914
915 menu = wx.Menu(title = _('Inbox Message Actions:'))
916
917 if self.__focussed_msg['pk_patient'] is not None:
918 ID = wx.NewId()
919 menu.AppendItem(wx.MenuItem(menu, ID, _('Activate patient')))
920 wx.EVT_MENU(menu, ID, self._on_goto_patient)
921
922 if not self.__focussed_msg['is_virtual']:
923
924 ID = wx.NewId()
925 menu.AppendItem(wx.MenuItem(menu, ID, _('Delete')))
926 wx.EVT_MENU(menu, ID, self._on_delete_focussed_msg)
927
928 ID = wx.NewId()
929 menu.AppendItem(wx.MenuItem(menu, ID, _('Edit')))
930 wx.EVT_MENU(menu, ID, self._on_edit_focussed_msg)
931
932
933
934
935
936
937
938
939 self.PopupMenu(menu, wx.DefaultPosition)
940 menu.Destroy()
941
946
951
954
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
984
985
986
987
988
989
990
991
992
993
994
996 return self._goto_patient(pk_patient = self.__focussed_msg['pk_patient'])
997
999 if self.__focussed_msg['is_virtual']:
1000 gmDispatcher.send(signal = 'statustext', msg = _('You must deal with the reason for this message to remove it from your inbox.'), beep = True)
1001 return False
1002
1003 if not self.provider.inbox.delete_message(self.__focussed_msg['pk_inbox_message']):
1004 gmDispatcher.send(signal='statustext', msg=_('Problem removing message from Inbox.'))
1005 return False
1006 return True
1007
1009 if self.__focussed_msg['is_virtual']:
1010 gmDispatcher.send(signal = 'statustext', msg = _('This message cannot be edited because it is virtual.'))
1011 return False
1012 edit_inbox_message(parent = self, message = self.__focussed_msg, single_entry = True)
1013 return True
1014
1016 if self.__focussed_msg['pk_staff'] is None:
1017 gmDispatcher.send(signal = 'statustext', msg = _('This message is already visible to all providers.'))
1018 return False
1019 print "now distributing"
1020 return True
1021
1023
1024 wx.BeginBusyCursor()
1025
1026 msg = _('There is a message about patient [%s].\n\n'
1027 'However, I cannot find that\n'
1028 'patient in the GNUmed database.'
1029 ) % pk_patient
1030
1031 try:
1032 pat = gmPerson.cIdentity(aPK_obj = pk_patient)
1033 except gmExceptions.ConstructorError:
1034 wx.EndBusyCursor()
1035 _log.exception('patient [%s] not found', pk_patient)
1036 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
1037 return False
1038 except:
1039 wx.EndBusyCursor()
1040 raise
1041
1042 success = gmPatSearchWidgets.set_active_patient(patient = pat)
1043
1044 wx.EndBusyCursor()
1045
1046 if not success:
1047 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
1048 return False
1049
1050 return True
1051
1079
1081
1082 msg = _('Supposedly there are unreviewed results\n'
1083 'for patient [%s]. However, I cannot find\n'
1084 'that patient in the GNUmed database.'
1085 ) % pk_patient
1086
1087 wx.BeginBusyCursor()
1088
1089 try:
1090 pat = gmPerson.cIdentity(aPK_obj = pk_patient)
1091 except gmExceptions.ConstructorError:
1092 wx.EndBusyCursor()
1093 _log.exception('patient [%s] not found', pk_patient)
1094 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
1095 return False
1096
1097 success = gmPatSearchWidgets.set_active_patient(patient = pat)
1098
1099 wx.EndBusyCursor()
1100
1101 if not success:
1102 gmGuiHelpers.gm_show_error(msg, _('handling provider inbox item'))
1103 return False
1104
1105 wx.CallAfter(gmDispatcher.send, signal = 'display_widget', name = 'gmMeasurementsGridPlugin')
1106 return True
1107
1136
1138
1139 if parent is None:
1140 parent = wx.GetApp().GetTopWindow()
1141
1142 def get_tooltip(item):
1143 if item is None:
1144 return None
1145 return item.format()
1146
1147 def switch_activation(item):
1148 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Switching clinical hint activation'))
1149 if conn is None:
1150 return False
1151 item['is_active'] = not item['is_active']
1152 return item.save(conn = conn)
1153
1154 def manage_data_packs(item):
1155 gmDataPackWidgets.manage_data_packs(parent = parent)
1156 return True
1157
1158 def refresh(lctrl):
1159 hints = gmProviderInbox.get_dynamic_hints(order_by = u'is_active DESC, source, hint')
1160 items = [ [
1161 gmTools.bool2subst(h['is_active'], gmTools.u_checkmark_thin, u''),
1162 h['title'],
1163 h['source'][:30],
1164 h['hint'][:60],
1165 gmTools.coalesce(h['url'], u'')[:60],
1166 h['lang'],
1167 h['pk']
1168 ] for h in hints ]
1169 lctrl.set_string_items(items)
1170 lctrl.set_data(hints)
1171
1172 gmListWidgets.get_choices_from_list (
1173 parent = parent,
1174 msg = _('\nDynamic hints registered with GNUmed.\n'),
1175 caption = _('Showing dynamic hints.'),
1176 columns = [ _('Active'), _('Title'), _('Source'), _('Hint'), u'URL', _('Language'), u'#' ],
1177 single_selection = True,
1178 refresh_callback = refresh,
1179 left_extra_button = (
1180 _('(De)-Activate'),
1181 _('Switch activation of the selected hint'),
1182 switch_activation
1183 ),
1184 right_extra_button = (
1185 _('Data packs'),
1186 _('Browse and install clinical hints data packs'),
1187 manage_data_packs
1188 ),
1189 list_tooltip_callback = get_tooltip
1190 )
1191
1192
1193 if __name__ == '__main__':
1194
1195 if len(sys.argv) < 2:
1196 sys.exit()
1197
1198 if sys.argv[1] != 'test':
1199 sys.exit()
1200
1201 gmI18N.activate_locale()
1202 gmI18N.install_domain(domain = 'gnumed')
1203
1207
1209 app = wx.PyWidgetTester(size = (800, 600))
1210 app.SetWidget(cProviderInboxPnl, -1)
1211 app.MainLoop()
1212
1214 app = wx.PyWidgetTester(size = (800, 600))
1215 app.SetWidget(cInboxMessageEAPnl, -1)
1216 app.MainLoop()
1217
1218
1219
1220
1221 test_msg_ea()
1222
1223
1224