Package Gnumed :: Package wxpython :: Module gmEMRStructWidgets
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmEMRStructWidgets

   1  """GNUmed EMR structure editors 
   2   
   3          This module contains widgets to create and edit EMR structural 
   4          elements (issues, enconters, episodes). 
   5   
   6          This is based on initial work and ideas by Syan <kittylitter@swiftdsl.com.au> 
   7          and Karsten <Karsten.Hilbert@gmx.net>. 
   8  """ 
   9  #================================================================ 
  10  # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmEMRStructWidgets.py,v $ 
  11  # $Id: gmEMRStructWidgets.py,v 1.114 2010/02/06 21:01:27 ncq Exp $ 
  12  __version__ = "$Revision: 1.114 $" 
  13  __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net" 
  14  __license__ = "GPL" 
  15   
  16  # stdlib 
  17  import sys, re, datetime as pydt, logging, time 
  18   
  19   
  20  # 3rd party 
  21  import wx 
  22  import wx.lib.pubsub as wxps 
  23   
  24   
  25  # GNUmed 
  26  if __name__ == '__main__': 
  27          sys.path.insert(0, '../../') 
  28  from Gnumed.pycommon import gmI18N, gmMatchProvider, gmDispatcher, gmTools, gmDateTime, gmCfg, gmExceptions 
  29  from Gnumed.business import gmEMRStructItems, gmPerson, gmSOAPimporter, gmSurgery 
  30  from Gnumed.wxpython import gmPhraseWheel, gmGuiHelpers, gmListWidgets, gmEditArea, gmPatSearchWidgets 
  31  from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg, wxgMoveNarrativeDlg 
  32  from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl 
  33  from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl, wxgEncounterEditAreaDlg 
  34  from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl 
  35  from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl 
  36   
  37   
  38  _log = logging.getLogger('gm.ui') 
  39  _log.info(__version__) 
  40  #================================================================ 
  41  # performed procedure related widgets/functions 
  42  #---------------------------------------------------------------- 
