1 """GNUmed encounter related widgets.
2
3 This module contains widgets to manage encounters."""
4
5 __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
6 __license__ = "GPL v2 or later"
7
8
9 import sys
10 import time
11 import logging
12 import datetime as pydt
13
14
15
16 import wx
17
18
19
20 if __name__ == '__main__':
21 sys.path.insert(0, '../../')
22
23 from Gnumed.pycommon import gmCfg
24 from Gnumed.pycommon import gmDateTime
25 from Gnumed.pycommon import gmTools
26 from Gnumed.pycommon import gmDispatcher
27 from Gnumed.pycommon import gmMatchProvider
28
29 from Gnumed.business import gmEMRStructItems
30 from Gnumed.business import gmPraxis
31 from Gnumed.business import gmPerson
32
33 from Gnumed.wxpython import gmPhraseWheel
34 from Gnumed.wxpython import gmGuiHelpers
35 from Gnumed.wxpython import gmListWidgets
36 from Gnumed.wxpython import gmEditArea
37
38
39 _log = logging.getLogger('gm.ui')
40
41
42
43
52
53
55 """This is used as the callback when the EMR detects that the
56 patient was here rather recently and wants to ask the
57 provider whether to continue the recent encounter.
58 """
59 if parent is None:
60 parent = wx.GetApp().GetTopWindow()
61
62 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
63 parent = None,
64 id = -1,
65 caption = caption,
66 question = msg,
67 button_defs = [
68 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
69 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
70 ],
71 show_checkbox = False
72 )
73
74 result = dlg.ShowModal()
75 dlg.Destroy()
76
77 if result == wx.ID_YES:
78 return True
79
80 return False
81
82
84 if parent is None:
85 parent = wx.GetApp().GetTopWindow()
86
87
88 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter, msg = msg)
89 if dlg.ShowModal() == wx.ID_OK:
90 dlg.Destroy()
91 return True
92 dlg.Destroy()
93 return False
94
95
98
99 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False):
127
128 def edit(enc=None):
129 return edit_encounter(parent = parent, encounter = enc)
130
131 def edit_active(enc=None):
132 return edit_encounter(parent = parent, encounter = emr.active_encounter)
133
134 def start_new(enc=None):
135 start_new_encounter(emr = emr)
136 return True
137
138 def get_tooltip(data):
139 if data is None:
140 return None
141 return data.format (
142 patient = patient,
143 with_soap = False,
144 with_docs = False,
145 with_tests = False,
146 with_vaccinations = False,
147 with_rfe_aoe = True,
148 with_family_history = False,
149 by_episode=False,
150 fancy_header = True,
151 )
152
153 def refresh(lctrl):
154 if encounters is None:
155 encs = emr.get_encounters()
156 else:
157 encs = encounters
158
159 items = [
160 [
161 u'%s - %s' % (gmDateTime.pydt_strftime(e['started'], '%Y %b %d %H:%M'), e['last_affirmed'].strftime('%H:%M')),
162 e['l10n_type'],
163 gmTools.coalesce(e['praxis_branch'], u''),
164 gmTools.coalesce(e['reason_for_encounter'], u''),
165 gmTools.coalesce(e['assessment_of_encounter'], u''),
166 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
167 e['pk_encounter']
168 ] for e in encs
169 ]
170 lctrl.set_string_items(items = items)
171 lctrl.set_data(data = encs)
172 active_pk = emr.active_encounter['pk_encounter']
173 for idx in range(len(encs)):
174 e = encs[idx]
175 if e['pk_encounter'] == active_pk:
176 lctrl.SetItemTextColour(idx, col=wx.NamedColour('RED'))
177
178 return gmListWidgets.get_choices_from_list (
179 parent = parent,
180 msg = _("The patient's encounters.\n"),
181 caption = _('Encounters ...'),
182 columns = [_('When'), _('Type'), _('Where'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
183 can_return_empty = False,
184 single_selection = single_selection,
185 refresh_callback = refresh,
186 edit_callback = edit,
187 new_callback = new,
188 list_tooltip_callback = get_tooltip,
189 ignore_OK_button = ignore_OK_button,
190 left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
191 middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
192 )
193
194
196
198 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
199
200 cmd = u"""
201 SELECT DISTINCT ON (list_label)
202 pk_encounter
203 AS data,
204 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type || ' [#' || pk_encounter || ']'
205 AS list_label,
206 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
207 AS field_label
208 FROM
209 clin.v_pat_encounters
210 WHERE
211 (
212 to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
213 OR
214 l10n_type %(fragment_condition)s
215 OR
216 type %(fragment_condition)s
217 ) %(ctxt_patient)s
218 ORDER BY
219 list_label
220 LIMIT
221 30
222 """
223 context = {'ctxt_patient': {
224 'where_part': u'AND pk_patient = %(patient)s',
225 'placeholder': u'patient'
226 }}
227
228 self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
229 self.matcher._SQL_data2match = u"""
230 SELECT
231 pk_encounter
232 AS data,
233 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
234 AS list_label,
235 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
236 AS field_label
237 FROM
238 clin.v_pat_encounters
239 WHERE
240 pk_encounter = %(pk)s
241 """
242 self.matcher.setThresholds(1, 3, 5)
243
244 self.selection_only = True
245
246 self.set_context(context = 'patient', val = None)
247
254
265
266
267 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
268
270
272 try:
273 self.__encounter = kwargs['encounter']
274 del kwargs['encounter']
275 except KeyError:
276 self.__encounter = None
277
278 try:
279 msg = kwargs['msg']
280 del kwargs['msg']
281 except KeyError:
282 msg = None
283
284 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
285
286 self.refresh(msg = msg)
287
288
289
290 - def refresh(self, encounter=None, msg=None):
291
292 if msg is not None:
293 self._LBL_instructions.SetLabel(msg)
294
295 if encounter is not None:
296 self.__encounter = encounter
297
298 if self.__encounter is None:
299 return True
300
301
302
303 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
304 self._LBL_patient.SetLabel(pat.get_description_gender().strip())
305 curr_pat = gmPerson.gmCurrentPatient()
306 if curr_pat.connected:
307 if curr_pat.ID == self.__encounter['pk_patient']:
308 self._LBL_patient.SetForegroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT))
309 else:
310 self._LBL_patient.SetForegroundColour('red')
311
312 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data = self.__encounter['pk_type'])
313 self._PRW_location.Enable(True)
314 self._PRW_location.display_as_disabled(False)
315 branch = self.__encounter.praxis_branch
316 if branch is None:
317 unit = self.__encounter.org_unit
318 if unit is None:
319 self._PRW_location.SetText(u'', data = None)
320 else:
321 self._PRW_location.Enable(False)
322 self._PRW_location.display_as_disabled(True)
323 self._PRW_location.SetText(_('old praxis branch: %s (%s)') % (unit['unit'], unit['organization']), data = None)
324 else:
325 self._PRW_location.SetText(self.__encounter['praxis_branch'], data = branch['pk_praxis_branch'])
326
327 fts = gmDateTime.cFuzzyTimestamp (
328 timestamp = self.__encounter['started'],
329 accuracy = gmDateTime.acc_minutes
330 )
331 self._PRW_start.SetText(fts.format_accurately(), data=fts)
332
333 fts = gmDateTime.cFuzzyTimestamp (
334 timestamp = self.__encounter['last_affirmed'],
335 accuracy = gmDateTime.acc_minutes
336 )
337 self._PRW_end.SetText(fts.format_accurately(), data=fts)
338
339
340 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
341 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
342 self._PRW_rfe_codes.SetText(val, data)
343
344
345 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
346 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
347 self._PRW_aoe_codes.SetText(val, data)
348
349
350 if self.__encounter['last_affirmed'] == self.__encounter['started']:
351 self._PRW_end.SetFocus()
352 else:
353 self._TCTRL_aoe.SetFocus()
354
355 return True
356
358
359 if self._PRW_encounter_type.GetData() is None:
360 self._PRW_encounter_type.SetBackgroundColour('pink')
361 self._PRW_encounter_type.Refresh()
362 self._PRW_encounter_type.SetFocus()
363 return False
364 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
365 self._PRW_encounter_type.Refresh()
366
367
368 if self._PRW_start.GetValue().strip() == u'':
369 self._PRW_start.SetBackgroundColour('pink')
370 self._PRW_start.Refresh()
371 self._PRW_start.SetFocus()
372 return False
373 if not self._PRW_start.is_valid_timestamp(empty_is_valid = False):
374 self._PRW_start.SetBackgroundColour('pink')
375 self._PRW_start.Refresh()
376 self._PRW_start.SetFocus()
377 return False
378 self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
379 self._PRW_start.Refresh()
380
381
382
383
384
385
386
387 if not self._PRW_end.is_valid_timestamp(empty_is_valid = False):
388 self._PRW_end.SetBackgroundColour('pink')
389 self._PRW_end.Refresh()
390 self._PRW_end.SetFocus()
391 return False
392 self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
393 self._PRW_end.Refresh()
394
395 return True
396
398 if not self.__is_valid_for_save():
399 return False
400
401 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
402 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
403 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
404 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
405 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
406 self.__encounter.save_payload()
407
408 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
409 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
410
411 return True
412
413
414 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg
415
416
418
420 encounter = kwargs['encounter']
421 del kwargs['encounter']
422
423 try:
424 button_defs = kwargs['button_defs']
425 del kwargs['button_defs']
426 except KeyError:
427 button_defs = None
428
429 try:
430 msg = kwargs['msg']
431 del kwargs['msg']
432 except KeyError:
433 msg = None
434
435 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
436 self.SetSize((450, 280))
437 self.SetMinSize((450, 280))
438
439 if button_defs is not None:
440 self._BTN_save.SetLabel(button_defs[0][0])
441 self._BTN_save.SetToolTipString(button_defs[0][1])
442 self._BTN_close.SetLabel(button_defs[1][0])
443 self._BTN_close.SetToolTipString(button_defs[1][1])
444 self.Refresh()
445
446 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
447
448 self.Fit()
449
456
458 start = self._PRW_encounter_start.GetData()
459 if start is None:
460 return
461 start = start.get_pydt()
462
463 end = self._PRW_encounter_end.GetData()
464 if end is None:
465 fts = gmDateTime.cFuzzyTimestamp (
466 timestamp = start,
467 accuracy = gmDateTime.acc_minutes
468 )
469 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
470 return
471 end = end.get_pydt()
472
473 if start > end:
474 end = end.replace (
475 year = start.year,
476 month = start.month,
477 day = start.day
478 )
479 fts = gmDateTime.cFuzzyTimestamp (
480 timestamp = end,
481 accuracy = gmDateTime.acc_minutes
482 )
483 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
484 return
485
486 emr = self.__pat.get_emr()
487 if start != emr.active_encounter['started']:
488 end = end.replace (
489 year = start.year,
490 month = start.month,
491 day = start.day
492 )
493 fts = gmDateTime.cFuzzyTimestamp (
494 timestamp = end,
495 accuracy = gmDateTime.acc_minutes
496 )
497 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
498 return
499
500 return
501
502
503 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
504
506
511
513 self._TCTRL_encounter.SetValue(u'')
514 self._TCTRL_encounter.SetToolTipString(u'')
515 self._BTN_new.Enable(False)
516 self._BTN_list.Enable(False)
517
519 pat = gmPerson.gmCurrentPatient()
520 if not pat.connected:
521 self.clear()
522 return
523
524 enc = pat.get_emr().active_encounter
525 self._TCTRL_encounter.SetValue(enc.format (
526 with_docs = False,
527 with_tests = False,
528 fancy_header = False,
529 with_vaccinations = False,
530 with_family_history = False).strip('\n')
531 )
532 self._TCTRL_encounter.SetToolTipString (
533 _('The active encounter of the current patient:\n\n%s') % enc.format(
534 with_docs = False,
535 with_tests = False,
536 fancy_header = True,
537 with_vaccinations = False,
538 with_rfe_aoe = True,
539 with_family_history = False).strip('\n')
540 )
541 self._BTN_new.Enable(True)
542 self._BTN_list.Enable(True)
543
545 self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
546
547 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear)
548
549
550 gmDispatcher.connect(signal = u'clin.episode_mod_db', receiver = self._schedule_refresh)
551 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh)
552 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
553
554
555
557 wx.CallAfter(self.clear)
558
560 wx.CallAfter(self.refresh)
561 return True
562
568
574
579
580
581
582
592
593
595
596 if parent is None:
597 parent = wx.GetApp().GetTopWindow()
598
599
600 def edit(enc_type=None):
601 return edit_encounter_type(parent = parent, encounter_type = enc_type)
602
603 def delete(enc_type=None):
604 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
605 return True
606 gmDispatcher.send (
607 signal = u'statustext',
608 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
609 beep = True
610 )
611 return False
612
613 def refresh(lctrl):
614 enc_types = gmEMRStructItems.get_encounter_types()
615 lctrl.set_string_items(items = enc_types)
616
617 gmListWidgets.get_choices_from_list (
618 parent = parent,
619 msg = _('\nSelect the encounter type you want to edit !\n'),
620 caption = _('Managing encounter types ...'),
621 columns = [_('Local name'), _('Encounter type')],
622 single_selection = True,
623 edit_callback = edit,
624 new_callback = edit,
625 delete_callback = delete,
626 refresh_callback = refresh
627 )
628
629
630 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
631
633
638
639
640
641
642
672
685
695
697 self._TCTRL_l10n_name.SetValue(u'')
698 self._TCTRL_name.SetValue(u'')
699 self._TCTRL_name.Enable(True)
700
702 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
703 self._TCTRL_name.SetValue(self.data['description'])
704
705 self._TCTRL_name.Enable(False)
706
708 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
709 self._TCTRL_name.SetValue(self.data['description'])
710 self._TCTRL_name.Enable(True)
711
712
713
714
715
716
717
719 """Phrasewheel to allow selection of encounter type.
720
721 - user input interpreted as encounter type in English or local language
722 - data returned is pk of corresponding encounter type or None
723 """
725
726 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
727
728 mp = gmMatchProvider.cMatchProvider_SQL2 (
729 queries = [
730 u"""
731 SELECT
732 data,
733 field_label,
734 list_label
735 FROM (
736 SELECT DISTINCT ON (data) *
737 FROM (
738 SELECT
739 pk AS data,
740 _(description) AS field_label,
741 case
742 when _(description) = description then _(description)
743 else _(description) || ' (' || description || ')'
744 end AS list_label
745 FROM
746 clin.encounter_type
747 WHERE
748 _(description) %(fragment_condition)s
749 OR
750 description %(fragment_condition)s
751 ) AS q_distinct_pk
752 ) AS q_ordered
753 ORDER BY
754 list_label
755 """ ]
756 )
757 mp.setThresholds(2, 4, 6)
758
759 self.matcher = mp
760 self.selection_only = True
761 self.picklist_delay = 50
762
763
764
765
766 if __name__ == '__main__':
767
768 if len(sys.argv) < 2:
769 sys.exit()
770
771 if sys.argv[1] != 'test':
772 sys.exit()
773
774 from Gnumed.pycommon import gmI18N
775 gmI18N.activate_locale()
776 gmI18N.install_domain()
777
778
788
797
798
799
800
801
802
803
804
805