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

Source Code for Module Gnumed.wxpython.gmPersonCreationWidgets

  1  """GNUmed patient creation widgets. 
  2   
  3  copyright: authors 
  4  """ 
  5  #============================================================ 
  6  __author__ = "K.Hilbert" 
  7  __license__ = "GPL v2 or later (details at http://www.gnu.org)" 
  8   
  9  import logging 
 10  import sys 
 11  import datetime as pydt 
 12   
 13   
 14  import wx 
 15   
 16   
 17  if __name__ == '__main__': 
 18          sys.path.insert(0, '../../') 
 19  from Gnumed.pycommon import gmCfg 
 20  from Gnumed.pycommon import gmPG2 
 21  from Gnumed.pycommon import gmTools 
 22  from Gnumed.pycommon import gmDateTime 
 23  from Gnumed.pycommon import gmDispatcher 
 24   
 25  from Gnumed.business import gmPraxis 
 26  from Gnumed.business import gmPerson 
 27  from Gnumed.business import gmStaff 
 28  from Gnumed.business import gmDemographicRecord 
 29   
 30  from Gnumed.wxpython import gmEditArea 
 31  from Gnumed.wxpython import gmGuiHelpers 
 32  from Gnumed.wxpython import gmEncounterWidgets 
 33  from Gnumed.wxpython.gmDemographicsWidgets import _validate_dob_field, _validate_tob_field, _empty_dob_allowed 
 34   
 35   
 36  _log = logging.getLogger('gm.patient') 
 37   
 38  #============================================================ 