43 -def manage_performed_procedures(parent=None):
44 45 pat = gmPerson.gmCurrentPatient() 46 emr = pat.get_emr() 47 48 if parent is None: 49 parent = wx.GetApp().GetTopWindow() 50 #----------------------------------------- 51 def edit(procedure=None): 52 return edit_procedure(parent = parent, procedure = procedure)
53 #----------------------------------------- 54 def delete(procedure=None): 55 if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']): 56 return True 57 58 gmDispatcher.send ( 59 signal = u'statustext', 60 msg = _('Cannot delete performed procedure.'), 61 beep = True 62 ) 63 return False 64 #----------------------------------------- 65 def refresh(lctrl): 66 procs = emr.get_performed_procedures() 67 68 items = [ 69 [ 70 p['clin_when'].strftime('%Y-%m-%d'), 71 p['clin_where'], 72 p['episode'], 73 p['performed_procedure'] 74 ] for p in procs 75 ] 76 lctrl.set_string_items(items = items) 77 lctrl.set_data(data = procs) 78 #----------------------------------------- 79 gmListWidgets.get_choices_from_list ( 80 parent = parent, 81 msg = _('\nSelect the procedure you want to edit !\n'), 82 caption = _('Editing performed procedures ...'), 83 columns = [_('When'), _('Where'), _('Episode'), _('Procedure')], 84 single_selection = True, 85 edit_callback = edit, 86 new_callback = edit, 87 delete_callback = delete, 88 refresh_callback = refresh 89 ) 90 #---------------------------------------------------------------- 91 from Gnumed.wxGladeWidgets import wxgProcedureEAPnl 92
93 -def edit_procedure(parent=None, procedure=None):
94 ea = cProcedureEAPnl(parent = parent, id = -1) 95 ea.data = procedure 96 ea.mode = gmTools.coalesce(procedure, 'new', 'edit') 97 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 98 dlg.SetTitle(gmTools.coalesce(procedure, _('Adding a procedure'), _('Editing a procedure'))) 99 if dlg.ShowModal() == wx.ID_OK: 100 dlg.Destroy() 101 return True 102 dlg.Destroy() 103 return False
104 #----------------------------------------------------------------
105 -class cProcedureEAPnl(wxgProcedureEAPnl.wxgProcedureEAPnl, gmEditArea.cGenericEditAreaMixin):
106
107 - def __init__(self, *args, **kwargs):
108 wxgProcedureEAPnl.wxgProcedureEAPnl.__init__(self, *args, **kwargs) 109 gmEditArea.cGenericEditAreaMixin.__init__(self) 110 111 self.mode = 'new' 112 self.data = None 113 114 self.__init_ui()
115 #----------------------------------------------------------------
116 - def __init_ui(self):
117 self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus) 118 self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus) 119 120 # location 121 mp = gmMatchProvider.cMatchProvider_SQL2 ( 122 queries = [ 123 u""" 124 select distinct on (clin_where) clin_where, clin_where 125 from clin.procedure 126 where clin_where %(fragment_condition)s 127 order by clin_where 128 limit 25 129 """ ] 130 ) 131 mp.setThresholds(2, 4, 6) 132 self._PRW_location.matcher = mp 133 134 # procedure 135 mp = gmMatchProvider.cMatchProvider_SQL2 ( 136 queries = [ 137 u""" 138 select distinct on (narrative) narrative, narrative 139 from clin.procedure 140 where narrative %(fragment_condition)s 141 order by narrative 142 limit 25 143 """ ] 144 ) 145 mp.setThresholds(2, 4, 6) 146 self._PRW_procedure.matcher = mp
147 #----------------------------------------------------------------
149 if self._PRW_hospital_stay.GetData() is None: 150 self._PRW_hospital_stay.SetText() 151 self._PRW_episode.Enable(True) 152 else: 153 self._PRW_location.SetText() 154 self._PRW_episode.SetText() 155 self._PRW_episode.Enable(False)
156 #----------------------------------------------------------------
157 - def _on_location_lost_focus(self):
158 if self._PRW_location.GetValue().strip() == u'': 159 return 160 161 self._PRW_hospital_stay.SetText() 162 self._PRW_episode.Enable(True)
163 #---------------------------------------------------------------- 164 # generic Edit Area mixin API 165 #----------------------------------------------------------------
166 - def _valid_for_save(self):
167 168 has_errors = False 169 170 if not self._DPRW_date.is_valid_timestamp(): 171 self._DPRW_date.display_as_valid(False) 172 has_errors = True 173 else: 174 self._DPRW_date.display_as_valid(True) 175 176 if self._PRW_hospital_stay.GetData() is None: 177 if self._PRW_episode.GetData() is None: 178 self._PRW_episode.display_as_valid(False) 179 has_errors = True 180 else: 181 self._PRW_episode.display_as_valid(True) 182 else: 183 self._PRW_episode.display_as_valid(True) 184 185 if (self._PRW_procedure.GetValue() is None) or (self._PRW_procedure.GetValue().strip() == u''): 186 self._PRW_procedure.display_as_valid(False) 187 has_errors = True 188 else: 189 self._PRW_procedure.display_as_valid(True) 190 191 invalid_location = ( 192 (self._PRW_hospital_stay.GetData() is None) and (self._PRW_location.GetValue().strip() == u'') 193 or 194 (self._PRW_hospital_stay.GetData() is not None) and (self._PRW_location.GetValue().strip() != u'') 195 ) 196 if invalid_location: 197 self._PRW_hospital_stay.display_as_valid(False) 198 self._PRW_location.display_as_valid(False) 199 has_errors = True 200 else: 201 self._PRW_hospital_stay.display_as_valid(True) 202 self._PRW_location.display_as_valid(True) 203 204 wxps.Publisher().sendMessage ( 205 topic = 'statustext', 206 data = {'msg': _('Cannot save procedure.'), 'beep': True} 207 ) 208 209 return (has_errors is False)
210 #----------------------------------------------------------------
211 - def _save_as_new(self):
212 213 pat = gmPerson.gmCurrentPatient() 214 emr = pat.get_emr() 215 216 if self._PRW_hospital_stay.GetData() is None: 217 epi = self._PRW_episode.GetData() 218 else: 219 stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData()) 220 epi = stay['pk_episode'] 221 222 proc = emr.add_performed_procedure ( 223 episode = epi, 224 location = self._PRW_location.GetValue().strip(), 225 hospital_stay = self._PRW_hospital_stay.GetData(), 226 procedure = self._PRW_procedure.GetValue().strip() 227 ) 228 proc['clin_when'] = self._DPRW_date.data.get_pydt() 229 proc.save() 230 231 self.data = proc 232 233 return True
234 #----------------------------------------------------------------
235 - def _save_as_update(self):
236 self.data['clin_when'] = self._DPRW_date.data.get_pydt() 237 238 if self._PRW_hospital_stay.GetData() is None: 239 self.data['pk_hospital_stay'] = None 240 self.data['clin_where'] = self._PRW_location.GetValue().strip() 241 self.data['pk_episode'] = self._PRW_episode.GetData() 242 else: 243 self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData() 244 self.data['clin_where'] = None 245 stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData()) 246 self.data['pk_episode'] = stay['pk_episode'] 247 248 self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip() 249 250 self.data.save() 251 return True
252 #----------------------------------------------------------------
253 - def _refresh_as_new(self):
254 self._DPRW_date.SetText() 255 self._PRW_hospital_stay.SetText() 256 self._PRW_location.SetText() 257 self._PRW_episode.SetText() 258 self._PRW_procedure.SetText() 259 260 self._DPRW_date.SetFocus()
261 #----------------------------------------------------------------
262 - def _refresh_from_existing(self):
263 self._DPRW_date.SetData(data = self.data['clin_when']) 264 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode']) 265 self._PRW_procedure.SetText(value = self.data['performed_procedure'], data = self.data['performed_procedure']) 266 267 if self.data['pk_hospital_stay'] is None: 268 self._PRW_hospital_stay.SetText() 269 self._PRW_location.SetText(value = self.data['clin_where'], data = self.data['clin_where']) 270 else: 271 self._PRW_hospital_stay.SetText(value = self.data['clin_where'], data = self.data['pk_hospital_stay']) 272 self._PRW_location.SetText() 273 274 self._DPRW_date.SetFocus()
275 #----------------------------------------------------------------
277 self._refresh_as_new() 278 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode']) 279 if self.data['pk_hospital_stay'] is None: 280 self._PRW_hospital_stay.SetText() 281 self._PRW_location.SetText(value = self.data['clin_where'], data = self.data['clin_where']) 282 else: 283 self._PRW_hospital_stay.SetText(value = self.data['clin_where'], data = self.data['pk_hospital_stay']) 284 self._PRW_location.SetText() 285 286 self._DPRW_date.SetFocus()
287 #----------------------------------------------------------------
289 edit_hospital_stay(parent = self.GetParent()) 290 evt.Skip()
291 #================================================================ 292 # hospital stay related widgets/functions 293 #----------------------------------------------------------------
294 -def manage_hospital_stays(parent=None):
295 296 pat = gmPerson.gmCurrentPatient() 297 emr = pat.get_emr() 298 299 if parent is None: 300 parent = wx.GetApp().GetTopWindow() 301 #----------------------------------------- 302 def edit(stay=None): 303 return edit_hospital_stay(parent = parent, hospital_stay = stay)
304 #----------------------------------------- 305 def delete(stay=None): 306 if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']): 307 return True 308 gmDispatcher.send ( 309 signal = u'statustext', 310 msg = _('Cannot delete hospital stay.'), 311 beep = True 312 ) 313 return False 314 #----------------------------------------- 315 def refresh(lctrl): 316 stays = emr.get_hospital_stays() 317 items = [ 318 [ 319 s['admission'].strftime('%Y-%m-%d'), 320 gmTools.coalesce(s['discharge'], u''), 321 s['episode'], 322 gmTools.coalesce(s['hospital'], u'') 323 ] for s in stays 324 ] 325 lctrl.set_string_items(items = items) 326 lctrl.set_data(data = stays) 327 #----------------------------------------- 328 gmListWidgets.get_choices_from_list ( 329 parent = parent, 330 msg = _('\nSelect the hospital stay you want to edit !\n'), 331 caption = _('Editing hospital stays ...'), 332 columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')], 333 single_selection = True, 334 edit_callback = edit, 335 new_callback = edit, 336 delete_callback = delete, 337 refresh_callback = refresh 338 ) 339 340 #----------------------------------------------------------------
341 -def edit_hospital_stay(parent=None, hospital_stay=None):
342 ea = cHospitalStayEditAreaPnl(parent = parent, id = -1) 343 ea.data = hospital_stay 344 ea.mode = gmTools.coalesce(hospital_stay, 'new', 'edit') 345 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 346 dlg.SetTitle(gmTools.coalesce(hospital_stay, _('Adding a hospital stay'), _('Editing a hospital stay'))) 347 if dlg.ShowModal() == wx.ID_OK: 348 dlg.Destroy() 349 return True 350 dlg.Destroy() 351 return False
352 #----------------------------------------------------------------
353 -class cHospitalStayPhraseWheel(gmPhraseWheel.cPhraseWheel):
354 """Phrasewheel to allow selection of a hospital stay. 355 """
356 - def __init__(self, *args, **kwargs):
357 358 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs) 359 360 ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}} 361 362 mp = gmMatchProvider.cMatchProvider_SQL2 ( 363 queries = [ 364 u""" 365 select 366 pk_hospital_stay, 367 descr 368 from ( 369 select distinct on (pk_hospital_stay) 370 pk_hospital_stay, 371 descr 372 from 373 (select 374 pk_hospital_stay, 375 ( 376 to_char(admission, 'YYYY-Mon-DD') 377 || coalesce((' (' || hospital || '):'), ': ') 378 || episode 379 || coalesce((' (' || health_issue || ')'), '') 380 ) as descr 381 from 382 clin.v_pat_hospital_stays 383 where 384 %(ctxt_pat)s 385 386 hospital %(fragment_condition)s 387 or 388 episode %(fragment_condition)s 389 or 390 health_issue %(fragment_condition)s 391 ) as the_stays 392 ) as distinct_stays 393 order by descr 394 limit 25 395 """ ], 396 context = ctxt 397 ) 398 mp.setThresholds(3, 4, 6) 399 mp.set_context('pat', gmPerson.gmCurrentPatient().ID) 400 401 self.matcher = mp 402 self.selection_only = True
403 #---------------------------------------------------------------- 404 from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl 405
406 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
407
408 - def __init__(self, *args, **kwargs):
409 wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl.__init__(self, *args, **kwargs) 410 gmEditArea.cGenericEditAreaMixin.__init__(self)
411 #---------------------------------------------------------------- 412 # generic Edit Area mixin API 413 #----------------------------------------------------------------
414 - def _valid_for_save(self):
415 if not self._DP_admission.GetValue().IsValid(): 416 self._DP_admission.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 417 wxps.Publisher().sendMessage ( 418 topic = 'statustext', 419 data = {'msg': _('Missing admission data. Cannot save hospital stay.'), 'beep': True} 420 ) 421 return False 422 else: 423 self._DP_admission.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 424 425 if self._DP_discharge.GetValue().IsValid(): 426 if not self._DP_discharge.GetValue().IsLaterThan(self._DP_admission.GetValue()): 427 self._DP_discharge.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 428 wxps.Publisher().sendMessage ( 429 topic = 'statustext', 430 data = {'msg': _('Discharge date must be empty or later than admission. Cannot save hospital stay.'), 'beep': True} 431 ) 432 return False 433 434 return True
435 #----------------------------------------------------------------
436 - def _save_as_new(self):
437 438 pat = gmPerson.gmCurrentPatient() 439 emr = pat.get_emr() 440 441 stay = gmEMRStructItems.create_hospital_stay ( 442 encounter = emr.active_encounter['pk_encounter'], 443 episode = self._PRW_episode.GetData(can_create = True) 444 ) 445 stay['hospital'] = gmTools.none_if(self._PRW_hospital.GetValue().strip(), u'') 446 stay['admission'] = gmDateTime.wxDate2py_dt(wxDate = self._DP_admission.GetValue()) 447 if self._DP_discharge.GetValue().IsValid(): 448 stay['discharge'] = gmDateTime.wxDate2py_dt(wxDate = self._DP_discharge.GetValue()) 449 stay.save_payload() 450 451 self.data = stay 452 return True
453 #----------------------------------------------------------------
454 - def _save_as_update(self):
455 456 self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True) 457 self.data['hospital'] = gmTools.none_if(self._PRW_hospital.GetValue().strip(), u'') 458 self.data['admission'] = gmDateTime.wxDate2py_dt(wxDate = self._DP_admission.GetValue()) 459 if self._DP_discharge.GetValue().IsValid(): 460 self.data['discharge'] = gmDateTime.wxDate2py_dt(wxDate = self._DP_discharge.GetValue()) 461 self.data.save_payload() 462 463 return True
464 #----------------------------------------------------------------
465 - def _refresh_as_new(self):
466 self._PRW_hospital.SetText(value = u'') 467 self._PRW_episode.SetText(value = u'') 468 self._DP_admission.SetValue(dt = wx.DateTime.UNow())
469 #self._DP_discharge.SetValue(dt = None) 470 #----------------------------------------------------------------
471 - def _refresh_from_existing(self):
472 if self.data['hospital'] is not None: 473 self._PRW_hospital.SetText(value = self.data['hospital']) 474 475 if self.data['pk_episode'] is not None: 476 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode']) 477 478 self._DP_admission.SetValue(gmDateTime.py_dt2wxDate(py_dt = self.data['admission'], wx = wx)) 479 480 if self.data['discharge'] is not None: 481 self._DP_discharge.SetValue(gmDateTime.py_dt2wxDate(py_dt = self.data['discharge'], wx = wx))
482 #----------------------------------------------------------------
484 print "this was not expected to be used in this edit area"
485 #================================================================ 486 # encounter related widgets/functions 487 #----------------------------------------------------------------
488 -def start_new_encounter(emr=None):
489 emr.start_new_encounter() 490 gmDispatcher.send(signal = 'statustext', msg = _('Started a new encounter for the active patient.'), beep = True) 491 time.sleep(0.5) 492 gmGuiHelpers.gm_show_info ( 493 _('\nA new encounter was started for the active patient.\n'), 494 _('Start of new encounter') 495 )
496 #----------------------------------------------------------------
497 -def edit_encounter(parent=None, encounter=None):
498 499 if parent is None: 500 parent = wx.GetApp().GetTopWindow() 501 502 # FIXME: use generic dialog 2 503 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter) 504 dlg.ShowModal()
505 #----------------------------------------------------------------
506 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None):
507 508 if patient is None: 509 patient = gmPerson.gmCurrentPatient() 510 511 if not patient.connected: 512 gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.')) 513 return False 514 515 if parent is None: 516 parent = wx.GetApp().GetTopWindow() 517 518 emr = patient.get_emr() 519 520 #-------------------- 521 def refresh(lctrl): 522 if encounters is not None: 523 encs = encounters 524 else: 525 encs = emr.get_encounters() 526 527 items = [ 528 [ 529 e['started'].strftime('%x %H:%M'), 530 e['last_affirmed'].strftime('%H:%M'), 531 e['l10n_type'], 532 gmTools.coalesce(e['reason_for_encounter'], u''), 533 gmTools.coalesce(e['assessment_of_encounter'], u''), 534 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin), 535 e['pk_encounter'] 536 ] for e in encs 537 ] 538 539 lctrl.set_string_items(items = items) 540 lctrl.set_data(data = encs)
541 #-------------------- 542 def edit(enc = None): 543 return edit_encounter(parent = parent, encounter = enc) 544 #-------------------- 545 return gmListWidgets.get_choices_from_list ( 546 parent = parent, 547 msg = _('\nBelow find the relevant encounters of the patient.\n'), 548 caption = _('Encounters ...'), 549 columns = [_('Started'), _('Ended'), _('Type'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'], 550 can_return_empty = True, 551 single_selection = single_selection, 552 refresh_callback = refresh, 553 edit_callback = edit 554 ) 555 #----------------------------------------------------------------
556 -def ask_for_encounter_continuation(msg=None, caption=None, encounter=None, parent=None):
557 558 if parent is None: 559 parent = wx.GetApp().GetTopWindow() 560 561 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 562 parent = None, 563 id = -1, 564 caption = caption, 565 question = msg, 566 button_defs = [ 567 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False}, 568 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True} 569 ], 570 show_checkbox = False 571 ) 572 573 result = dlg.ShowModal() 574 dlg.Destroy() 575 576 if result == wx.ID_YES: 577 return True 578 579 return False
580 #----------------------------------------------------------------
581 -def manage_encounter_types(parent=None):
582 583 if parent is None: 584 parent = wx.GetApp().GetTopWindow() 585 586 #-------------------- 587 def edit(enc_type=None): 588 return edit_encounter_type(parent = parent, encounter_type = enc_type)
589 #-------------------- 590 def delete(enc_type=None): 591 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']): 592 return True 593 gmDispatcher.send ( 594 signal = u'statustext', 595 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'], 596 beep = True 597 ) 598 return False 599 #-------------------- 600 def refresh(lctrl): 601 enc_types = gmEMRStructItems.get_encounter_types() 602 lctrl.set_string_items(items = enc_types) 603 #-------------------- 604 gmListWidgets.get_choices_from_list ( 605 parent = parent, 606 msg = _('\nSelect the encounter type you want to edit !\n'), 607 caption = _('Managing encounter types ...'), 608 columns = [_('Local name'), _('Encounter type')], 609 single_selection = True, 610 edit_callback = edit, 611 new_callback = edit, 612 delete_callback = delete, 613 refresh_callback = refresh 614 ) 615 #----------------------------------------------------------------
616 -def edit_encounter_type(parent=None, encounter_type=None):
617 ea = cEncounterTypeEditAreaPnl(parent = parent, id = -1) 618 ea.data = encounter_type 619 ea.mode = gmTools.coalesce(encounter_type, 'new', 'edit') 620 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea) 621 dlg.SetTitle(gmTools.coalesce(encounter_type, _('Adding new encounter type'), _('Editing local encounter type name'))) 622 if dlg.ShowModal() == wx.ID_OK: 623 return True 624 return False
625 #----------------------------------------------------------------
626 -class cEncounterTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
627 """Phrasewheel to allow selection of encounter type. 628 629 - user input interpreted as encounter type in English or local language 630 - data returned is pk of corresponding encounter type or None 631 """
632 - def __init__(self, *args, **kwargs):
633 634 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs) 635 636 mp = gmMatchProvider.cMatchProvider_SQL2 ( 637 queries = [ 638 u""" 639 select pk, l10n_description from ( 640 select distinct on (pk) * from ( 641 (select 642 pk, 643 _(description) as l10n_description, 644 1 as rank 645 from 646 clin.encounter_type 647 where 648 _(description) %(fragment_condition)s 649 650 ) union all ( 651 652 select 653 pk, 654 _(description) as l10n_description, 655 2 as rank 656 from 657 clin.encounter_type 658 where 659 description %(fragment_condition)s 660 ) 661 ) as q_distinct_pk 662 ) as q_ordered order by rank, l10n_description 663 """ ] 664 ) 665 mp.setThresholds(2, 4, 6) 666 667 self.matcher = mp 668 self.selection_only = True 669 self.picklist_delay = 50
670 #----------------------------------------------------------------
671 -class cEncounterTypeEditAreaPnl(wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
672
673 - def __init__(self, *args, **kwargs):
674 675 wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl.__init__(self, *args, **kwargs) 676 gmEditArea.cGenericEditAreaMixin.__init__(self)
677 678 # self.__register_interests() 679 #------------------------------------------------------- 680 # generic edit area API 681 #-------------------------------------------------------
682 - def _valid_for_save(self):
683 if self.mode == 'edit': 684 if self._TCTRL_l10n_name.GetValue().strip() == u'': 685 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False) 686 return False 687 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True) 688 return True 689 690 no_errors = True 691 692 if self._TCTRL_l10n_name.GetValue().strip() == u'': 693 if self._TCTRL_name.GetValue().strip() == u'': 694 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False) 695 no_errors = False 696 else: 697 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True) 698 else: 699 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True) 700 701 if self._TCTRL_name.GetValue().strip() == u'': 702 if self._TCTRL_l10n_name.GetValue().strip() == u'': 703 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = False) 704 no_errors = False 705 else: 706 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True) 707 else: 708 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True) 709 710 return no_errors
711 #-------------------------------------------------------
712 - def _save_as_new(self):
713 enc_type = gmEMRStructItems.create_encounter_type ( 714 description = gmTools.none_if(self._TCTRL_name.GetValue().strip(), u''), 715 l10n_description = gmTools.coalesce ( 716 gmTools.none_if(self._TCTRL_l10n_name.GetValue().strip(), u''), 717 self._TCTRL_name.GetValue().strip() 718 ) 719 ) 720 if enc_type is None: 721 return False 722 self.data = enc_type 723 return True
724 #-------------------------------------------------------
725 - def _save_as_update(self):
726 enc_type = gmEMRStructItems.update_encounter_type ( 727 description = self._TCTRL_name.GetValue().strip(), 728 l10n_description = self._TCTRL_l10n_name.GetValue().strip() 729 ) 730 if enc_type is None: 731 return False 732 self.data = enc_type 733 return True
734 #-------------------------------------------------------
735 - def _refresh_as_new(self):
736 self._TCTRL_l10n_name.SetValue(u'') 737 self._TCTRL_name.SetValue(u'') 738 self._TCTRL_name.Enable(True)
739 #-------------------------------------------------------
740 - def _refresh_from_existing(self):
741 self._TCTRL_l10n_name.SetValue(self.data['l10n_description']) 742 self._TCTRL_name.SetValue(self.data['description']) 743 # disallow changing type on all encounters by editing system name 744 self._TCTRL_name.Enable(False)
745 #-------------------------------------------------------
747 self._TCTRL_l10n_name.SetValue(self.data['l10n_description']) 748 self._TCTRL_name.SetValue(self.data['description']) 749 self._TCTRL_name.Enable(True)
750 #------------------------------------------------------- 751 # internal API 752 #------------------------------------------------------- 753 # def __register_interests(self): 754 # return 755 #----------------------------------------------------------------
756 -class cEncounterEditAreaPnl(wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl):
757
758 - def __init__(self, *args, **kwargs):
759 try: 760 self.__encounter = kwargs['encounter'] 761 del kwargs['encounter'] 762 except KeyError: 763 self.__encounter = None 764 765 try: 766 msg = kwargs['msg'] 767 del kwargs['msg'] 768 except KeyError: 769 msg = None 770 771 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs) 772 773 self.refresh(msg = msg)
774 #-------------------------------------------------------- 775 # external API 776 #--------------------------------------------------------
777 - def refresh(self, encounter=None, msg=None):
778 779 if msg is not None: 780 self._LBL_instructions.SetLabel(msg) 781 782 if encounter is not None: 783 self.__encounter = encounter 784 785 if self.__encounter is None: 786 return True 787 788 # getting the patient via the encounter allows us to act 789 # on any encounter regardless of the currently active patient 790 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient']) 791 self._LBL_patient.SetLabel(pat.get_description_gender()) 792 793 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data=self.__encounter['pk_type']) 794 795 fts = gmDateTime.cFuzzyTimestamp ( 796 timestamp = self.__encounter['started'], 797 accuracy = gmDateTime.acc_minutes 798 ) 799 self._PRW_start.SetText(fts.format_accurately(), data=fts) 800 801 fts = gmDateTime.cFuzzyTimestamp ( 802 timestamp = self.__encounter['last_affirmed'], 803 accuracy = gmDateTime.acc_minutes 804 ) 805 self._PRW_end.SetText(fts.format_accurately(), data=fts) 806 807 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], '')) 808 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], '')) 809 810 if self.__encounter['last_affirmed'] == self.__encounter['started']: 811 self._PRW_end.SetFocus() 812 else: 813 self._TCTRL_aoe.SetFocus() 814 815 return True
816 #--------------------------------------------------------
817 - def __is_valid_for_save(self):
818 819 if self._PRW_encounter_type.GetData() is None: 820 self._PRW_encounter_type.SetBackgroundColour('pink') 821 self._PRW_encounter_type.Refresh() 822 self._PRW_encounter_type.SetFocus() 823 return False 824 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 825 self._PRW_encounter_type.Refresh() 826 827 if not self._PRW_start.is_valid_timestamp(): 828 self._PRW_start.SetFocus() 829 return False 830 831 if not self._PRW_end.is_valid_timestamp(): 832 self._PRW_end.SetFocus() 833 return False 834 835 return True
836 #--------------------------------------------------------
837 - def save(self):
838 if not self.__is_valid_for_save(): 839 return False 840 841 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData() 842 self.__encounter['started'] = self._PRW_start.GetData().get_pydt() 843 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt() 844 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 845 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'') 846 self.__encounter.save_payload() # FIXME: error checking 847 848 return True
849 #---------------------------------------------------------------- 850 # FIXME: use generic dialog 2
851 -class cEncounterEditAreaDlg(wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg):
852
853 - def __init__(self, *args, **kwargs):
854 encounter = kwargs['encounter'] 855 del kwargs['encounter'] 856 857 try: 858 button_defs = kwargs['button_defs'] 859 del kwargs['button_defs'] 860 except KeyError: 861 button_defs = None 862 863 try: 864 msg = kwargs['msg'] 865 del kwargs['msg'] 866 except KeyError: 867 msg = None 868 869 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs) 870 self.SetSize((450, 280)) 871 self.SetMinSize((450, 280)) 872 873 if button_defs is not None: 874 self._BTN_save.SetLabel(button_defs[0][0]) 875 self._BTN_save.SetToolTipString(button_defs[0][1]) 876 self._BTN_close.SetLabel(button_defs[1][0]) 877 self._BTN_close.SetToolTipString(button_defs[1][1]) 878 self.Refresh() 879 880 self._PNL_edit_area.refresh(encounter = encounter, msg = msg) 881 882 self.Fit()
883 #--------------------------------------------------------
884 - def _on_save_button_pressed(self, evt):
885 if self._PNL_edit_area.save(): 886 if self.IsModal(): 887 self.EndModal(wx.ID_OK) 888 else: 889 self.Close()
890 #================================================================ 891 # episode related widgets/functions 892 #----------------------------------------------------------------
893 -def edit_episode(parent=None, episode=None):
894 ea = cEpisodeEditAreaPnl(parent = parent, id = -1) 895 ea.data = episode 896 ea.mode = gmTools.coalesce(episode, 'new', 'edit') 897 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 898 dlg.SetTitle(gmTools.coalesce(episode, _('Adding a new episode'), _('Editing an episode'))) 899 if dlg.ShowModal() == wx.ID_OK: 900 return True 901 return False
902 #----------------------------------------------------------------
903 -def promote_episode_to_issue(parent=None, episode=None, emr=None):
904 905 created_new_issue = False 906 907 try: 908 issue = gmEMRStructItems.cHealthIssue(name = episode['description'], patient = episode['pk_patient']) 909 except gmExceptions.NoSuchBusinessObjectError: 910 issue = None 911 912 if issue is None: 913 issue = emr.add_health_issue(issue_name = episode['description']) 914 created_new_issue = True 915 else: 916 # issue exists already, so ask user 917 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 918 parent, 919 -1, 920 caption = _('Promoting episode to health issue'), 921 question = _( 922 'There already is a health issue\n' 923 '\n' 924 ' %s\n' 925 '\n' 926 'What do you want to do ?' 927 ) % issue['description'], 928 button_defs = [ 929 {'label': _('Use existing'), 'tooltip': _('Move episode into existing health issue'), 'default': False}, 930 {'label': _('Create new'), 'tooltip': _('Create a new health issue with another name'), 'default': True} 931 ] 932 ) 933 use_existing = dlg.ShowModal() 934 dlg.Destroy() 935 936 if use_existing == wx.ID_CANCEL: 937 return 938 939 # user wants to create new issue with alternate name 940 if use_existing == wx.ID_NO: 941 # loop until name modified but non-empty or cancelled 942 issue_name = episode['description'] 943 while issue_name == episode['description']: 944 dlg = wx.TextEntryDialog ( 945 parent = parent, 946 message = _('Enter a short descriptive name for the new health issue:'), 947 caption = _('Creating a new health issue ...'), 948 defaultValue = issue_name, 949 style = wx.OK | wx.CANCEL | wx.CENTRE 950 ) 951 decision = dlg.ShowModal() 952 if decision != wx.ID_OK: 953 dlg.Destroy() 954 return 955 issue_name = dlg.GetValue().strip() 956 dlg.Destroy() 957 if issue_name == u'': 958 issue_name = episode['description'] 959 960 issue = emr.add_health_issue(issue_name = issue_name) 961 created_new_issue = True 962 963 # eventually move the episode to the issue 964 if not move_episode_to_issue(episode = episode, target_issue = issue, save_to_backend = True): 965 # user cancelled the move so delete just-created issue 966 if created_new_issue: 967 # shouldn't fail as it is completely new 968 gmEMRStructItems.delete_health_issue(health_issue = issue) 969 return 970 971 return
972 #----------------------------------------------------------------
973 -def move_episode_to_issue(episode=None, target_issue=None, save_to_backend=False):
974 """Prepare changing health issue for an episode. 975 976 Checks for two-open-episodes conflict. When this 977 function succeeds, the pk_health_issue has been set 978 on the episode instance and the episode should - for 979 all practical purposes - be ready for save_payload(). 980 """ 981 # episode is closed: should always work 982 if not episode['episode_open']: 983 episode['pk_health_issue'] = target_issue['pk_health_issue'] 984 if save_to_backend: 985 episode.save_payload() 986 return True 987 988 # un-associate: should always work, too 989 if target_issue is None: 990 episode['pk_health_issue'] = None 991 if save_to_backend: 992 episode.save_payload() 993 return True 994 995 # try closing possibly expired episode on target issue if any 996 db_cfg = gmCfg.cCfgSQL() 997 epi_ttl = int(db_cfg.get2 ( 998 option = u'episode.ttl', 999 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1000 bias = 'user', 1001 default = 60 # 2 months 1002 )) 1003 if target_issue.close_expired_episode(ttl=epi_ttl) is True: 1004 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description'])) 1005 existing_epi = target_issue.get_open_episode() 1006 1007 # no more open episode on target issue: should work now 1008 if existing_epi is None: 1009 episode['pk_health_issue'] = target_issue['pk_health_issue'] 1010 if save_to_backend: 1011 episode.save_payload() 1012 return True 1013 1014 # don't conflict on SELF ;-) 1015 if existing_epi['pk_episode'] == episode['pk_episode']: 1016 episode['pk_health_issue'] = target_issue['pk_health_issue'] 1017 if save_to_backend: 1018 episode.save_payload() 1019 return True 1020 1021 # we got two open episodes at once, ask user 1022 move_range = episode.get_access_range() 1023 exist_range = existing_epi.get_access_range() 1024 question = _( 1025 'You want to associate the running episode:\n\n' 1026 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n' 1027 'with the health issue:\n\n' 1028 ' "%(issue_name)s"\n\n' 1029 'There already is another episode running\n' 1030 'for this health issue:\n\n' 1031 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n' 1032 'However, there can only be one running\n' 1033 'episode per health issue.\n\n' 1034 'Which episode do you want to close ?' 1035 ) % { 1036 'new_epi_name': episode['description'], 1037 'new_epi_start': move_range[0].strftime('%m/%y'), 1038 'new_epi_end': move_range[1].strftime('%m/%y'), 1039 'issue_name': target_issue['description'], 1040 'old_epi_name': existing_epi['description'], 1041 'old_epi_start': exist_range[0].strftime('%m/%y'), 1042 'old_epi_end': exist_range[1].strftime('%m/%y') 1043 } 1044 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 1045 parent = None, 1046 id = -1, 1047 caption = _('Resolving two-running-episodes conflict'), 1048 question = question, 1049 button_defs = [ 1050 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']}, 1051 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']} 1052 ] 1053 ) 1054 decision = dlg.ShowModal() 1055 1056 if decision == wx.ID_CANCEL: 1057 # button 3: move cancelled by user 1058 return False 1059 1060 elif decision == wx.ID_YES: 1061 # button 1: close old episode 1062 existing_epi['episode_open'] = False 1063 existing_epi.save_payload() 1064 1065 elif decision == wx.ID_NO: 1066 # button 2: close new episode 1067 episode['episode_open'] = False 1068 1069 else: 1070 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision) 1071 1072 episode['pk_health_issue'] = target_issue['pk_health_issue'] 1073 if save_to_backend: 1074 episode.save_payload() 1075 return True
1076 #----------------------------------------------------------------
1077 -class cEpisodeListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
1078 1079 # FIXME: support pre-selection 1080
1081 - def __init__(self, *args, **kwargs):
1082 1083 episodes = kwargs['episodes'] 1084 del kwargs['episodes'] 1085 1086 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs) 1087 1088 self.SetTitle(_('Select the episodes you are interested in ...')) 1089 self._LCTRL_items.set_columns([_('Episode'), _('Status'), _('Health Issue')]) 1090 self._LCTRL_items.set_string_items ( 1091 items = [ 1092 [ epi['description'], 1093 gmTools.bool2str(epi['episode_open'], _('ongoing'), u''), 1094 gmTools.coalesce(epi['health_issue'], u'') 1095 ] 1096 for epi in episodes ] 1097 ) 1098 self._LCTRL_items.set_column_widths() 1099 self._LCTRL_items.set_data(data = episodes)
1100 #----------------------------------------------------------------
1101 -class cEpisodeDescriptionPhraseWheel(gmPhraseWheel.cPhraseWheel):
1102 """Let user select an episode *description*. 1103 1104 The user can select an episode description from the previously 1105 used descriptions across all episodes across all patients. 1106 1107 Selection is done with a phrasewheel so the user can 1108 type the episode name and matches will be shown. Typing 1109 "*" will show the entire list of episodes. 1110 1111 If the user types a description not existing yet a 1112 new episode description will be returned. 1113 """
1114 - def __init__(self, *args, **kwargs):
1115 1116 mp = gmMatchProvider.cMatchProvider_SQL2 ( 1117 queries = [u""" 1118 select distinct on (description) description, description, 1 1119 from clin.episode 1120 where description %(fragment_condition)s 1121 order by description 1122 limit 30""" 1123 ] 1124 ) 1125 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1126 self.matcher = mp
1127 #----------------------------------------------------------------
1128 -class cEpisodeSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
1129 """Let user select an episode. 1130 1131 The user can select an episode from the existing episodes of a 1132 patient. Selection is done with a phrasewheel so the user 1133 can type the episode name and matches will be shown. Typing 1134 "*" will show the entire list of episodes. Closed episodes 1135 will be marked as such. If the user types an episode name not 1136 in the list of existing episodes a new episode can be created 1137 from it if the programmer activated that feature. 1138 1139 If keyword <patient_id> is set to None or left out the control 1140 will listen to patient change signals and therefore act on 1141 gmPerson.gmCurrentPatient() changes. 1142 """
1143 - def __init__(self, *args, **kwargs):
1144 1145 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}} 1146 1147 mp = gmMatchProvider.cMatchProvider_SQL2 ( 1148 queries = [ 1149 u"""( 1150 1151 select 1152 pk_episode, 1153 coalesce ( 1154 description || ' - ' || health_issue, 1155 description 1156 ) as description, 1157 1 as rank 1158 from 1159 clin.v_pat_episodes 1160 where 1161 episode_open is true and 1162 description %(fragment_condition)s 1163 %(ctxt_pat)s 1164 1165 ) union all ( 1166 1167 select 1168 pk_episode, 1169 coalesce ( 1170 description || _(' (closed)') || ' - ' || health_issue, 1171 description || _(' (closed)') 1172 ) as description, 1173 2 as rank 1174 from 1175 clin.v_pat_episodes 1176 where 1177 description %(fragment_condition)s and 1178 episode_open is false 1179 %(ctxt_pat)s 1180 1181 ) 1182 order by rank, description 1183 limit 30""" 1184 ], 1185 context = ctxt 1186 ) 1187 1188 try: 1189 kwargs['patient_id'] 1190 except KeyError: 1191 kwargs['patient_id'] = None 1192 1193 if kwargs['patient_id'] is None: 1194 self.use_current_patient = True 1195 self.__register_patient_change_signals() 1196 pat = gmPerson.gmCurrentPatient() 1197 if pat.connected: 1198 mp.set_context('pat', pat.ID) 1199 else: 1200 self.use_current_patient = False 1201 self.__patient_id = int(kwargs['patient_id']) 1202 mp.set_context('pat', self.__patient_id) 1203 1204 del kwargs['patient_id'] 1205 1206 gmPhraseWheel.cPhraseWheel.__init__ ( 1207 self, 1208 *args, 1209 **kwargs 1210 ) 1211 self.matcher = mp
1212 #-------------------------------------------------------- 1213 # external API 1214 #--------------------------------------------------------
1215 - def set_patient(self, patient_id=None):
1216 if self.use_current_patient: 1217 return False 1218 self.__patient_id = int(patient_id) 1219 self.set_context('pat', self.__patient_id) 1220 return True
1221 #--------------------------------------------------------
1222 - def GetData(self, can_create=False, is_open=False):
1223 if self.data is None: 1224 if can_create: 1225 epi_name = self.GetValue().strip() 1226 if epi_name == u'': 1227 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True) 1228 _log.debug('cannot create episode without name') 1229 else: 1230 if self.use_current_patient: 1231 pat = gmPerson.gmCurrentPatient() 1232 else: 1233 pat = gmPerson.cPatient(aPK_obj=self.__patient_id) 1234 1235 emr = pat.get_emr() 1236 epi = emr.add_episode(episode_name = epi_name, is_open = is_open) 1237 if epi is None: 1238 self.data = None 1239 else: 1240 self.data = epi['pk_episode'] 1241 1242 return gmPhraseWheel.cPhraseWheel.GetData(self)
1243 #-------------------------------------------------------- 1244 # internal API 1245 #--------------------------------------------------------
1247 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection') 1248 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
1249 #--------------------------------------------------------
1250 - def _pre_patient_selection(self):
1251 return True
1252 #--------------------------------------------------------
1253 - def _post_patient_selection(self):
1254 if self.use_current_patient: 1255 patient = gmPerson.gmCurrentPatient() 1256 self.set_context('pat', patient.ID) 1257 return True
1258 #----------------------------------------------------------------
1259 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1260
1261 - def __init__(self, *args, **kwargs):
1262 1263 try: 1264 episode = kwargs['episode'] 1265 del kwargs['episode'] 1266 except KeyError: 1267 episode = None 1268 1269 wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl.__init__(self, *args, **kwargs) 1270 gmEditArea.cGenericEditAreaMixin.__init__(self) 1271 1272 self.data = episode
1273 #---------------------------------------------------------------- 1274 # generic Edit Area mixin API 1275 #----------------------------------------------------------------
1276 - def _valid_for_save(self):
1277 1278 errors = False 1279 1280 if len(self._PRW_description.GetValue().strip()) == 0: 1281 errors = True 1282 self._PRW_description.display_as_valid(False) 1283 self._PRW_description.SetFocus() 1284 else: 1285 self._PRW_description.display_as_valid(True) 1286 self._PRW_description.Refresh() 1287 1288 return not errors
1289 #----------------------------------------------------------------
1290 - def _save_as_new(self):
1291 1292 pat = gmPerson.gmCurrentPatient() 1293 emr = pat.get_emr() 1294 1295 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip()) 1296 epi['episode_open'] = not self._CHBOX_closed.IsChecked() 1297 epi['diagnostic_certainty_classification'] = self._PRW_classification.GetData() 1298 1299 issue_name = self._PRW_issue.GetValue().strip() 1300 if len(issue_name) != 0: 1301 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True) 1302 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue']) 1303 1304 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False): 1305 gmDispatcher.send ( 1306 signal = 'statustext', 1307 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % ( 1308 epi['description'], 1309 issue['description'] 1310 ) 1311 ) 1312 gmEMRStructItems.delete_episode(episode = epi) 1313 return False 1314 1315 epi.save() 1316 1317 self.data = epi 1318 return True
1319 #----------------------------------------------------------------
1320 - def _save_as_update(self):
1321 1322 self.data['description'] = self._PRW_description.GetValue().strip() 1323 self.data['episode_open'] = not self._CHBOX_closed.IsChecked() 1324 self.data['diagnostic_certainty_classification'] = self._PRW_classification.GetData() 1325 1326 issue_name = self._PRW_issue.GetValue().strip() 1327 if len(issue_name) != 0: 1328 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True) 1329 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue']) 1330 1331 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False): 1332 gmDispatcher.send ( 1333 signal = 'statustext', 1334 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % ( 1335 self.data['description'], 1336 issue['description'] 1337 ) 1338 ) 1339 return False 1340 1341 self.data.save() 1342 return True
1343 #----------------------------------------------------------------
1344 - def _refresh_as_new(self):
1345 if self.data is None: 1346 ident = gmPerson.gmCurrentPatient() 1347 else: 1348 ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient']) 1349 self._TCTRL_patient.SetValue(ident.get_description_gender()) 1350 self._PRW_issue.SetText() 1351 self._PRW_description.SetText() 1352 self._PRW_classification.SetText() 1353 self._CHBOX_closed.SetValue(False)
1354 #----------------------------------------------------------------
1355 - def _refresh_from_existing(self):
1356 ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient']) 1357 self._TCTRL_patient.SetValue(ident.get_description_gender()) 1358 1359 if self.data['pk_health_issue'] is not None: 1360 self._PRW_issue.SetText(self.data['health_issue'], data=self.data['pk_health_issue']) 1361 1362 self._PRW_description.SetText(self.data['description'], data=self.data['description']) 1363 1364 if self.data['diagnostic_certainty_classification'] is not None: 1365 self._PRW_classification.SetData(data = self.data['diagnostic_certainty_classification']) 1366 1367 self._CHBOX_closed.SetValue(not self.data['episode_open'])
1368 #----------------------------------------------------------------
1370 self._refresh_as_new()
1371 #================================================================ 1372 # health issue related widgets/functions 1373 #----------------------------------------------------------------
1374 -def edit_health_issue(parent=None, issue=None):
1375 ea = cHealthIssueEditAreaPnl(parent = parent, id = -1) 1376 ea.data = issue 1377 ea.mode = gmTools.coalesce(issue, 'new', 'edit') 1378 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 1379 dlg.SetTitle(gmTools.coalesce(issue, _('Adding a new health issue'), _('Editing a health issue'))) 1380 if dlg.ShowModal() == wx.ID_OK: 1381 return True 1382 return False
1383 #----------------------------------------------------------------
1384 -class cIssueListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
1385 1386 # FIXME: support pre-selection 1387
1388 - def __init__(self, *args, **kwargs):
1389 1390 issues = kwargs['issues'] 1391 del kwargs['issues'] 1392 1393 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs) 1394 1395 self.SetTitle(_('Select the health issues you are interested in ...')) 1396 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u'']) 1397 1398 for issue in issues: 1399 if issue['is_confidential']: 1400 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential')) 1401 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED')) 1402 else: 1403 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'') 1404 1405 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description']) 1406 if issue['clinically_relevant']: 1407 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant')) 1408 if issue['is_active']: 1409 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active')) 1410 if issue['is_cause_of_death']: 1411 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal')) 1412 1413 self._LCTRL_items.set_column_widths() 1414 self._LCTRL_items.set_data(data = issues)
1415 #----------------------------------------------------------------
1416 -class cIssueSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
1417 """Let the user select a health issue. 1418 1419 The user can select a health issue from the existing issues 1420 of a patient. Selection is done with a phrasewheel so the user 1421 can type the issue name and matches will be shown. Typing 1422 "*" will show the entire list of issues. Inactive issues 1423 will be marked as such. If the user types an issue name not 1424 in the list of existing issues a new issue can be created 1425 from it if the programmer activated that feature. 1426 1427 If keyword <patient_id> is set to None or left out the control 1428 will listen to patient change signals and therefore act on 1429 gmPerson.gmCurrentPatient() changes. 1430 """
1431 - def __init__(self, *args, **kwargs):
1432 1433 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}} 1434 1435 mp = gmMatchProvider.cMatchProvider_SQL2 ( 1436 # FIXME: consider clin.health_issue.clinically_relevant 1437 queries = [u""" 1438 (select pk_health_issue, description, 1 1439 from clin.v_health_issues where 1440 is_active is true and 1441 description %(fragment_condition)s and 1442 %(ctxt_pat)s 1443 order by description) 1444 1445 union 1446 1447 (select pk_health_issue, description || _(' (inactive)'), 2 1448 from clin.v_health_issues where 1449 is_active is false and 1450 description %(fragment_condition)s and 1451 %(ctxt_pat)s 1452 order by description)""" 1453 ], 1454 context = ctxt 1455 ) 1456 1457 try: kwargs['patient_id'] 1458 except KeyError: kwargs['patient_id'] = None 1459 1460 if kwargs['patient_id'] is None: 1461 self.use_current_patient = True 1462 self.__register_patient_change_signals() 1463 pat = gmPerson.gmCurrentPatient() 1464 if pat.connected: 1465 mp.set_context('pat', pat.ID) 1466 else: 1467 self.use_current_patient = False 1468 self.__patient_id = int(kwargs['patient_id']) 1469 mp.set_context('pat', self.__patient_id) 1470 1471 del kwargs['patient_id'] 1472 1473 gmPhraseWheel.cPhraseWheel.__init__ ( 1474 self, 1475 *args, 1476 **kwargs 1477 ) 1478 self.matcher = mp
1479 #-------------------------------------------------------- 1480 # external API 1481 #--------------------------------------------------------
1482 - def set_patient(self, patient_id=None):
1483 if self.use_current_patient: 1484 return False 1485 self.__patient_id = int(patient_id) 1486 self.set_context('pat', self.__patient_id) 1487 return True
1488 #--------------------------------------------------------
1489 - def GetData(self, can_create=False, is_open=False):
1490 if self.data is None: 1491 if can_create: 1492 issue_name = self.GetValue().strip() 1493 1494 if self.use_current_patient: 1495 pat = gmPerson.gmCurrentPatient() 1496 else: 1497 pat = gmPerson.cPatient(aPK_obj=self.__patient_id) 1498 emr = pat.get_emr() 1499 1500 issue = emr.add_health_issue(issue_name = issue_name) 1501 if issue is None: 1502 self.data = None 1503 else: 1504 self.data = issue['pk_health_issue'] 1505 1506 return gmPhraseWheel.cPhraseWheel.GetData(self)
1507 #-------------------------------------------------------- 1508 # internal API 1509 #--------------------------------------------------------
1511 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection') 1512 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
1513 #--------------------------------------------------------
1514 - def _pre_patient_selection(self):
1515 return True
1516 #--------------------------------------------------------
1517 - def _post_patient_selection(self):
1518 if self.use_current_patient: 1519 patient = gmPerson.gmCurrentPatient() 1520 self.set_context('pat', patient.ID) 1521 return True
1522 #------------------------------------------------------------
1523 -class cIssueSelectionDlg(wxgIssueSelectionDlg.wxgIssueSelectionDlg):
1524
1525 - def __init__(self, *args, **kwargs):
1526 try: 1527 msg = kwargs['message'] 1528 except KeyError: 1529 msg = None 1530 del kwargs['message'] 1531 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs) 1532 if msg is not None: 1533 self._lbl_message.SetLabel(label=msg)
1534 #--------------------------------------------------------
1535 - def _on_OK_button_pressed(self, event):
1536 event.Skip() 1537 pk_issue = self._PhWheel_issue.GetData(can_create=True) 1538 if pk_issue is None: 1539 gmGuiHelpers.gm_show_error ( 1540 _('Cannot create new health issue:\n [%(issue)s]') % {'issue': self._PhWheel_issue.GetValue().strip()}, 1541 _('Selecting health issue') 1542 ) 1543 return False 1544 return True
1545 #------------------------------------------------------------
1546 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
1547 """Panel encapsulating health issue edit area functionality.""" 1548
1549 - def __init__(self, *args, **kwargs):
1550 1551 try: 1552 issue = kwargs['issue'] 1553 except KeyError: 1554 issue = None 1555 1556 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs) 1557 1558 gmEditArea.cGenericEditAreaMixin.__init__(self) 1559 1560 # FIXME: include more sources: coding systems/other database columns 1561 mp = gmMatchProvider.cMatchProvider_SQL2 ( 1562 queries = [u"select distinct on (description) description, description from clin.health_issue where description %(fragment_condition)s limit 50"] 1563 ) 1564 mp.setThresholds(1, 3, 5) 1565 self._PRW_condition.matcher = mp 1566 1567 mp = gmMatchProvider.cMatchProvider_SQL2 ( 1568 queries = [u""" 1569 select distinct on (grouping) grouping, grouping from ( 1570 1571 select rank, grouping from (( 1572 1573 select 1574 grouping, 1575 1 as rank 1576 from 1577 clin.health_issue 1578 where 1579 grouping %%(fragment_condition)s 1580 and 1581 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter) 1582 1583 ) union ( 1584 1585 select 1586 grouping, 1587 2 as rank 1588 from 1589 clin.health_issue 1590 where 1591 grouping %%(fragment_condition)s 1592 1593 )) as union_result 1594 1595 order by rank 1596 1597 ) as order_result 1598 1599 limit 50""" % gmPerson.gmCurrentPatient().ID 1600 ] 1601 ) 1602 mp.setThresholds(1, 3, 5) 1603 self._PRW_grouping.matcher = mp 1604 1605 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted) 1606 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted) 1607 1608 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted) 1609 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted) 1610 1611 self.data = issue
1612 #---------------------------------------------------------------- 1613 # generic Edit Area mixin API 1614 #----------------------------------------------------------------
1615 - def _valid_for_save(self):
1616 1617 if self._PRW_condition.GetValue().strip() == '': 1618 self._PRW_condition.display_as_valid(False) 1619 self._PRW_condition.SetFocus() 1620 return False 1621 self._PRW_condition.display_as_valid(True) 1622 self._PRW_condition.Refresh() 1623 1624 # FIXME: sanity check age/year diagnosed 1625 age_noted = self._PRW_age_noted.GetValue().strip() 1626 if age_noted != '': 1627 if gmDateTime.str2interval(str_interval = age_noted) is None: 1628 self._PRW_age_noted.display_as_valid(False) 1629 self._PRW_age_noted.SetFocus() 1630 return False 1631 self._PRW_age_noted.display_as_valid(True) 1632 1633 return True
1634 #----------------------------------------------------------------
1635 - def _save_as_new(self):
1636 pat = gmPerson.gmCurrentPatient() 1637 emr = pat.get_emr() 1638 1639 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip()) 1640 1641 side = u'' 1642 if self._ChBOX_left.GetValue(): 1643 side += u's' 1644 if self._ChBOX_right.GetValue(): 1645 side += u'd' 1646 issue['laterality'] = side 1647 1648 issue['diagnostic_certainty_classification'] = self._PRW_classification.GetData() 1649 issue['grouping'] = self._PRW_grouping.GetValue().strip() 1650 issue['is_active'] = self._ChBOX_active.GetValue() 1651 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue() 1652 issue['is_confidential'] = self._ChBOX_confidential.GetValue() 1653 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue() 1654 1655 age_noted = self._PRW_age_noted.GetData() 1656 if age_noted is not None: 1657 issue['age_noted'] = age_noted 1658 1659 issue.save() 1660 1661 narr = self._TCTRL_notes.GetValue().strip() 1662 if narr != u'': 1663 epi = emr.add_episode(episode_name = _('inception notes'), pk_health_issue = issue['pk_health_issue']) 1664 emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi) 1665 1666 self.data = issue 1667 1668 return True
1669 #----------------------------------------------------------------
1670 - def _save_as_update(self):
1671 # update self.data and save the changes 1672 1673 self.data['description'] = self._PRW_condition.GetValue().strip() 1674 1675 side = u'' 1676 if self._ChBOX_left.GetValue(): 1677 side += u's' 1678 if self._ChBOX_right.GetValue(): 1679 side += u'd' 1680 self.data['laterality'] = side 1681 1682 self.data['diagnostic_certainty_classification'] = self._PRW_classification.GetData() 1683 self.data['grouping'] = self._PRW_grouping.GetValue().strip() 1684 self.data['is_active'] = bool(self._ChBOX_active.GetValue()) 1685 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue()) 1686 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue()) 1687 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue()) 1688 1689 age_noted = self._PRW_age_noted.GetData() 1690 if age_noted is not None: 1691 self.data['age_noted'] = age_noted 1692 1693 self.data.save() 1694 1695 narr = self._TCTRL_notes.GetValue().strip() 1696 if narr != '': 1697 pat = gmPerson.gmCurrentPatient() 1698 emr = pat.get_emr() 1699 epi = emr.add_episode(episode_name = _('inception notes'), pk_health_issue = self.data['pk_health_issue']) 1700 emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi) 1701 1702 # FIXME: handle is_operation 1703 return True
1704 #----------------------------------------------------------------
1705 - def _refresh_as_new(self):
1706 self._PRW_condition.SetText() 1707 self._ChBOX_left.SetValue(0) 1708 self._ChBOX_right.SetValue(0) 1709 self._PRW_classification.SetText() 1710 self._PRW_grouping.SetText() 1711 self._TCTRL_notes.SetValue(u'') 1712 self._PRW_age_noted.SetText() 1713 self._PRW_year_noted.SetText() 1714 self._ChBOX_active.SetValue(0) 1715 self._ChBOX_relevant.SetValue(1) 1716 self._ChBOX_is_operation.SetValue(0) 1717 self._ChBOX_confidential.SetValue(0) 1718 self._ChBOX_caused_death.SetValue(0) 1719 1720 return True
1721 #----------------------------------------------------------------
1722 - def _refresh_from_existing(self):
1723 self._PRW_condition.SetText(self.data['description']) 1724 1725 lat = gmTools.coalesce(self.data['laterality'], '') 1726 if lat.find('s') == -1: 1727 self._ChBOX_left.SetValue(0) 1728 else: 1729 self._ChBOX_left.SetValue(1) 1730 if lat.find('d') == -1: 1731 self._ChBOX_right.SetValue(0) 1732 else: 1733 self._ChBOX_right.SetValue(1) 1734 1735 self._PRW_classification.SetData(data = self.data['diagnostic_certainty_classification']) 1736 self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], u'')) 1737 self._TCTRL_notes.SetValue('') 1738 1739 if self.data['age_noted'] is None: 1740 self._PRW_age_noted.SetText() 1741 else: 1742 self._PRW_age_noted.SetText ( 1743 value = '%sd' % self.data['age_noted'].days, 1744 data = self.data['age_noted'] 1745 ) 1746 1747 self._ChBOX_active.SetValue(self.data['is_active']) 1748 self._ChBOX_relevant.SetValue(self.data['clinically_relevant']) 1749 self._ChBOX_is_operation.SetValue(0) # FIXME 1750 self._ChBOX_confidential.SetValue(self.data['is_confidential']) 1751 self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death']) 1752 1753 # this dance should assure self._PRW_year_noted gets set -- but it doesn't ... 1754 # self._PRW_age_noted.SetFocus() 1755 # self._PRW_condition.SetFocus() 1756 1757 return True
1758 #----------------------------------------------------------------
1760 return self._refresh_as_new()
1761 #-------------------------------------------------------- 1762 # internal helpers 1763 #--------------------------------------------------------
1764 - def _on_leave_age_noted(self, *args, **kwargs):
1765 1766 if not self._PRW_age_noted.IsModified(): 1767 return True 1768 1769 str_age = self._PRW_age_noted.GetValue().strip() 1770 1771 if str_age == u'': 1772 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True) 1773 return True 1774 1775 age = gmDateTime.str2interval(str_interval = str_age) 1776 pat = gmPerson.gmCurrentPatient() 1777 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob'] 1778 1779 if age is None: 1780 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age) 1781 self._PRW_age_noted.SetBackgroundColour('pink') 1782 self._PRW_age_noted.Refresh() 1783 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True) 1784 return True 1785 1786 if age >= max_age: 1787 gmDispatcher.send ( 1788 signal = 'statustext', 1789 msg = _( 1790 'Health issue cannot have been noted at age %s. Patient is only %s old.' 1791 ) % (age, pat.get_medical_age()) 1792 ) 1793 self._PRW_age_noted.SetBackgroundColour('pink') 1794 self._PRW_age_noted.Refresh() 1795 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True) 1796 return True 1797 1798 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1799 self._PRW_age_noted.Refresh() 1800 self._PRW_age_noted.SetData(data=age) 1801 1802 fts = gmDateTime.cFuzzyTimestamp ( 1803 timestamp = pat['dob'] + age, 1804 accuracy = gmDateTime.acc_months 1805 ) 1806 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts) 1807 # if we do this we will *always* navigate there, regardless of TAB vs ALT-TAB 1808 #wx.CallAfter(self._ChBOX_active.SetFocus) 1809 # if we do the following instead it will take us to the save/update button ... 1810 #wx.CallAfter(self.Navigate) 1811 1812 return True
1813 #--------------------------------------------------------
1814 - def _on_leave_year_noted(self, *args, **kwargs):
1815 1816 if not self._PRW_year_noted.IsModified(): 1817 return True 1818 1819 year_noted = self._PRW_year_noted.GetData() 1820 1821 if year_noted is None: 1822 if self._PRW_year_noted.GetValue().strip() == u'': 1823 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True) 1824 return True 1825 self._PRW_year_noted.SetBackgroundColour('pink') 1826 self._PRW_year_noted.Refresh() 1827 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True) 1828 return True 1829 1830 year_noted = year_noted.get_pydt() 1831 1832 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo): 1833 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.')) 1834 self._PRW_year_noted.SetBackgroundColour('pink') 1835 self._PRW_year_noted.Refresh() 1836 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True) 1837 return True 1838 1839 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1840 self._PRW_year_noted.Refresh() 1841 1842 pat = gmPerson.gmCurrentPatient() 1843 issue_age = year_noted - pat['dob'] 1844 str_age = gmDateTime.format_interval_medically(interval = issue_age) 1845 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age) 1846 1847 return True
1848 #--------------------------------------------------------
1849 - def _on_modified_age_noted(self, *args, **kwargs):
1850 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True) 1851 return True
1852 #--------------------------------------------------------
1853 - def _on_modified_year_noted(self, *args, **kwargs):
1854 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True) 1855 return True
1856 #================================================================ 1857 # diagnostic certainty related widgets/functions 1858 #----------------------------------------------------------------
1859 -class cDiagnosticCertaintyClassificationPhraseWheel(gmPhraseWheel.cPhraseWheel):
1860
1861 - def __init__(self, *args, **kwargs):
1862 1863 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1864 1865 self.selection_only = False # can be NULL, too 1866 1867 mp = gmMatchProvider.cMatchProvider_FixedList ( 1868 aSeq = [ 1869 {'data': u'A', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1}, 1870 {'data': u'B', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1}, 1871 {'data': u'C', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1}, 1872 {'data': u'D', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1} 1873 ] 1874 ) 1875 mp.setThresholds(1, 2, 4) 1876 self.matcher = mp 1877 1878 self.SetToolTipString(_( 1879 "The diagnostic classification or grading of this assessment.\n" 1880 "\n" 1881 "This documents how certain one is about this being a true diagnosis." 1882 ))
1883 #================================================================ 1884 # MAIN 1885 #---------------------------------------------------------------- 1886 if __name__ == '__main__': 1887 1888 #================================================================
1889 - class testapp (wx.App):
1890 """ 1891 Test application for testing EMR struct widgets 1892 """ 1893 #--------------------------------------------------------
1894 - def OnInit (self):
1895 """ 1896 Create test application UI 1897 """ 1898 frame = wx.Frame ( 1899 None, 1900 -4, 1901 'Testing EMR struct widgets', 1902 size=wx.Size(600, 400), 1903 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE 1904 ) 1905 filemenu= wx.Menu() 1906 filemenu.AppendSeparator() 1907 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application") 1908 1909 # Creating the menubar. 1910 menuBar = wx.MenuBar() 1911 menuBar.Append(filemenu,"&File") 1912 1913 frame.SetMenuBar(menuBar) 1914 1915 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"), 1916 wx.DefaultPosition, wx.DefaultSize, 0 ) 1917 1918 # event handlers 1919 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow) 1920 1921 # patient EMR 1922 self.__pat = gmPerson.gmCurrentPatient() 1923 1924 frame.Show(1) 1925 return 1
1926 #--------------------------------------------------------
1927 - def OnCloseWindow (self, e):
1928 """ 1929 Close test aplication 1930 """ 1931 self.ExitMainLoop ()
1932 #----------------------------------------------------------------
1933 - def test_encounter_edit_area_panel():
1934 app = wx.PyWidgetTester(size = (200, 300)) 1935 emr = pat.get_emr() 1936 enc = emr.active_encounter 1937 #enc = gmEMRStructItems.cEncounter(1) 1938 pnl = cEncounterEditAreaPnl(app.frame, -1, encounter=enc) 1939 app.frame.Show(True) 1940 app.MainLoop() 1941 return
1942 #----------------------------------------------------------------
1943 - def test_encounter_edit_area_dialog():
1944 app = wx.PyWidgetTester(size = (200, 300)) 1945 emr = pat.get_emr() 1946 enc = emr.active_encounter 1947 #enc = gmEMRStructItems.cEncounter(1) 1948 1949 dlg = cEncounterEditAreaDlg(parent=app.frame, id=-1, size = (400,400), encounter=enc) 1950 dlg.ShowModal()
1951 1952 # pnl = cEncounterEditAreaDlg(app.frame, -1, encounter=enc) 1953 # app.frame.Show(True) 1954 # app.MainLoop() 1955 #----------------------------------------------------------------
1956 - def test_epsiode_edit_area_pnl():
1957 app = wx.PyWidgetTester(size = (200, 300)) 1958 emr = pat.get_emr() 1959 epi = emr.get_episodes()[0] 1960 pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi) 1961 app.frame.Show(True) 1962 app.MainLoop()
1963 #----------------------------------------------------------------
1964 - def test_episode_edit_area_dialog():
1965 app = wx.PyWidgetTester(size = (200, 300)) 1966 emr = pat.get_emr() 1967 epi = emr.get_episodes()[0] 1968 edit_episode(parent=app.frame, episode=epi)
1969 #----------------------------------------------------------------
1970 - def test_hospital_stay_prw():
1971 app = wx.PyWidgetTester(size = (400, 40)) 1972 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20)) 1973 app.MainLoop()
1974 #----------------------------------------------------------------
1975 - def test_episode_selection_prw():
1976 app = wx.PyWidgetTester(size = (400, 40)) 1977 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20)) 1978 # app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(350,20), pos=(10,20), patient_id=pat.ID) 1979 app.MainLoop()
1980 #----------------------------------------------------------------
1981 - def test_health_issue_edit_area_dlg():
1982 app = wx.PyWidgetTester(size = (200, 300)) 1983 edit_health_issue(parent=app.frame, issue=None)
1984 #----------------------------------------------------------------
1985 - def test_health_issue_edit_area_pnl():
1986 app = wx.PyWidgetTester(size = (200, 300)) 1987 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400)) 1988 app.MainLoop()
1989 #----------------------------------------------------------------
1990 - def test_edit_procedure():
1991 app = wx.PyWidgetTester(size = (200, 300)) 1992 edit_procedure(parent=app.frame)
1993 #================================================================ 1994 1995 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 1996 1997 gmI18N.activate_locale() 1998 gmI18N.install_domain() 1999 gmDateTime.init() 2000 2001 # obtain patient 2002 pat = gmPerson.ask_for_patient() 2003 if pat is None: 2004 print "No patient. Exiting gracefully..." 2005 sys.exit(0) 2006 gmPatSearchWidgets.set_active_patient(patient=pat) 2007 2008 # try: 2009 # lauch emr dialogs test application 2010 # app = testapp(0) 2011 # app.MainLoop() 2012 # except StandardError: 2013 # _log.exception("unhandled exception caught !") 2014 # but re-raise them 2015 # raise 2016 2017 #test_encounter_edit_area_panel() 2018 #test_encounter_edit_area_dialog() 2019 #test_epsiode_edit_area_pnl() 2020 #test_episode_edit_area_dialog() 2021 #test_health_issue_edit_area_dlg() 2022 #test_episode_selection_prw() 2023 #test_hospital_stay_prw() 2024 test_edit_procedure() 2025 2026 #================================================================ 2027 # $Log: gmEMRStructWidgets.py,v $ 2028 # Revision 1.114 2010/02/06 21:01:27 ncq 2029 # - fix health issue editing 2030 # 2031 # Revision 1.113 2010/01/21 08:42:31 ncq 2032 # - adjust to Dx certainty mapper changes 2033 # 2034 # Revision 1.112 2009/12/03 17:48:15 ncq 2035 # - only create episode in GetData if there's a name available 2036 # 2037 # Revision 1.111 2009/12/01 10:50:44 ncq 2038 # - fix issue creation 2039 # 2040 # Revision 1.110 2009/11/15 01:05:44 ncq 2041 # - start-new-encounter 2042 # - enhance select-encounters 2043 # 2044 # Revision 1.109 2009/11/13 21:07:20 ncq 2045 # - fully implement procedure EA 2046 # 2047 # Revision 1.108 2009/11/06 15:17:46 ncq 2048 # - set better size on encounter EA 2049 # 2050 # Revision 1.107 2009/09/23 14:42:04 ncq 2051 # - implement procedure management 2052 # - implement promote-episode-to-issue 2053 # 2054 # Revision 1.106 2009/09/17 21:53:41 ncq 2055 # - start support for managing performed procedures 2056 # 2057 # Revision 1.105 2009/09/15 15:24:21 ncq 2058 # - start procedure EA 2059 # - phrasewheel for hospital stays 2060 # 2061 # Revision 1.104 2009/09/13 18:45:25 ncq 2062 # - no more get-active-encounter() 2063 # 2064 # Revision 1.103 2009/09/01 23:05:20 ncq 2065 # - improved tooltip for classification phrasewheel 2066 # 2067 # Revision 1.102 2009/09/01 22:29:09 ncq 2068 # - normalize issue/episode edit area handling, obsoleting their dialogs 2069 # - add edit_episode/edit_health_issue 2070 # - support diagnostic certainty 2071 # 2072 # Revision 1.101 2009/07/30 12:03:54 ncq 2073 # - fix editing age noted 2074 # 2075 # Revision 1.100 2009/07/16 09:52:15 ncq 2076 # - improved labelling 2077 # 2078 # Revision 1.99 2009/07/15 21:32:35 ncq 2079 # - add missing () thereby making changing enc type possible 2080 # 2081 # Revision 1.98 2009/07/09 16:46:20 ncq 2082 # - cleanup 2083 # - improved wording as per list 2084 # 2085 # Revision 1.97 2009/07/02 20:50:37 ncq 2086 # - use generic EA dlg 2 2087 # 2088 # Revision 1.96 2009/07/01 17:07:16 ncq 2089 # - in episode selector sort closed below unclosed episodes 2090 # 2091 # Revision 1.95 2009/06/29 15:05:52 ncq 2092 # - fix typo 2093 # 2094 # Revision 1.94 2009/06/11 12:37:25 ncq 2095 # - much simplified initial setup of list ctrls 2096 # 2097 # Revision 1.93 2009/06/04 16:30:30 ncq 2098 # - use set active patient from pat search widgets 2099 # 2100 # Revision 1.92 2009/05/13 13:12:21 ncq 2101 # - enable encounter editing right from the list 2102 # 2103 # Revision 1.91 2009/05/13 12:18:35 ncq 2104 # - streamline managing encounters 2105 # 2106 # Revision 1.90 2009/05/08 07:59:33 ncq 2107 # - cleanup, better display of encounter list 2108 # 2109 # Revision 1.89 2009/04/21 16:59:59 ncq 2110 # - edit area dlg now takes single_entry argument 2111 # 2112 # Revision 1.88 2009/04/19 22:26:47 ncq 2113 # - interval parsing moved 2114 # 2115 # Revision 1.87 2009/04/13 10:54:06 ncq 2116 # - show encounter list 2117 # - allow removing RFE/AOE from edit area 2118 # 2119 # Revision 1.86 2009/04/05 18:04:46 ncq 2120 # - support and use grouping 2121 # 2122 # Revision 1.85 2009/04/03 09:47:29 ncq 2123 # - hospital stay widgets 2124 # 2125 # Revision 1.84 2009/01/02 11:39:48 ncq 2126 # - support custom message/buttons in encounter edit area/dlg 2127 # 2128 # Revision 1.83 2008/12/09 23:28:51 ncq 2129 # - use description_gender 2130 # 2131 # Revision 1.82 2008/10/22 12:19:10 ncq 2132 # - rename pseudo episode on health issue creation to "inceptio notes" 2133 # 2134 # Revision 1.81 2008/10/12 16:15:17 ncq 2135 # - no more "foundational" health issue 2136 # 2137 # Revision 1.80 2008/09/02 19:01:12 ncq 2138 # - adjust to clin health_issue fk_patient drop and related changes 2139 # 2140 # Revision 1.79 2008/07/24 13:58:40 ncq 2141 # - manage encounter types 2142 # 2143 # Revision 1.78 2008/07/07 13:43:16 ncq 2144 # - current patient .connected 2145 # 2146 # Revision 1.77 2008/06/09 15:33:59 ncq 2147 # - improved episode selector SQL 2148 # 2149 # Revision 1.76 2008/05/13 14:11:53 ncq 2150 # - properly handle age=None in pHX ea 2151 # 2152 # Revision 1.75 2008/05/07 15:21:10 ncq 2153 # - move health issue EA behaviour close to Richard's specs 2154 # 2155 # Revision 1.74 2008/03/05 22:37:45 ncq 2156 # - new style logging 2157 # - new health issue adding 2158 # 2159 # Revision 1.73 2008/01/30 14:03:41 ncq 2160 # - use signal names directly 2161 # - switch to std lib logging 2162 # 2163 # Revision 1.72 2008/01/22 12:21:27 ncq 2164 # - better encounter editor 2165 # 2166 # Revision 1.71 2007/10/07 12:32:41 ncq 2167 # - workplace property now on gmSurgery.gmCurrentPractice() borg 2168 # 2169 # Revision 1.70 2007/08/29 22:08:57 ncq 2170 # - narrative widgets factored out 2171 # 2172 # Revision 1.69 2007/08/29 14:38:39 ncq 2173 # - improve encounter details dialog 2174 # 2175 # Revision 1.68 2007/08/15 09:19:32 ncq 2176 # - cleanup 2177 # 2178 # Revision 1.67 2007/08/13 22:00:48 ncq 2179 # - proper year format specs 2180 # 2181 # Revision 1.66 2007/08/13 11:07:41 ncq 2182 # - make episode descriptions phrasewheel actually *match* 2183 # on input, IOW, add a where clause to the select ;-) 2184 # 2185 # Revision 1.65 2007/08/12 00:09:07 ncq 2186 # - no more gmSignals.py 2187 # 2188 # Revision 1.64 2007/07/13 12:20:48 ncq 2189 # - select_narrative_from_episodes(), related widgets, and test suite 2190 # 2191 # Revision 1.63 2007/06/10 10:02:53 ncq 2192 # - episode pk is pk_episode 2193 # 2194 # Revision 1.62 2007/05/18 13:28:57 ncq 2195 # - implement cMoveNarrativeDlg 2196 # 2197 # Revision 1.61 2007/05/14 13:11:24 ncq 2198 # - use statustext() signal 2199 # 2200 # Revision 1.60 2007/04/27 13:28:25 ncq 2201 # - use c2ButtonQuestionDlg 2202 # 2203 # Revision 1.59 2007/04/25 22:00:47 ncq 2204 # - use better question dialog for very recent encounter 2205 # 2206 # Revision 1.58 2007/04/13 15:58:00 ncq 2207 # - don't conflict open episode on itself 2208 # 2209 # Revision 1.57 2007/04/02 18:39:52 ncq 2210 # - gmFuzzyTimestamp -> gmDateTime 2211 # 2212 # Revision 1.56 2007/03/31 21:50:15 ncq 2213 # - cPatient now cIdentity child 2214 # 2215 # Revision 1.55 2007/03/18 14:05:31 ncq 2216 # - re-add lost 1.55 2217 # 2218 # Revision 1.55 2007/03/12 12:27:13 ncq 2219 # - convert some statustext calls to use signal handler 2220 # 2221 # Revision 1.54 2007/03/08 11:39:13 ncq 2222 # - cEpisodeSelectionPhraseWheel 2223 # - limit 30 2224 # - order by weight, description 2225 # - show both open and closed 2226 # - include health issue string 2227 # - test suite 2228 # 2229 # Revision 1.53 2007/02/22 17:41:13 ncq 2230 # - adjust to gmPerson changes 2231 # 2232 # Revision 1.52 2007/02/17 14:13:11 ncq 2233 # - gmPerson.gmCurrentProvider().workplace now property 2234 # 2235 # Revision 1.51 2007/02/16 12:53:19 ncq 2236 # - now that we have cPhraseWheel.suppress_text_update_smarts we 2237 # can avoid infinite looping due to circular on_modified callbacks 2238 # 2239 # Revision 1.50 2007/02/09 14:59:39 ncq 2240 # - cleanup 2241 # 2242 # Revision 1.49 2007/02/06 13:43:40 ncq 2243 # - no more aDelay in __init__() 2244 # 2245 # Revision 1.48 2007/02/05 12:15:23 ncq 2246 # - no more aMatchProvider/selection_only in cPhraseWheel.__init__() 2247 # 2248 # Revision 1.47 2007/02/04 15:53:58 ncq 2249 # - use SetText() 2250 # 2251 # Revision 1.46 2007/01/15 20:22:09 ncq 2252 # - fix several bugs in move_episode_to_issue() 2253 # 2254 # Revision 1.45 2007/01/15 13:02:26 ncq 2255 # - completely revamped move_episode_to_issue() and use it 2256 # 2257 # Revision 1.44 2007/01/09 12:59:01 ncq 2258 # - datetime.timedelta needs int, not decimal, so make epi_ttl an int 2259 # - missing _ in front of CHBOX_closed 2260 # - save() needs to return True/False so dialog can close or not 2261 # 2262 # Revision 1.43 2007/01/04 23:29:02 ncq 2263 # - cEpisodeDescriptionPhraseWheel 2264 # - cEpisodeEditAreaPnl 2265 # - cEpisodeEditAreaDlg 2266 # - test suite enhancement 2267 # 2268 # Revision 1.42 2007/01/02 16:18:10 ncq 2269 # - health issue phrasewheel: clin.health_issue has fk_patient, not pk_patient 2270 # 2271 # Revision 1.41 2006/12/25 22:52:14 ncq 2272 # - encounter details editor 2273 # - set patient name 2274 # - valid_for_save(), save() 2275 # - properly set started/ended 2276 # 2277 # Revision 1.40 2006/12/23 01:07:28 ncq 2278 # - fix encounter type phrasewheel query 2279 # - add encounter details edit area panel/dialog 2280 # - still needs save() and friends fixed 2281 # - general cleanup/moving about of stuff 2282 # - fix move_episode_to_issue() logic 2283 # - start cleanup of test suite 2284 # 2285 # Revision 1.39 2006/11/28 20:44:36 ncq 2286 # - some rearrangement 2287 # 2288 # Revision 1.38 2006/11/27 23:15:01 ncq 2289 # - remove prints 2290 # 2291 # Revision 1.37 2006/11/27 23:05:49 ncq 2292 # - add commented out on_modified callbacks 2293 # 2294 # Revision 1.36 2006/11/27 12:40:20 ncq 2295 # - adapt to field name fixes from wxGlade 2296 # 2297 # Revision 1.35 2006/11/24 16:40:35 ncq 2298 # - age_noted can be NULL so handle set when refresh()ing health issue edit area 2299 # 2300 # Revision 1.34 2006/11/24 14:22:35 ncq 2301 # - cannot pass issue keyword to wx.Dialog child in cHealthIssueEditAreaDlg.__init__ 2302 # - relabel buttons to save or update re clear/restore when adding/editing health issue 2303 # - EndModal needs argument 2304 # 2305 # Revision 1.33 2006/11/24 09:55:05 ncq 2306 # - cHealthIssueEditArea(Pnl/Dlg) closely following Richard's specs 2307 # - test code 2308 # 2309 # Revision 1.32 2006/11/15 00:40:07 ncq 2310 # - properly set up context for phrasewheels 2311 # 2312 # Revision 1.31 2006/10/31 17:21:16 ncq 2313 # - unicode()ify queries 2314 # 2315 # Revision 1.30 2006/10/24 13:22:40 ncq 2316 # - gmPG -> gmPG2 2317 # - no need for service name in cMatchProvider_SQL2() 2318 # 2319 # Revision 1.29 2006/09/03 11:30:28 ncq 2320 # - add move_episode_to_issue() 2321 # 2322 # Revision 1.28 2006/06/26 21:37:43 ncq 2323 # - cleanup 2324 # 2325 # Revision 1.27 2006/06/26 13:07:00 ncq 2326 # - fix issue selection phrasewheel SQL UNION 2327 # - improved variable naming 2328 # - track patient id in set_patient on issue/episode selection phrasewheel 2329 # so GetData can create new issues/episodes if told to do so 2330 # - add cIssueSelectionDlg 2331 # 2332 # Revision 1.26 2006/06/23 21:32:11 ncq 2333 # - add cIssueSelectionPhrasewheel 2334 # 2335 # Revision 1.25 2006/05/31 09:46:20 ncq 2336 # - cleanup 2337 # 2338 # Revision 1.24 2006/05/28 15:40:51 ncq 2339 # - fix typo in variable 2340 # 2341 # Revision 1.23 2006/05/25 22:19:25 ncq 2342 # - add preconfigured episode selection/creation phrasewheel 2343 # - cleanup, fix unit test 2344 # 2345 # Revision 1.22 2006/05/04 09:49:20 ncq 2346 # - get_clinical_record() -> get_emr() 2347 # - adjust to changes in set_active_patient() 2348 # - need explicit set_active_patient() after ask_for_patient() if wanted 2349 # 2350 # Revision 1.21 2005/12/26 05:26:37 sjtan 2351 # 2352 # match schema 2353 # 2354 # Revision 1.20 2005/12/26 04:23:05 sjtan 2355 # 2356 # match schema changes. 2357 # 2358 # Revision 1.19 2005/12/06 14:24:15 ncq 2359 # - clin.clin_health_issue/episode -> clin.health_issue/episode 2360 # 2361 # Revision 1.18 2005/10/20 07:42:27 ncq 2362 # - somewhat improved edit area for issue 2363 # 2364 # Revision 1.17 2005/10/08 12:33:09 sjtan 2365 # tree can be updated now without refetching entire cache; done by passing emr object to create_xxxx methods and calling emr.update_cache(key,obj);refresh_historical_tree non-destructively checks for changes and removes removed nodes and adds them if cache mismatch. 2366 # 2367 # Revision 1.16 2005/10/04 19:24:53 sjtan 2368 # browser now remembers expansion state and select state between change of patients, between health issue rename, episode rename or encounter relinking. This helps when reviewing the record more than once in a day. 2369 # 2370 # Revision 1.15 2005/09/27 20:44:58 ncq 2371 # - wx.wx* -> wx.* 2372 # 2373 # Revision 1.14 2005/09/26 18:01:50 ncq 2374 # - use proper way to import wx26 vs wx2.4 2375 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES 2376 # - time for fixup 2377 # 2378 # Revision 1.13 2005/09/24 09:17:28 ncq 2379 # - some wx2.6 compatibility fixes 2380 # 2381 # Revision 1.12 2005/08/06 16:50:51 ncq 2382 # - zero-size spacer seems pointless and besides it 2383 # don't work like that in wx2.5 2384 # 2385 # Revision 1.11 2005/06/29 15:06:38 ncq 2386 # - defaults for edit area popup/editarea2 __init__ 2387 # 2388 # Revision 1.10 2005/06/28 17:14:56 cfmoro 2389 # Auto size flag causes the text not being displayed 2390 # 2391 # Revision 1.9 2005/06/20 13:03:38 cfmoro 2392 # Relink encounter to another episode 2393 # 2394 # Revision 1.8 2005/06/10 23:22:43 ncq 2395 # - SQL2 match provider now requires query *list* 2396 # 2397 # Revision 1.7 2005/05/06 15:30:15 ncq 2398 # - attempt to properly set focus 2399 # 2400 # Revision 1.6 2005/04/25 08:30:59 ncq 2401 # - make past medical history proxy episodes closed by default 2402 # 2403 # Revision 1.5 2005/04/24 14:45:18 ncq 2404 # - cleanup, use generic edit area popup dialog 2405 # - "finalize" (as for 0.1) health issue edit area 2406 # 2407 # Revision 1.4 2005/04/20 22:09:54 ncq 2408 # - add edit area and popup dialog for health issue 2409 # 2410 # Revision 1.3 2005/03/14 14:36:31 ncq 2411 # - use simplified episode naming 2412 # 2413 # Revision 1.2 2005/01/31 18:51:08 ncq 2414 # - caching emr = patient.get_clinical_record() locally is unsafe 2415 # because patient can change but emr will stay the same (it's a 2416 # local "pointer", after all, and not a singleton) 2417 # - adding episodes actually works now 2418 # 2419 # Revision 1.1 2005/01/31 13:09:21 ncq 2420 # - this is OK to go in 2421 # 2422 # Revision 1.9 2005/01/31 13:06:02 ncq 2423 # - use gmPerson.ask_for_patient() 2424 # 2425 # Revision 1.8 2005/01/31 09:50:59 ncq 2426 # - gmPatient -> gmPerson 2427 # 2428 # Revision 1.7 2005/01/29 19:12:19 cfmoro 2429 # Episode creation on episode editor widget 2430 # 2431 # Revision 1.6 2005/01/29 18:01:20 ncq 2432 # - some cleanup 2433 # - actually create new episodes 2434 # 2435 # Revision 1.5 2005/01/28 18:05:56 cfmoro 2436 # Implemented episode picker and episode selector dialogs and widgets 2437 # 2438 # Revision 1.4 2005/01/24 16:57:38 ncq 2439 # - some cleanup here and there 2440 # 2441 # 2442