39 -def create_new_person(parent=None, activate=False):
40 41 if parent is None: 42 parent = wx.GetApp().GetTopWindow() 43 44 if activate: # meaning we will switch away from the current patient if any 45 msg = _( 46 'Before creating a new person review the encounter details\n' 47 'of the patient you just worked on:\n' 48 ) 49 gmEncounterWidgets.sanity_check_encounter_of_active_patient(parent = parent, msg = msg) 50 51 msg = _('Edit the current encounter of the patient you are ABOUT TO LEAVE:') 52 53 dbcfg = gmCfg.cCfgSQL() 54 55 def_region = dbcfg.get2 ( 56 option = 'person.create.default_region', 57 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 58 bias = 'user' 59 ) 60 def_country = None 61 62 if def_region is None: 63 def_country = dbcfg.get2 ( 64 option = 'person.create.default_country', 65 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 66 bias = 'user' 67 ) 68 else: 69 countries = gmDemographicRecord.get_country_for_region(region = def_region) 70 if len(countries) == 1: 71 def_country = countries[0]['code_country'] 72 73 ea = cNewPatientEAPnl(parent, -1, country = def_country, region = def_region) 74 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = True) 75 dlg.SetTitle(_('Adding new person')) 76 ea._PRW_lastname.SetFocus() 77 result = dlg.ShowModal() 78 pat = ea.data 79 dlg.Destroy() 80 81 if result != wx.ID_OK: 82 return False 83 84 _log.debug('created new person [%s]', pat.ID) 85 86 if activate: 87 from Gnumed.wxpython import gmPatSearchWidgets 88 gmPatSearchWidgets.set_active_patient(patient = pat) 89 90 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin') 91 92 return True
93 94 #============================================================ 95 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl 96
97 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
98
99 - def __init__(self, *args, **kwargs):
100 101 try: 102 self.default_region = kwargs['region'] 103 del kwargs['region'] 104 except KeyError: 105 self.default_region = None 106 107 try: 108 self.default_country = kwargs['country'] 109 del kwargs['country'] 110 except KeyError: 111 self.default_country = None 112 113 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs) 114 gmEditArea.cGenericEditAreaMixin.__init__(self) 115 116 self.mode = 'new' 117 self.data = None 118 self._address = None 119 120 self.__init_ui() 121 self.__register_interests()
122 #---------------------------------------------------------------- 123 # internal helpers 124 #----------------------------------------------------------------
125 - def __init_ui(self):
126 self._PRW_lastname.final_regex = '.+' 127 self._PRW_firstnames.final_regex = '.+' 128 self._PRW_address_searcher.selection_only = False 129 130 # only if we would support None on selection_only's: 131 # self._PRW_external_id_type.selection_only = True 132 133 if self.default_country is not None: 134 match = self._PRW_country._data2match(data = self.default_country) 135 if match is not None: 136 self._PRW_country.SetText(value = match['field_label'], data = match['data']) 137 138 if self.default_region is not None: 139 self._PRW_region.SetText(value = self.default_region) 140 141 self._PRW_type.SetText(value = 'home') 142 # FIXME: only use this if member of gm-doctors, 143 # FIXME: other than that check fallback_primary_provider 144 self._PRW_primary_provider.SetData(data = gmStaff.gmCurrentProvider()['pk_staff']) 145 146 self._PRW_lastname.SetFocus()
147 #----------------------------------------------------------------
148 - def _refresh_ext_id_warning(self):
149 id_type = self._PRW_external_id_type.GetData() 150 if id_type is None: 151 self._LBL_id_exists.SetLabel('') 152 return 153 val = self._TCTRL_external_id_value.GetValue().strip() 154 if val == '': 155 self._LBL_id_exists.SetLabel('') 156 return 157 if gmPerson.external_id_exists(pk_issuer = id_type, value = val) > 0: 158 self._LBL_id_exists.SetLabel(_('ID exists !')) 159 else: 160 self._LBL_id_exists.SetLabel('')
161 #----------------------------------------------------------------
162 - def _refresh_dupe_warning(self):
163 lname = self._PRW_lastname.GetValue().strip() 164 if lname == '': 165 self._LBL_person_exists.SetLabel('') 166 return 167 168 dob = self._PRW_dob.GetData() 169 if dob is None: 170 self._LBL_person_exists.SetLabel('') 171 return 172 173 fname = gmTools.none_if(self._PRW_firstnames.GetValue().strip()[:1], '') 174 175 no_of_dupes = gmPerson.get_potential_person_dupes(lastnames = lname, firstnames = fname, dob = dob) 176 if no_of_dupes == 0: 177 lbl = '' 178 elif no_of_dupes == 1: 179 lbl = _('One "%s, %s (%s)" already exists !') % ( 180 lname, 181 gmTools.coalesce(fname, '?', '%s %%s. %s' % (gmTools.u_ellipsis, gmTools.u_ellipsis)), 182 gmDateTime.pydt_strftime(dob, '%Y %b %d', 'utf8') 183 ) 184 else: 185 lbl = _('%s "%s, %s (%s)" already exist !') % ( 186 no_of_dupes, 187 lname, 188 gmTools.coalesce(fname, '?', '%s %%s. %s' % (gmTools.u_ellipsis, gmTools.u_ellipsis)), 189 gmDateTime.pydt_strftime(dob, '%Y %b %d', 'utf8') 190 ) 191 192 self._LBL_person_exists.SetLabel(lbl)
193 #----------------------------------------------------------------
194 - def __perhaps_invalidate_address_searcher(self, ctrl=None, field=None):
195 196 adr = self._PRW_address_searcher.address 197 if adr is None: 198 return True 199 200 if ctrl.GetValue().strip() != adr[field]: 201 wx.CallAfter(self._PRW_address_searcher.SetText, value = '', data = None) 202 return True 203 204 return False
205 #----------------------------------------------------------------
207 adr = self._PRW_address_searcher.address 208 if adr is None: 209 return True 210 211 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode']) 212 213 self._PRW_street.SetText(value = adr['street'], data = adr['street']) 214 self._PRW_street.set_context(context = 'zip', val = adr['postcode']) 215 216 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb']) 217 self._PRW_urb.set_context(context = 'zip', val = adr['postcode']) 218 219 self._PRW_region.SetText(value = adr['l10n_region'], data = adr['code_region']) 220 self._PRW_region.set_context(context = 'zip', val = adr['postcode']) 221 222 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country']) 223 self._PRW_country.set_context(context = 'zip', val = adr['postcode'])
224 #----------------------------------------------------------------
226 error = False 227 228 # name fields 229 if self._PRW_lastname.GetValue().strip() == '': 230 error = True 231 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 232 self._PRW_lastname.display_as_valid(False) 233 else: 234 self._PRW_lastname.display_as_valid(True) 235 236 if self._PRW_firstnames.GetValue().strip() == '': 237 error = True 238 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 239 self._PRW_firstnames.display_as_valid(False) 240 else: 241 self._PRW_firstnames.display_as_valid(True) 242 243 # gender 244 if self._PRW_gender.GetData() is None: 245 error = True 246 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 247 self._PRW_gender.display_as_valid(False) 248 else: 249 self._PRW_gender.display_as_valid(True) 250 251 # dob validation 252 if not _validate_dob_field(self._PRW_dob): 253 error = True 254 255 # TOB validation 256 if _validate_tob_field(self._TCTRL_tob): 257 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = True) 258 else: 259 error = True 260 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = False) 261 262 # uniqueness 263 if gmPerson.this_person_exists ( 264 self._PRW_lastname.GetValue().strip(), 265 self._PRW_firstnames.GetValue().strip(), 266 self._PRW_dob.GetData(), 267 gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 268 ): 269 error = True 270 self.status_message = _('Duplicate person. Modify name and/or DOB or use comment to make unique.') 271 272 return (not error)
273 #----------------------------------------------------------------
274 - def __address_valid_for_save(self, empty_address_is_valid=False):
275 276 # existing address ? if so set other fields 277 if self._PRW_address_searcher.GetData() is not None: 278 wx.CallAfter(self.__set_fields_from_address_searcher) 279 return True 280 281 # must either all contain something or none of them 282 fields_to_fill = ( 283 self._TCTRL_number, 284 self._PRW_zip, 285 self._PRW_street, 286 self._PRW_urb, 287 self._PRW_type 288 ) 289 no_of_filled_fields = 0 290 291 for field in fields_to_fill: 292 if field.GetValue().strip() != '': 293 no_of_filled_fields += 1 294 field.display_as_valid(True) 295 296 # empty address ? 297 if no_of_filled_fields == 0: 298 if empty_address_is_valid: 299 return True 300 else: 301 return None 302 303 # incompletely filled address ? 304 if no_of_filled_fields != len(fields_to_fill): 305 for field in fields_to_fill: 306 if field.GetValue().strip() == '': 307 field.display_as_valid(False) 308 field.SetFocus() 309 msg = _('To properly create an address, all the related fields must be filled in.') 310 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 311 return False 312 313 # fields which must contain a selected item 314 # FIXME: they must also contain an *acceptable combination* which 315 # FIXME: can only be tested against the database itself ... 316 strict_fields = ( 317 self._PRW_type, 318 self._PRW_region, 319 self._PRW_country 320 ) 321 error = False 322 for field in strict_fields: 323 if field.GetData() is None: 324 error = True 325 field.display_as_valid(False) 326 field.SetFocus() 327 else: 328 field.display_as_valid(True) 329 330 if error: 331 msg = _('This field must contain an item selected from the dropdown list.') 332 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 333 return False 334 335 return True
336 #----------------------------------------------------------------
337 - def __register_interests(self):
338 339 # identity 340 self._PRW_lastname.add_callback_on_lose_focus(self._on_leaving_lastname) 341 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname) 342 self._PRW_dob.add_callback_on_lose_focus(self._on_leaving_dob) 343 344 # address 345 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher) 346 347 # invalidate address searcher when any field edited 348 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher) 349 self._TCTRL_number.Bind(wx.EVT_KILL_FOCUS, self._on_leaving_number) 350 self._TCTRL_unit.Bind(wx.EVT_KILL_FOCUS, self._on_leaving_unit) 351 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher) 352 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher) 353 354 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip) 355 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country) 356 357 self._PRW_external_id_type.add_callback_on_lose_focus(callback = self._on_leaving_ext_id_type) 358 self._TCTRL_external_id_value.add_callback_on_lose_focus(callback = self._on_leaving_ext_id_val)
359 360 #---------------------------------------------------------------- 361 # event handlers 362 #----------------------------------------------------------------
363 - def _on_leaving_ext_id_type(self):
364 wx.CallAfter(self._refresh_ext_id_warning)
365 #----------------------------------------------------------------
366 - def _on_leaving_ext_id_val(self):
367 wx.CallAfter(self._refresh_ext_id_warning)
368 #----------------------------------------------------------------
369 - def _on_leaving_lastname(self):
370 wx.CallAfter(self._refresh_dupe_warning)
371 #----------------------------------------------------------------
372 - def _on_leaving_firstname(self):
373 """Set the gender according to entered firstname. 374 375 Matches are fetched from existing records in backend. 376 """ 377 wx.CallAfter(self._refresh_dupe_warning) 378 379 # only set if not already set so as to not 380 # overwrite a change by the user 381 if self._PRW_gender.GetData() is not None: 382 return True 383 384 firstname = self._PRW_firstnames.GetValue().strip() 385 if firstname == '': 386 return True 387 388 gender = gmPerson.map_firstnames2gender(firstnames = firstname) 389 if gender is None: 390 return True 391 392 wx.CallAfter(self._PRW_gender.SetData, gender) 393 394 return True
395 #----------------------------------------------------------------
396 - def _on_leaving_dob(self):
397 _validate_dob_field(self._PRW_dob) 398 wx.CallAfter(self._refresh_dupe_warning)
399 #----------------------------------------------------------------
400 - def _on_leaving_zip(self):
401 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode') 402 403 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), '') 404 self._PRW_street.set_context(context = 'zip', val = zip_code) 405 self._PRW_urb.set_context(context = 'zip', val = zip_code) 406 self._PRW_region.set_context(context = 'zip', val = zip_code) 407 self._PRW_country.set_context(context = 'zip', val = zip_code) 408 409 return True
410 #----------------------------------------------------------------
411 - def _on_leaving_country(self):
412 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country') 413 414 country = gmTools.none_if(self._PRW_country.GetValue().strip(), '') 415 self._PRW_region.set_context(context = 'country', val = country) 416 417 return True
418 #----------------------------------------------------------------
419 - def _on_leaving_number(self, evt):
420 if self._TCTRL_number.GetValue().strip() == '': 421 adr = self._PRW_address_searcher.address 422 if adr is None: 423 return True 424 self._TCTRL_number.SetValue(adr['number']) 425 return True 426 427 self.__perhaps_invalidate_address_searcher(self._TCTRL_number, 'number') 428 return True
429 #----------------------------------------------------------------
430 - def _on_leaving_unit(self, evt):
431 if self._TCTRL_unit.GetValue().strip() == '': 432 adr = self._PRW_address_searcher.address 433 if adr is None: 434 return True 435 self._TCTRL_unit.SetValue(gmTools.coalesce(adr['subunit'], '')) 436 return True 437 438 self.__perhaps_invalidate_address_searcher(self._TCTRL_unit, 'subunit') 439 return True
440 #----------------------------------------------------------------
441 - def _invalidate_address_searcher(self, *args, **kwargs):
442 mapping = [ 443 (self._PRW_street, 'street'), 444 (self._PRW_urb, 'urb'), 445 (self._PRW_region, 'l10n_region') 446 ] 447 # loop through fields and invalidate address searcher if different 448 for ctrl, field in mapping: 449 if self.__perhaps_invalidate_address_searcher(ctrl, field): 450 return True 451 452 return True
453 #----------------------------------------------------------------
455 if self._PRW_address_searcher.address is None: 456 return True 457 458 wx.CallAfter(self.__set_fields_from_address_searcher) 459 return True
460 #---------------------------------------------------------------- 461 # generic Edit Area mixin API 462 #----------------------------------------------------------------
463 - def _valid_for_save(self):
464 if self._PRW_primary_provider.GetValue().strip() == '': 465 self._PRW_primary_provider.display_as_valid(True) 466 else: 467 if self._PRW_primary_provider.GetData() is None: 468 self._PRW_primary_provider.display_as_valid(False) 469 else: 470 self._PRW_primary_provider.display_as_valid(True) 471 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
472 #----------------------------------------------------------------
473 - def _save_as_new(self):
474 475 if self._PRW_dob.GetValue().strip() == '': 476 if not _empty_dob_allowed(): 477 self._PRW_dob.display_as_valid(False) 478 self._PRW_dob.SetFocus() 479 return False 480 481 # identity 482 new_identity = gmPerson.create_identity ( 483 gender = self._PRW_gender.GetData(), 484 dob = self._PRW_dob.GetData(), 485 lastnames = self._PRW_lastname.GetValue().strip(), 486 firstnames = self._PRW_firstnames.GetValue().strip(), 487 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '') 488 ) 489 if new_identity is None: 490 gmGuiHelpers.gm_show_error ( 491 title = _('Creating person.'), 492 error = _( 493 'Failed to create person. Does it already exist ?\n' 494 '\n' 495 'If so you need to add a unique comment.' 496 ) 497 ) 498 return False 499 _log.info('identity created: %s' % new_identity) 500 501 new_identity['dob_is_estimated'] = self._CHBOX_estimated_dob.GetValue() 502 val = self._TCTRL_tob.GetValue().strip() 503 if val != '': 504 new_identity['tob'] = pydt.time(int(val[:2]), int(val[3:5])) 505 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip()) 506 507 prov = self._PRW_primary_provider.GetData() 508 if prov is not None: 509 new_identity['pk_primary_provider'] = prov 510 #new_identity['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '') 511 new_identity.save() 512 _log.info('new identity updated: %s' % new_identity) 513 514 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), '')) 515 _log.info('nickname set on new identity: %s' % new_identity) 516 517 # address 518 # if we reach this the address cannot be completely empty 519 is_valid = self.__address_valid_for_save(empty_address_is_valid = False) 520 if is_valid is True: 521 # because we currently only check for non-emptiness 522 # we must still deal with database errors 523 try: 524 new_identity.link_address ( 525 number = self._TCTRL_number.GetValue().strip(), 526 street = self._PRW_street.GetValue().strip(), 527 postcode = self._PRW_zip.GetValue().strip(), 528 urb = self._PRW_urb.GetValue().strip(), 529 region_code = self._PRW_region.GetData(), 530 country_code = self._PRW_country.GetData(), 531 subunit = gmTools.none_if(self._TCTRL_unit.GetValue().strip(), ''), 532 id_type = self._PRW_type.GetData() 533 ) 534 except gmPG2.dbapi.InternalError: 535 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip()) 536 _log.debug('(sub)unit: >>%s<<', self._TCTRL_unit.GetValue().strip()) 537 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip()) 538 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip()) 539 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip()) 540 _log.debug('region: >>%s<<', self._PRW_region.GetData().strip()) 541 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip()) 542 _log.exception('cannot link address') 543 gmGuiHelpers.gm_show_error ( 544 aTitle = _('Saving address'), 545 aMessage = _( 546 'Cannot save this address.\n' 547 '\n' 548 'You will have to add it via the Demographics plugin.\n' 549 ) 550 ) 551 elif is_valid is False: 552 gmGuiHelpers.gm_show_error ( 553 aTitle = _('Saving address'), 554 aMessage = _( 555 'Address not saved.\n' 556 '\n' 557 'You will have to add it via the Demographics plugin.\n' 558 ) 559 ) 560 # else it is None which means empty address which we ignore 561 562 # phone 563 channel_name = self._PRW_channel_type.GetValue().strip() 564 pk_channel_type = self._PRW_channel_type.GetData() 565 if pk_channel_type is None: 566 if channel_name == '': 567 channel_name = 'homephone' 568 new_identity.link_comm_channel ( 569 comm_medium = channel_name, 570 pk_channel_type = pk_channel_type, 571 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), ''), 572 is_confidential = False 573 ) 574 575 # external ID 576 pk_type = self._PRW_external_id_type.GetData() 577 id_value = self._TCTRL_external_id_value.GetValue().strip() 578 if (pk_type is not None) and (id_value != ''): 579 new_identity.add_external_id(value = id_value, pk_type = pk_type) 580 581 # occupation 582 new_identity.link_occupation ( 583 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), '') 584 ) 585 586 self.data = new_identity 587 return True
588 #----------------------------------------------------------------
589 - def _save_as_update(self):
590 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
591 #----------------------------------------------------------------
592 - def _refresh_as_new(self):
593 # FIXME: button "empty out" 594 return
595 #----------------------------------------------------------------
596 - def _refresh_from_existing(self):
597 return # there is no forward button so nothing to do here
598 #----------------------------------------------------------------
600 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
601 602 #============================================================ 603 # main 604 #------------------------------------------------------------ 605 if __name__ == "__main__": 606 607 if len(sys.argv) < 2: 608 sys.exit() 609 610 if sys.argv[1] != 'test': 611 sys.exit() 612 613 # from Gnumed.pycommon import gmPG2 614 # from Gnumed.pycommon import gmI18N 615 # gmI18N.activate_locale() 616 # gmI18N.install_domain() 617 618 #-------------------------------------------------------- 619 #test_org_unit_prw() 620