1 """Widgets dealing with patient demographics."""
2
3
4
5 __version__ = "$Revision: 1.173 $"
6 __author__ = "R.Terry, SJ Tan, I Haywood, Carlos Moro <cfmoro1976@yahoo.es>"
7 __license__ = 'GPL (details at http://www.gnu.org)'
8
9
10 import time, string, sys, os, datetime as pyDT, csv, codecs, re as regex, psycopg2, logging
11
12
13 import wx
14 import wx.wizard
15
16
17
18 if __name__ == '__main__':
19 sys.path.insert(0, '../../')
20 from Gnumed.pycommon import gmDispatcher, gmI18N, gmMatchProvider, gmPG2, gmTools, gmDateTime, gmShellAPI
21 from Gnumed.business import gmDemographicRecord, gmPerson
22 from Gnumed.wxpython import gmPlugin, gmPhraseWheel, gmGuiHelpers, gmDateTimeInput
23 from Gnumed.wxpython import gmRegetMixin, gmDataMiningWidgets, gmListWidgets, gmEditArea
24 from Gnumed.wxpython import gmAuthWidgets
25 from Gnumed.wxGladeWidgets import wxgGenericAddressEditAreaPnl, wxgPersonContactsManagerPnl, wxgPersonIdentityManagerPnl
26 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl, wxgCommChannelEditAreaPnl, wxgExternalIDEditAreaPnl
27
28
29
30 _log = logging.getLogger('gm.ui')
31
32
33 try:
34 _('dummy-no-need-to-translate-but-make-epydoc-happy')
35 except NameError:
36 _ = lambda x:x
37
38
39
40
42 ea = cProvinceEAPnl(parent = parent, id = -1, province = province)
43 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (province is not None))
44 dlg.SetTitle(gmTools.coalesce(province, _('Adding province'), _('Editing province')))
45 result = dlg.ShowModal()
46 dlg.Destroy()
47 return (result == wx.ID_OK)
48
50
51 msg = _(
52 'Are you sure you want to delete this province ?\n'
53 '\n'
54 'Deletion will only work if this province is not\n'
55 'yet in use in any patient addresses.'
56 )
57
58 tt = _(
59 'Also delete any towns/cities/villages known\n'
60 'to be situated in this state as long as\n'
61 'no patients are recorded to live there.'
62 )
63
64 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
65 parent,
66 -1,
67 caption = _('Deleting province'),
68 question = msg,
69 show_checkbox = True,
70 checkbox_msg = _('delete related townships'),
71 checkbox_tooltip = tt,
72 button_defs = [
73 {'label': _('Yes, delete'), 'tooltip': _('Delete province and possibly related townships.'), 'default': False},
74 {'label': _('No'), 'tooltip': _('No, do NOT delete anything.'), 'default': True}
75 ]
76 )
77
78 decision = dlg.ShowModal()
79 if decision != wx.ID_YES:
80 dlg.Destroy()
81 return False
82
83 include_urbs = dlg.checkbox_is_checked()
84 dlg.Destroy()
85
86 return gmDemographicRecord.delete_province(province = province, delete_urbs = include_urbs)
87
89
90 if parent is None:
91 parent = wx.GetApp().GetTopWindow()
92
93
94 def delete(province=None):
95 return delete_province(parent = parent, province = province['pk_state'])
96
97 def edit(province=None):
98 return edit_province(parent = parent, province = province)
99
100 def refresh(lctrl):
101 wx.BeginBusyCursor()
102 provinces = gmDemographicRecord.get_provinces()
103 lctrl.set_string_items(provinces)
104 lctrl.set_data(provinces)
105 wx.EndBusyCursor()
106
107 msg = _(
108 '\n'
109 'This list shows the provinces known to GNUmed.\n'
110 '\n'
111 'In your jurisdiction "province" may correspond to either of "state",\n'
112 '"county", "region", "territory", or some such term.\n'
113 '\n'
114 'Select the province you want to edit !\n'
115 )
116
117 gmListWidgets.get_choices_from_list (
118 parent = parent,
119 msg = msg,
120 caption = _('Editing provinces ...'),
121 columns = [_('Province'), _('Country')],
122 single_selection = True,
123 new_callback = edit,
124
125 delete_callback = delete,
126 refresh_callback = refresh
127 )
128
130
132
133 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
134
135 context = {
136 u'ctxt_country_name': {
137 u'where_part': u'and l10n_country ilike %(country_name)s or country ilike %(country_name)s',
138 u'placeholder': u'country_name'
139 },
140 u'ctxt_zip': {
141 u'where_part': u'and zip ilike %(zip)s',
142 u'placeholder': u'zip'
143 },
144 u'ctxt_country_code': {
145 u'where_part': u'and country in (select code from dem.country where _(name) ilike %(country_name)s or name ilike %(country_name)s)',
146 u'placeholder': u'country_name'
147 }
148 }
149
150 query = u"""
151 select code, name from (
152 select distinct on (name) code, name, rank from (
153 -- 1: find states based on name, context: zip and country name
154 select
155 code_state as code, state as name, 1 as rank
156 from dem.v_zip2data
157 where
158 state %(fragment_condition)s
159 %(ctxt_country_name)s
160 %(ctxt_zip)s
161
162 union all
163
164 -- 2: find states based on code, context: zip and country name
165 select
166 code_state as code, state as name, 2 as rank
167 from dem.v_zip2data
168 where
169 code_state %(fragment_condition)s
170 %(ctxt_country_name)s
171 %(ctxt_zip)s
172
173 union all
174
175 -- 3: find states based on name, context: country
176 select
177 code as code, name as name, 3 as rank
178 from dem.state
179 where
180 name %(fragment_condition)s
181 %(ctxt_country_code)s
182
183 union all
184
185 -- 4: find states based on code, context: country
186 select
187 code as code, name as name, 3 as rank
188 from dem.state
189 where
190 code %(fragment_condition)s
191 %(ctxt_country_code)s
192
193 ) as q2
194 ) as q1 order by rank, name limit 50"""
195
196 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context)
197 mp.setThresholds(2, 5, 6)
198 mp.word_separators = u'[ \t]+'
199 self.matcher = mp
200
201 self.unset_context(context = u'zip')
202 self.unset_context(context = u'country_name')
203 self.SetToolTipString(_('Type or select a state/region/province/territory.'))
204 self.capitalisation_mode = gmTools.CAPS_FIRST
205 self.selection_only = True
206
207 from Gnumed.wxGladeWidgets import wxgProvinceEAPnl
208
209 -class cProvinceEAPnl(wxgProvinceEAPnl.wxgProvinceEAPnl, gmEditArea.cGenericEditAreaMixin):
210
228
230 self._PRW_province.selection_only = False
231
232
233
261
277
279
280
281
282
283
284
285
286 return True
287
294
296 self._PRW_province.SetText(self.data['l10n_state'], self.data['code_state'])
297 self._TCTRL_code.SetValue(self.data['code_state'])
298 self._PRW_country.SetText(self.data['l10n_country'], self.data['code_country'])
299
300 self._PRW_province.SetFocus()
301
308
309
311
313
314 kwargs['message'] = _("Today's KOrganizer appointments ...")
315 kwargs['button_defs'] = [
316 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')},
317 {'label': u''},
318 {'label': u''},
319 {'label': u''},
320 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')}
321 ]
322 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs)
323
324 self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv'))
325 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
326
327
331
341
343 try: os.remove(self.fname)
344 except OSError: pass
345 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True)
346 try:
347 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace')
348 except IOError:
349 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True)
350 return
351
352 csv_lines = gmTools.unicode_csv_reader (
353 csv_file,
354 delimiter = ','
355 )
356
357 self._LCTRL_items.set_columns ([
358 _('Place'),
359 _('Start'),
360 u'',
361 u'',
362 _('Patient'),
363 _('Comment')
364 ])
365 items = []
366 data = []
367 for line in csv_lines:
368 items.append([line[5], line[0], line[1], line[3], line[4], line[6]])
369 data.append([line[4], line[7]])
370
371 self._LCTRL_items.set_string_items(items = items)
372 self._LCTRL_items.set_column_widths()
373 self._LCTRL_items.set_data(data = data)
374 self._LCTRL_items.patient_key = 0
375
376
377
380
382
383 pat = gmPerson.gmCurrentPatient()
384 curr_jobs = pat.get_occupations()
385 if len(curr_jobs) > 0:
386 old_job = curr_jobs[0]['l10n_occupation']
387 update = curr_jobs[0]['modified_when'].strftime('%m/%Y')
388 else:
389 old_job = u''
390 update = u''
391
392 msg = _(
393 'Please enter the primary occupation of the patient.\n'
394 '\n'
395 'Currently recorded:\n'
396 '\n'
397 ' %s (last updated %s)'
398 ) % (old_job, update)
399
400 new_job = wx.GetTextFromUser (
401 message = msg,
402 caption = _('Editing primary occupation'),
403 default_value = old_job,
404 parent = None
405 )
406 if new_job.strip() == u'':
407 return
408
409 for job in curr_jobs:
410
411 if job['l10n_occupation'] != new_job:
412 pat.unlink_occupation(occupation = job['l10n_occupation'])
413
414 pat.link_occupation(occupation = new_job)
415
417
418 go_ahead = gmGuiHelpers.gm_show_question (
419 _('Are you sure you really, positively want\n'
420 'to disable the following person ?\n'
421 '\n'
422 ' %s %s %s\n'
423 ' born %s\n'
424 '\n'
425 '%s\n'
426 ) % (
427 identity['firstnames'],
428 identity['lastnames'],
429 identity['gender'],
430 identity['dob'],
431 gmTools.bool2subst (
432 identity.is_patient,
433 _('This patient DID receive care.'),
434 _('This person did NOT receive care.')
435 )
436 ),
437 _('Disabling person')
438 )
439 if not go_ahead:
440 return True
441
442
443 conn = gmAuthWidgets.get_dbowner_connection (
444 procedure = _('Disabling patient')
445 )
446
447 if conn is False:
448 return True
449
450 if conn is None:
451 return False
452
453
454 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}])
455
456 return True
457
458
459
461 """A list for managing a person's addresses.
462
463 Does NOT act on/listen to the current patient.
464 """
482
483
484
485 - def refresh(self, *args, **kwargs):
486 if self.__identity is None:
487 self._LCTRL_items.set_string_items()
488 return
489
490 adrs = self.__identity.get_addresses()
491 self._LCTRL_items.set_string_items (
492 items = [ [
493 a['l10n_address_type'],
494 a['street'],
495 gmTools.coalesce(a['notes_street'], u''),
496 a['number'],
497 gmTools.coalesce(a['subunit'], u''),
498 a['postcode'],
499 a['urb'],
500 gmTools.coalesce(a['suburb'], u''),
501 a['l10n_state'],
502 a['l10n_country'],
503 gmTools.coalesce(a['notes_subunit'], u'')
504 ] for a in adrs
505 ]
506 )
507 self._LCTRL_items.set_column_widths()
508 self._LCTRL_items.set_data(data = adrs)
509
510
511
513 self._LCTRL_items.SetToolTipString(_('List of known addresses.'))
514 self._LCTRL_items.set_columns(columns = [
515 _('Type'),
516 _('Street'),
517 _('Street info'),
518 _('Number'),
519 _('Subunit'),
520 _('Postal code'),
521 _('Place'),
522 _('Suburb'),
523 _('Region'),
524 _('Country'),
525 _('Comment')
526 ])
527
536
538 ea = cAddressEditAreaPnl(self, -1, address = address)
539 ea.identity = self.__identity
540 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
541 dlg.SetTitle(_('Editing address'))
542 if dlg.ShowModal() == wx.ID_OK:
543
544
545 if ea.address['pk_address'] != address['pk_address']:
546 self.__identity.unlink_address(address = address)
547 return True
548 return False
549
551 go_ahead = gmGuiHelpers.gm_show_question (
552 _( 'Are you sure you want to remove this\n'
553 "address from the patient's addresses ?\n"
554 '\n'
555 'The address itself will not be deleted\n'
556 'but it will no longer be associated with\n'
557 'this patient.'
558 ),
559 _('Removing address')
560 )
561 if not go_ahead:
562 return False
563 self.__identity.unlink_address(address = address)
564 return True
565
566
567
569 return self.__identity
570
574
575 identity = property(_get_identity, _set_identity)
576
609
611 """An edit area for editing/creating an address.
612
613 Does NOT act on/listen to the current patient.
614 """
616 try:
617 self.address = kwargs['address']
618 del kwargs['address']
619 except KeyError:
620 self.address = None
621
622 wxgGenericAddressEditAreaPnl.wxgGenericAddressEditAreaPnl.__init__(self, *args, **kwargs)
623
624 self.identity = None
625
626 self.__register_interests()
627 self.refresh()
628
629
630
631 - def refresh(self, address = None):
632 if address is not None:
633 self.address = address
634
635 if self.address is not None:
636 self._PRW_type.SetText(self.address['l10n_address_type'])
637 self._PRW_zip.SetText(self.address['postcode'])
638 self._PRW_street.SetText(self.address['street'], data = self.address['street'])
639 self._TCTRL_notes_street.SetValue(gmTools.coalesce(self.address['notes_street'], ''))
640 self._TCTRL_number.SetValue(self.address['number'])
641 self._TCTRL_subunit.SetValue(gmTools.coalesce(self.address['subunit'], ''))
642 self._PRW_suburb.SetText(gmTools.coalesce(self.address['suburb'], ''))
643 self._PRW_urb.SetText(self.address['urb'], data = self.address['urb'])
644 self._PRW_state.SetText(self.address['l10n_state'], data = self.address['code_state'])
645 self._PRW_country.SetText(self.address['l10n_country'], data = self.address['code_country'])
646 self._TCTRL_notes_subunit.SetValue(gmTools.coalesce(self.address['notes_subunit'], ''))
647
648
649
650
652 """Links address to patient, creating new address if necessary"""
653
654 if not self.__valid_for_save():
655 return False
656
657
658 try:
659 adr = self.identity.link_address (
660 number = self._TCTRL_number.GetValue().strip(),
661 street = self._PRW_street.GetValue().strip(),
662 postcode = self._PRW_zip.GetValue().strip(),
663 urb = self._PRW_urb.GetValue().strip(),
664 state = self._PRW_state.GetData(),
665 country = self._PRW_country.GetData(),
666 subunit = gmTools.none_if(self._TCTRL_subunit.GetValue().strip(), u''),
667 suburb = gmTools.none_if(self._PRW_suburb.GetValue().strip(), u''),
668 id_type = self._PRW_type.GetData()
669 )
670 except:
671 _log.exception('cannot save address')
672 gmGuiHelpers.gm_show_error (
673 _('Cannot save address.\n\n'
674 'Does the state [%s]\n'
675 'exist in country [%s] ?'
676 ) % (
677 self._PRW_state.GetValue().strip(),
678 self._PRW_country.GetValue().strip()
679 ),
680 _('Saving address')
681 )
682 return False
683
684 notes = self._TCTRL_notes_street.GetValue().strip()
685 if notes != u'':
686 adr['notes_street'] = notes
687 notes = self._TCTRL_notes_subunit.GetValue().strip()
688 if notes != u'':
689 adr['notes_subunit'] = notes
690 adr.save_payload()
691
692 self.address = adr
693
694 return True
695
696
697
701
703 """Set the street, town, state and country according to entered zip code."""
704 zip_code = self._PRW_zip.GetValue()
705 if zip_code.strip() == u'':
706 self._PRW_street.unset_context(context = u'zip')
707 self._PRW_urb.unset_context(context = u'zip')
708 self._PRW_state.unset_context(context = u'zip')
709 self._PRW_country.unset_context(context = u'zip')
710 else:
711 self._PRW_street.set_context(context = u'zip', val = zip_code)
712 self._PRW_urb.set_context(context = u'zip', val = zip_code)
713 self._PRW_state.set_context(context = u'zip', val = zip_code)
714 self._PRW_country.set_context(context = u'zip', val = zip_code)
715
717 """Set the states according to entered country."""
718 country = self._PRW_country.GetData()
719 if country is None:
720 self._PRW_state.unset_context(context = 'country')
721 else:
722 self._PRW_state.set_context(context = 'country', val = country)
723
724
725
727
728
729 is_any_field_filled = False
730
731 required_fields = (
732 self._PRW_type,
733 self._PRW_zip,
734 self._PRW_street,
735 self._TCTRL_number,
736 self._PRW_urb
737 )
738 for field in required_fields:
739 if len(field.GetValue().strip()) == 0:
740 if is_any_field_filled:
741 field.SetBackgroundColour('pink')
742 field.SetFocus()
743 field.Refresh()
744 gmGuiHelpers.gm_show_error (
745 _('Address details must be filled in completely or not at all.'),
746 _('Saving contact data')
747 )
748 return False
749 else:
750 is_any_field_filled = True
751 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
752 field.Refresh()
753
754 required_fields = (
755 self._PRW_state,
756 self._PRW_country
757 )
758 for field in required_fields:
759 if field.GetData() is None:
760 if is_any_field_filled:
761 field.SetBackgroundColour('pink')
762 field.SetFocus()
763 field.Refresh()
764 gmGuiHelpers.gm_show_error (
765 _('Address details must be filled in completely or not at all.'),
766 _('Saving contact data')
767 )
768 return False
769 else:
770 is_any_field_filled = True
771 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
772 field.Refresh()
773
774 return True
775
777
779
780 query = u"""
781 select * from (
782 (select
783 pk_address,
784 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', '
785 || urb || coalesce(' (' || suburb || ')', '') || ', '
786 || postcode
787 || coalesce(', ' || notes_street, '')
788 || coalesce(', ' || notes_subunit, '')
789 ) as address
790 from
791 dem.v_address
792 where
793 street %(fragment_condition)s
794
795 ) union (
796
797 select
798 pk_address,
799 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', '
800 || urb || coalesce(' (' || suburb || ')', '') || ', '
801 || postcode
802 || coalesce(', ' || notes_street, '')
803 || coalesce(', ' || notes_subunit, '')
804 ) as address
805 from
806 dem.v_address
807 where
808 postcode_street %(fragment_condition)s
809
810 ) union (
811
812 select
813 pk_address,
814 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', '
815 || urb || coalesce(' (' || suburb || ')', '') || ', '
816 || postcode
817 || coalesce(', ' || notes_street, '')
818 || coalesce(', ' || notes_subunit, '')
819 ) as address
820 from
821 dem.v_address
822 where
823 postcode_urb %(fragment_condition)s
824 )
825 ) as union_result
826 order by union_result.address limit 50"""
827
828 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = query)
829
830 self.setThresholds(2, 4, 6)
831
832
833
835
849
851
852 pk = self.GetData()
853
854 if pk is None:
855 self.__address = None
856 return None
857
858 if self.__address is None:
859 self.__old_pk = pk
860 self.__address = gmDemographicRecord.cAddress(aPK_obj = pk)
861 else:
862 if pk != self.__old_pk:
863 self.__old_pk = pk
864 self.__address = gmDemographicRecord.cAddress(aPK_obj = pk)
865
866 return self.__address
867
869
871
872 query = u"""
873 select id, type from ((
874 select id, _(name) as type, 1 as rank
875 from dem.address_type
876 where _(name) %(fragment_condition)s
877 ) union (
878 select id, name as type, 2 as rank
879 from dem.address_type
880 where name %(fragment_condition)s
881 )) as ur
882 order by
883 ur.rank, ur.type
884 """
885 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
886 mp.setThresholds(1, 2, 4)
887 mp.word_separators = u'[ \t]+'
888 gmPhraseWheel.cPhraseWheel.__init__ (
889 self,
890 *args,
891 **kwargs
892 )
893 self.matcher = mp
894 self.SetToolTipString(_('Select the type of address.'))
895
896 self.selection_only = True
897
898
899
900
901
902
903
905
907
908 query = u"""
909 (select distinct postcode, postcode from dem.street where postcode %(fragment_condition)s limit 20)
910 union
911 (select distinct postcode, postcode from dem.urb where postcode %(fragment_condition)s limit 20)"""
912 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
913 mp.setThresholds(2, 3, 15)
914 gmPhraseWheel.cPhraseWheel.__init__ (
915 self,
916 *args,
917 **kwargs
918 )
919 self.SetToolTipString(_("Type or select a zip code (postcode)."))
920 self.matcher = mp
921
923
925 context = {
926 u'ctxt_zip': {
927 u'where_part': u'and zip ilike %(zip)s',
928 u'placeholder': u'zip'
929 }
930 }
931 query = u"""
932 select s1, s2 from (
933 select s1, s2, rank from (
934 select distinct on (street)
935 street as s1, street as s2, 1 as rank
936 from dem.v_zip2data
937 where
938 street %(fragment_condition)s
939 %(ctxt_zip)s
940
941 union all
942
943 select distinct on (name)
944 name as s1, name as s2, 2 as rank
945 from dem.street
946 where
947 name %(fragment_condition)s
948
949 ) as q2
950 ) as q1 order by rank, s2 limit 50"""
951 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context)
952 mp.setThresholds(3, 5, 8)
953 gmPhraseWheel.cPhraseWheel.__init__ (
954 self,
955 *args,
956 **kwargs
957 )
958 self.unset_context(context = u'zip')
959
960 self.SetToolTipString(_('Type or select a street.'))
961 self.capitalisation_mode = gmTools.CAPS_FIRST
962 self.matcher = mp
963
986
988
990 context = {
991 u'ctxt_zip': {
992 u'where_part': u'and zip ilike %(zip)s',
993 u'placeholder': u'zip'
994 }
995 }
996 query = u"""
997 select u1, u2 from (
998 select distinct on (rank, u1)
999 u1, u2, rank
1000 from (
1001 select
1002 urb as u1, urb as u2, 1 as rank
1003 from dem.v_zip2data
1004 where
1005 urb %(fragment_condition)s
1006 %(ctxt_zip)s
1007
1008 union all
1009
1010 select
1011 name as u1, name as u2, 2 as rank
1012 from dem.urb
1013 where
1014 name %(fragment_condition)s
1015 ) as union_result
1016 order by rank, u1
1017 ) as distincted_union
1018 limit 50
1019 """
1020 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context)
1021 mp.setThresholds(3, 5, 7)
1022 gmPhraseWheel.cPhraseWheel.__init__ (
1023 self,
1024 *args,
1025 **kwargs
1026 )
1027 self.unset_context(context = u'zip')
1028
1029 self.SetToolTipString(_('Type or select a city/town/village/dwelling.'))
1030 self.capitalisation_mode = gmTools.CAPS_FIRST
1031 self.matcher = mp
1032
1034
1035
1037
1038 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1039
1040 context = {
1041 u'ctxt_zip': {
1042 u'where_part': u'and zip ilike %(zip)s',
1043 u'placeholder': u'zip'
1044 }
1045 }
1046 query = u"""
1047 select code, name from (
1048 select distinct on (code, name) code, (name || ' (' || code || ')') as name, rank from (
1049
1050 -- localized to user
1051
1052 select
1053 code_country as code, l10n_country as name, 1 as rank
1054 from dem.v_zip2data
1055 where
1056 l10n_country %(fragment_condition)s
1057 %(ctxt_zip)s
1058
1059 union all
1060
1061 select
1062 code as code, _(name) as name, 2 as rank
1063 from dem.country
1064 where
1065 _(name) %(fragment_condition)s
1066
1067 union all
1068
1069 -- non-localized
1070
1071 select
1072 code_country as code, country as name, 3 as rank
1073 from dem.v_zip2data
1074 where
1075 country %(fragment_condition)s
1076 %(ctxt_zip)s
1077
1078 union all
1079
1080 select
1081 code as code, name as name, 4 as rank
1082 from dem.country
1083 where
1084 name %(fragment_condition)s
1085
1086 ) as q2
1087 ) as q1 order by rank, name limit 25"""
1088 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context)
1089 mp.setThresholds(2, 5, 9)
1090 self.matcher = mp
1091
1092 self.unset_context(context = u'zip')
1093 self.SetToolTipString(_('Type or select a country.'))
1094 self.capitalisation_mode = gmTools.CAPS_FIRST
1095 self.selection_only = True
1096
1097
1098
1100
1102
1103 query = u"""
1104 select pk, type from ((
1105 select pk, _(description) as type, 1 as rank
1106 from dem.enum_comm_types
1107 where _(description) %(fragment_condition)s
1108 ) union (
1109 select pk, description as type, 2 as rank
1110 from dem.enum_comm_types
1111 where description %(fragment_condition)s
1112 )) as ur
1113 order by
1114 ur.rank, ur.type
1115 """
1116 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1117 mp.setThresholds(1, 2, 4)
1118 mp.word_separators = u'[ \t]+'
1119 gmPhraseWheel.cPhraseWheel.__init__ (
1120 self,
1121 *args,
1122 **kwargs
1123 )
1124 self.matcher = mp
1125 self.SetToolTipString(_('Select the type of communications channel.'))
1126 self.selection_only = True
1127
1129 """An edit area for editing/creating a comms channel.
1130
1131 Does NOT act on/listen to the current patient.
1132 """
1134 try:
1135 self.channel = kwargs['comm_channel']
1136 del kwargs['comm_channel']
1137 except KeyError:
1138 self.channel = None
1139
1140 wxgCommChannelEditAreaPnl.wxgCommChannelEditAreaPnl.__init__(self, *args, **kwargs)
1141
1142 self.identity = None
1143
1144 self.refresh()
1145
1146
1147
1148 - def refresh(self, comm_channel = None):
1149 if comm_channel is not None:
1150 self.channel = comm_channel
1151
1152 if self.channel is None:
1153 self._PRW_type.SetText(u'')
1154 self._TCTRL_url.SetValue(u'')
1155 self._PRW_address.SetText(value = u'', data = None)
1156 self._CHBOX_confidential.SetValue(False)
1157 else:
1158 self._PRW_type.SetText(self.channel['l10n_comm_type'])
1159 self._TCTRL_url.SetValue(self.channel['url'])
1160 self._PRW_address.SetData(data = self.channel['pk_address'])
1161 self._CHBOX_confidential.SetValue(self.channel['is_confidential'])
1162
1164 """Links comm channel to patient."""
1165 if self.channel is None:
1166 if not self.__valid_for_save():
1167 return False
1168 try:
1169 self.channel = self.identity.link_comm_channel (
1170 pk_channel_type = self._PRW_type.GetData(),
1171 url = self._TCTRL_url.GetValue().strip(),
1172 is_confidential = self._CHBOX_confidential.GetValue(),
1173 )
1174 except psycopg2.IntegrityError:
1175 _log.exception('error saving comm channel')
1176 gmDispatcher.send(signal = u'statustext', msg = _('Cannot save communications channel.'), beep = True)
1177 return False
1178 else:
1179 comm_type = self._PRW_type.GetValue().strip()
1180 if comm_type != u'':
1181 self.channel['comm_type'] = comm_type
1182 url = self._TCTRL_url.GetValue().strip()
1183 if url != u'':
1184 self.channel['url'] = url
1185 self.channel['is_confidential'] = self._CHBOX_confidential.GetValue()
1186
1187 self.channel['pk_address'] = self._PRW_address.GetData()
1188 self.channel.save_payload()
1189
1190 return True
1191
1192
1193
1195
1196 no_errors = True
1197
1198 if self._PRW_type.GetData() is None:
1199 self._PRW_type.SetBackgroundColour('pink')
1200 self._PRW_type.SetFocus()
1201 self._PRW_type.Refresh()
1202 no_errors = False
1203 else:
1204 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1205 self._PRW_type.Refresh()
1206
1207 if self._TCTRL_url.GetValue().strip() == u'':
1208 self._TCTRL_url.SetBackgroundColour('pink')
1209 self._TCTRL_url.SetFocus()
1210 self._TCTRL_url.Refresh()
1211 no_errors = False
1212 else:
1213 self._TCTRL_url.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1214 self._TCTRL_url.Refresh()
1215
1216 return no_errors
1217
1219 """A list for managing a person's comm channels.
1220
1221 Does NOT act on/listen to the current patient.
1222 """
1240
1241
1242
1243 - def refresh(self, *args, **kwargs):
1254
1255
1256
1258 self._LCTRL_items.SetToolTipString(_('List of known communication channels.'))
1259 self._LCTRL_items.set_columns(columns = [
1260 _('confidential'),
1261 _('Type'),
1262 _('Value')
1263 ])
1264
1273
1282
1284 go_ahead = gmGuiHelpers.gm_show_question (
1285 _( 'Are you sure this patient can no longer\n'
1286 "be contacted via this channel ?"
1287 ),
1288 _('Removing communication channel')
1289 )
1290 if not go_ahead:
1291 return False
1292 self.__identity.unlink_comm_channel(comm_channel = comm)
1293 return True
1294
1295
1296
1298 return self.__identity
1299
1303
1304 identity = property(_get_identity, _set_identity)
1305
1306
1307
1308
1309
1324
1326
1328 query = u"""
1329 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
1330 union
1331 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
1332 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1333 mp.setThresholds(3, 5, 9)
1334 gmPhraseWheel.cPhraseWheel.__init__ (
1335 self,
1336 *args,
1337 **kwargs
1338 )
1339 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name)."))
1340 self.capitalisation_mode = gmTools.CAPS_NAMES
1341 self.matcher = mp
1342
1344
1346 query = u"""
1347 (select distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20)
1348 union
1349 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
1350 union
1351 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
1352 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1353 mp.setThresholds(3, 5, 9)
1354 gmPhraseWheel.cPhraseWheel.__init__ (
1355 self,
1356 *args,
1357 **kwargs
1358 )
1359 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name)."))
1360
1361
1362 self.matcher = mp
1363
1365
1367 query = u"select distinct title, title from dem.identity where title %(fragment_condition)s"
1368 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1369 mp.setThresholds(1, 3, 9)
1370 gmPhraseWheel.cPhraseWheel.__init__ (
1371 self,
1372 *args,
1373 **kwargs
1374 )
1375 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !"))
1376 self.matcher = mp
1377
1379 """Let user select a gender."""
1380
1381 _gender_map = None
1382
1384
1385 if cGenderSelectionPhraseWheel._gender_map is None:
1386 cmd = u"""
1387 select tag, l10n_label, sort_weight
1388 from dem.v_gender_labels
1389 order by sort_weight desc"""
1390 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
1391 cGenderSelectionPhraseWheel._gender_map = {}
1392 for gender in rows:
1393 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = {
1394 'data': gender[idx['tag']],
1395 'label': gender[idx['l10n_label']],
1396 'weight': gender[idx['sort_weight']]
1397 }
1398
1399 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values())
1400 mp.setThresholds(1, 1, 3)
1401
1402 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1403 self.selection_only = True
1404 self.matcher = mp
1405 self.picklist_delay = 50
1406
1421
1423
1425 query = u"""
1426 select distinct pk, (name || coalesce(' (%s ' || issuer || ')', '')) as label
1427 from dem.enum_ext_id_types
1428 where name %%(fragment_condition)s
1429 order by label limit 25""" % _('issued by')
1430 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1431 mp.setThresholds(1, 3, 5)
1432 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1433 self.SetToolTipString(_("Enter or select a type for the external ID."))
1434 self.matcher = mp
1435
1450
1451
1452
1454 """An edit area for editing/creating external IDs.
1455
1456 Does NOT act on/listen to the current patient.
1457 """
1459
1460 try:
1461 self.ext_id = kwargs['external_id']
1462 del kwargs['external_id']
1463 except:
1464 self.ext_id = None
1465
1466 wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl.__init__(self, *args, **kwargs)
1467
1468 self.identity = None
1469
1470 self.__register_events()
1471
1472 self.refresh()
1473
1474
1475
1477 if ext_id is not None:
1478 self.ext_id = ext_id
1479
1480 if self.ext_id is not None:
1481 self._PRW_type.SetText(value = self.ext_id['name'], data = self.ext_id['pk_type'])
1482 self._TCTRL_value.SetValue(self.ext_id['value'])
1483 self._PRW_issuer.SetText(self.ext_id['issuer'])
1484 self._TCTRL_comment.SetValue(gmTools.coalesce(self.ext_id['comment'], u''))
1485
1486
1487
1488
1490
1491 if not self.__valid_for_save():
1492 return False
1493
1494
1495 type = regex.split(' \(%s .+\)$' % _('issued by'), self._PRW_type.GetValue().strip(), 1)[0]
1496
1497
1498 if self.ext_id is None:
1499 self.identity.add_external_id (
1500 type_name = type,
1501 value = self._TCTRL_value.GetValue().strip(),
1502 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
1503 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1504 )
1505
1506 else:
1507 self.identity.update_external_id (
1508 pk_id = self.ext_id['pk_id'],
1509 type = type,
1510 value = self._TCTRL_value.GetValue().strip(),
1511 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
1512 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1513 )
1514
1515 return True
1516
1517
1518
1521
1523 """Set the issuer according to the selected type.
1524
1525 Matches are fetched from existing records in backend.
1526 """
1527 pk_curr_type = self._PRW_type.GetData()
1528 if pk_curr_type is None:
1529 return True
1530 rows, idx = gmPG2.run_ro_queries(queries = [{
1531 'cmd': u"select issuer from dem.enum_ext_id_types where pk = %s",
1532 'args': [pk_curr_type]
1533 }])
1534 if len(rows) == 0:
1535 return True
1536 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0])
1537 return True
1538
1540
1541 no_errors = True
1542
1543
1544
1545
1546 if self._PRW_type.GetValue().strip() == u'':
1547 self._PRW_type.SetBackgroundColour('pink')
1548 self._PRW_type.SetFocus()
1549 self._PRW_type.Refresh()
1550 else:
1551 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1552 self._PRW_type.Refresh()
1553
1554 if self._TCTRL_value.GetValue().strip() == u'':
1555 self._TCTRL_value.SetBackgroundColour('pink')
1556 self._TCTRL_value.SetFocus()
1557 self._TCTRL_value.Refresh()
1558 no_errors = False
1559 else:
1560 self._TCTRL_value.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1561 self._TCTRL_value.Refresh()
1562
1563 return no_errors
1564
1566 """An edit area for editing/creating name/gender/dob.
1567
1568 Does NOT act on/listen to the current patient.
1569 """
1571
1572 self.__name = kwargs['name']
1573 del kwargs['name']
1574 self.__identity = gmPerson.cIdentity(aPK_obj = self.__name['pk_identity'])
1575
1576 wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl.__init__(self, *args, **kwargs)
1577
1578 self.__register_interests()
1579 self.refresh()
1580
1581
1582
1584 if self.__name is None:
1585 return
1586
1587 self._PRW_title.SetText(gmTools.coalesce(self.__name['title'], u''))
1588 self._PRW_firstname.SetText(self.__name['firstnames'])
1589 self._PRW_lastname.SetText(self.__name['lastnames'])
1590 self._PRW_nick.SetText(gmTools.coalesce(self.__name['preferred'], u''))
1591 dob = self.__identity['dob']
1592 self._PRW_dob.SetText(value = dob.strftime('%Y-%m-%d %H:%M'), data = dob)
1593 self._PRW_gender.SetData(self.__name['gender'])
1594 self._CHBOX_active.SetValue(self.__name['active_name'])
1595 self._TCTRL_comment.SetValue(gmTools.coalesce(self.__name['comment'], u''))
1596
1597
1598
1599
1601
1602 if not self.__valid_for_save():
1603 return False
1604
1605 self.__identity['gender'] = self._PRW_gender.GetData()
1606 if self._PRW_dob.GetValue().strip() == u'':
1607 self.__identity['dob'] = None
1608 else:
1609 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt()
1610 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
1611 self.__identity.save_payload()
1612
1613 active = self._CHBOX_active.GetValue()
1614 first = self._PRW_firstname.GetValue().strip()
1615 last = self._PRW_lastname.GetValue().strip()
1616 old_nick = self.__name['preferred']
1617
1618
1619 old_name = self.__name['firstnames'] + self.__name['lastnames']
1620 if (first + last) != old_name:
1621 self.__name = self.__identity.add_name(first, last, active)
1622
1623 self.__name['active_name'] = active
1624 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
1625 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1626
1627 self.__name.save_payload()
1628
1629 return True
1630
1631
1632
1635
1637 """Set the gender according to entered firstname.
1638
1639 Matches are fetched from existing records in backend.
1640 """
1641 firstname = self._PRW_firstname.GetValue().strip()
1642 if firstname == u'':
1643 return True
1644 rows, idx = gmPG2.run_ro_queries(queries = [{
1645 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
1646 'args': [firstname]
1647 }])
1648 if len(rows) == 0:
1649 return True
1650 wx.CallAfter(self._PRW_gender.SetData, rows[0][0])
1651 return True
1652
1653
1654
1656
1657 error_found = True
1658
1659 if self._PRW_gender.GetData() is None:
1660 self._PRW_gender.SetBackgroundColour('pink')
1661 self._PRW_gender.Refresh()
1662 self._PRW_gender.SetFocus()
1663 error_found = False
1664 else:
1665 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1666 self._PRW_gender.Refresh()
1667
1668 if not self._PRW_dob.is_valid_timestamp():
1669 val = self._PRW_dob.GetValue().strip()
1670 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
1671 self._PRW_dob.SetBackgroundColour('pink')
1672 self._PRW_dob.Refresh()
1673 self._PRW_dob.SetFocus()
1674 error_found = False
1675 else:
1676 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1677 self._PRW_dob.Refresh()
1678
1679 if self._PRW_lastname.GetValue().strip() == u'':
1680 self._PRW_lastname.SetBackgroundColour('pink')
1681 self._PRW_lastname.Refresh()
1682 self._PRW_lastname.SetFocus()
1683 error_found = False
1684 else:
1685 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1686 self._PRW_lastname.Refresh()
1687
1688 if self._PRW_firstname.GetValue().strip() == u'':
1689 self._PRW_firstname.SetBackgroundColour('pink')
1690 self._PRW_firstname.Refresh()
1691 self._PRW_firstname.SetFocus()
1692 error_found = False
1693 else:
1694 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1695 self._PRW_firstname.Refresh()
1696
1697 return error_found
1698
1699
1700
1702 """A list for managing a person's names.
1703
1704 Does NOT act on/listen to the current patient.
1705 """
1723
1724
1725
1726 - def refresh(self, *args, **kwargs):
1727 if self.__identity is None:
1728 self._LCTRL_items.set_string_items()
1729 return
1730
1731 names = self.__identity.get_names()
1732 self._LCTRL_items.set_string_items (
1733 items = [ [
1734 gmTools.bool2str(n['active_name'], 'X', ''),
1735 gmTools.coalesce(n['title'], gmPerson.map_gender2salutation(n['gender'])),
1736 n['lastnames'],
1737 n['firstnames'],
1738 gmTools.coalesce(n['preferred'], u''),
1739 gmTools.coalesce(n['comment'], u'')
1740 ] for n in names ]
1741 )
1742 self._LCTRL_items.set_column_widths()
1743 self._LCTRL_items.set_data(data = names)
1744
1745
1746
1748 self._LCTRL_items.set_columns(columns = [
1749 _('Active'),
1750 _('Title'),
1751 _('Lastname'),
1752 _('Firstname(s)'),
1753 _('Preferred Name'),
1754 _('Comment')
1755 ])
1756
1766
1776
1778
1779 if len(self.__identity.get_names()) == 1:
1780 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1781 return False
1782
1783 go_ahead = gmGuiHelpers.gm_show_question (
1784 _( 'It is often advisable to keep old names around and\n'
1785 'just create a new "currently active" name.\n'
1786 '\n'
1787 'This allows finding the patient by both the old\n'
1788 'and the new name (think before/after marriage).\n'
1789 '\n'
1790 'Do you still want to really delete\n'
1791 "this name from the patient ?"
1792 ),
1793 _('Deleting name')
1794 )
1795 if not go_ahead:
1796 return False
1797
1798 self.__identity.delete_name(name = name)
1799 return True
1800
1801
1802
1804 return self.__identity
1805
1809
1810 identity = property(_get_identity, _set_identity)
1811
1813 """A list for managing a person's external IDs.
1814
1815 Does NOT act on/listen to the current patient.
1816 """
1834
1835
1836
1837 - def refresh(self, *args, **kwargs):
1855
1856
1857
1859 self._LCTRL_items.set_columns(columns = [
1860 _('ID type'),
1861 _('Value'),
1862 _('Issuer'),
1863 _('Context'),
1864 _('Comment')
1865 ])
1866
1877
1888
1890 go_ahead = gmGuiHelpers.gm_show_question (
1891 _( 'Do you really want to delete this\n'
1892 'external ID from the patient ?'),
1893 _('Deleting external ID')
1894 )
1895 if not go_ahead:
1896 return False
1897 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
1898 return True
1899
1900
1901
1903 return self.__identity
1904
1908
1909 identity = property(_get_identity, _set_identity)
1910
1911
1912
1914 """A panel for editing identity data for a person.
1915
1916 - provides access to:
1917 - name
1918 - external IDs
1919
1920 Does NOT act on/listen to the current patient.
1921 """
1923
1924 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs)
1925
1926 self.__identity = None
1927 self.refresh()
1928
1929
1930
1932 self._PNL_names.identity = self.__identity
1933 self._PNL_ids.identity = self.__identity
1934
1935
1936
1938 return self.__identity
1939
1943
1944 identity = property(_get_identity, _set_identity)
1945
1946
1947
1968
1969 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl
1970
1971 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
1972
1984
1985
1986
1988 self._PRW_lastname.final_regex = '.+'
1989 self._PRW_firstnames.final_regex = '.+'
1990 self._PRW_address_searcher.selection_only = False
1991 low = wx.DateTimeFromDMY(1,0,1900)
1992 hi = wx.DateTime()
1993 self._DP_dob.SetRange(low, hi.SetToCurrent())
1994
1995
1996
1998
1999 adr = self._PRW_address_searcher.get_address()
2000 if adr is None:
2001 return True
2002
2003 if ctrl.GetValue().strip() != adr[field]:
2004 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None)
2005 return True
2006
2007 return False
2008
2010 adr = self._PRW_address_searcher.get_address()
2011 if adr is None:
2012 return True
2013
2014 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode'])
2015
2016 self._PRW_street.SetText(value = adr['street'], data = adr['street'])
2017 self._PRW_street.set_context(context = u'zip', val = adr['postcode'])
2018
2019 self._TCTRL_number.SetValue(adr['number'])
2020
2021 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb'])
2022 self._PRW_urb.set_context(context = u'zip', val = adr['postcode'])
2023
2024 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state'])
2025 self._PRW_region.set_context(context = u'zip', val = adr['postcode'])
2026
2027 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country'])
2028 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
2029
2031 error = False
2032
2033
2034 if self._PRW_lastname.GetValue().strip() == u'':
2035 error = True
2036 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2037 self._PRW_lastname.display_as_valid(False)
2038 else:
2039 self._PRW_lastname.display_as_valid(True)
2040
2041 if self._PRW_firstnames.GetValue().strip() == '':
2042 error = True
2043 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2044 self._PRW_firstnames.display_as_valid(False)
2045 else:
2046 self._PRW_firstnames.display_as_valid(True)
2047
2048
2049 if self._PRW_gender.GetData() is None:
2050 error = True
2051 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2052 self._PRW_gender.display_as_valid(False)
2053 else:
2054 self._PRW_gender.display_as_valid(True)
2055
2056
2057 if not self._DP_dob.is_valid_timestamp():
2058
2059 gmDispatcher.send(signal = 'statustext', msg = _('Cannot use this date of birth. Does it lie before 1900 ?'))
2060
2061 do_it_anyway = gmGuiHelpers.gm_show_question (
2062 _(
2063 'Are you sure you want to register this person\n'
2064 'without a valid date of birth ?\n'
2065 '\n'
2066 'This can be useful for temporary staff members\n'
2067 'but will provoke nag screens if this person\n'
2068 'becomes a patient.\n'
2069 '\n'
2070 'Note that the date of birth cannot technically\n'
2071 'be before 1900, either :-(\n'
2072 ),
2073 _('Registering new person')
2074 )
2075
2076 if not do_it_anyway:
2077 error = True
2078
2079 if self._DP_dob.GetValue().GetYear() < 1900:
2080 error = True
2081 gmDispatcher.send(signal = 'statustext', msg = _('The year of birth must lie after 1900.'), beep = True)
2082 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2083 self._DP_dob.SetFocus()
2084 else:
2085 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2086 self._DP_dob.Refresh()
2087
2088
2089
2090
2091 return (not error)
2092
2094
2095
2096 if self._PRW_address_searcher.GetData() is not None:
2097 wx.CallAfter(self.__set_fields_from_address_searcher)
2098 return True
2099
2100
2101 fields_to_fill = (
2102 self._TCTRL_number,
2103 self._PRW_zip,
2104 self._PRW_street,
2105 self._PRW_urb,
2106 self._PRW_region,
2107 self._PRW_country
2108 )
2109 no_of_filled_fields = 0
2110
2111 for field in fields_to_fill:
2112 if field.GetValue().strip() != u'':
2113 no_of_filled_fields += 1
2114 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2115 field.Refresh()
2116
2117
2118 if no_of_filled_fields == 0:
2119 if empty_address_is_valid:
2120 return True
2121 else:
2122 return None
2123
2124
2125 if no_of_filled_fields != len(fields_to_fill):
2126 for field in fields_to_fill:
2127 if field.GetValue().strip() == u'':
2128 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2129 field.SetFocus()
2130 field.Refresh()
2131 msg = _('To properly create an address, all the related fields must be filled in.')
2132 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2133 return False
2134
2135
2136
2137
2138 strict_fields = (
2139 self._PRW_region,
2140 self._PRW_country
2141 )
2142 error = False
2143 for field in strict_fields:
2144 if field.GetData() is None:
2145 error = True
2146 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2147 field.SetFocus()
2148 else:
2149 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2150 field.Refresh()
2151
2152 if error:
2153 msg = _('This field must contain an item selected from the dropdown list.')
2154 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2155 return False
2156
2157 return True
2158
2175
2176
2177
2179 """Set the gender according to entered firstname.
2180
2181 Matches are fetched from existing records in backend.
2182 """
2183
2184
2185 if self._PRW_gender.GetData() is not None:
2186 return True
2187
2188 firstname = self._PRW_firstnames.GetValue().strip()
2189 if firstname == u'':
2190 return True
2191
2192 gender = gmPerson.map_firstnames2gender(firstnames = firstname)
2193 if gender is None:
2194 return True
2195
2196 wx.CallAfter(self._PRW_gender.SetData, gender)
2197 return True
2198
2200 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode')
2201
2202 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'')
2203 self._PRW_street.set_context(context = u'zip', val = zip_code)
2204 self._PRW_urb.set_context(context = u'zip', val = zip_code)
2205 self._PRW_region.set_context(context = u'zip', val = zip_code)
2206 self._PRW_country.set_context(context = u'zip', val = zip_code)
2207
2208 return True
2209
2211 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country')
2212
2213 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'')
2214 self._PRW_region.set_context(context = u'country', val = country)
2215
2216 return True
2217
2219 mapping = [
2220 (self._PRW_street, 'street'),
2221 (self._TCTRL_number, 'number'),
2222 (self._PRW_urb, 'urb'),
2223 (self._PRW_region, 'l10n_state')
2224 ]
2225
2226
2227 for ctrl, field in mapping:
2228 if self.__perhaps_invalidate_address_searcher(ctrl, field):
2229 return True
2230
2231 return True
2232
2234 adr = self._PRW_address_searcher.get_address()
2235 if adr is None:
2236 return True
2237
2238 wx.CallAfter(self.__set_fields_from_address_searcher)
2239 return True
2240
2241
2242
2244 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
2245
2247
2248
2249 new_identity = gmPerson.create_identity (
2250 gender = self._PRW_gender.GetData(),
2251 dob = self._DP_dob.get_pydt(),
2252 lastnames = self._PRW_lastname.GetValue().strip(),
2253 firstnames = self._PRW_firstnames.GetValue().strip()
2254 )
2255 _log.debug('identity created: %s' % new_identity)
2256
2257 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
2258 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
2259
2260 new_identity.save()
2261
2262 name = new_identity.get_active_name()
2263 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
2264 name.save()
2265
2266
2267 is_valid = self.__address_valid_for_save(empty_address_is_valid = False)
2268 if is_valid is True:
2269
2270
2271 try:
2272 new_identity.link_address (
2273 number = self._TCTRL_number.GetValue().strip(),
2274 street = self._PRW_street.GetValue().strip(),
2275 postcode = self._PRW_zip.GetValue().strip(),
2276 urb = self._PRW_urb.GetValue().strip(),
2277 state = self._PRW_region.GetData(),
2278 country = self._PRW_country.GetData()
2279 )
2280 except psycopg2.InternalError:
2281
2282 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip())
2283 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip())
2284 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip())
2285 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip())
2286 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip())
2287 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip())
2288 _log.exception('cannot link address')
2289 gmGuiHelpers.gm_show_error (
2290 aTitle = _('Saving address'),
2291 aMessage = _(
2292 'Cannot save this address.\n'
2293 '\n'
2294 'You will have to add it via the Demographics plugin.\n'
2295 )
2296 )
2297 elif is_valid is False:
2298 gmGuiHelpers.gm_show_error (
2299 aTitle = _('Saving address'),
2300 aMessage = _(
2301 'Address not saved.\n'
2302 '\n'
2303 'You will have to add it via the Demographics plugin.\n'
2304 )
2305 )
2306
2307
2308
2309 new_identity.link_comm_channel (
2310 comm_medium = u'homephone',
2311 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''),
2312 is_confidential = False
2313 )
2314
2315
2316 pk_type = self._PRW_external_id_type.GetData()
2317 id_value = self._TCTRL_external_id_value.GetValue().strip()
2318 if (pk_type is not None) and (id_value != u''):
2319 new_identity.add_external_id(value = id_value, pk_type = pk_type)
2320
2321
2322 new_identity.link_occupation (
2323 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'')
2324 )
2325
2326 self.data = new_identity
2327 return True
2328
2330 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2331
2335
2338
2340 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2341
2342
2343
2344 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
2345 """
2346 Wizard page for entering patient's basic demographic information
2347 """
2348
2349 form_fields = (
2350 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation',
2351 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment'
2352 )
2353
2354 - def __init__(self, parent, title):
2355 """
2356 Creates a new instance of BasicPatDetailsPage
2357 @param parent - The parent widget
2358 @type parent - A wx.Window instance
2359 @param tile - The title of the page
2360 @type title - A StringType instance
2361 """
2362 wx.wizard.WizardPageSimple.__init__(self, parent)
2363 self.__title = title
2364 self.__do_layout()
2365 self.__register_interests()
2366
2367 - def __do_layout(self):
2368 PNL_form = wx.Panel(self, -1)
2369
2370
2371 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name'))
2372 STT_lastname.SetForegroundColour('red')
2373 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1)
2374 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)'))
2375
2376
2377 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)'))
2378 STT_firstname.SetForegroundColour('red')
2379 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1)
2380 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name'))
2381
2382
2383 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name'))
2384 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1)
2385
2386
2387 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth'))
2388 STT_dob.SetForegroundColour('red')
2389 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1)
2390 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one"))
2391
2392
2393 STT_gender = wx.StaticText(PNL_form, -1, _('Gender'))
2394 STT_gender.SetForegroundColour('red')
2395 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1)
2396 self.PRW_gender.SetToolTipString(_("Required: gender of patient"))
2397
2398
2399 STT_title = wx.StaticText(PNL_form, -1, _('Title'))
2400 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1)
2401
2402
2403 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code'))
2404 STT_zip_code.SetForegroundColour('orange')
2405 self.PRW_zip_code = cZipcodePhraseWheel(parent = PNL_form, id = -1)
2406 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code"))
2407
2408
2409 STT_street = wx.StaticText(PNL_form, -1, _('Street'))
2410 STT_street.SetForegroundColour('orange')
2411 self.PRW_street = cStreetPhraseWheel(parent = PNL_form, id = -1)
2412 self.PRW_street.SetToolTipString(_("primary/home address: name of street"))
2413
2414
2415 STT_address_number = wx.StaticText(PNL_form, -1, _('Number'))
2416 STT_address_number.SetForegroundColour('orange')
2417 self.TTC_address_number = wx.TextCtrl(PNL_form, -1)
2418 self.TTC_address_number.SetToolTipString(_("primary/home address: address number"))
2419
2420
2421 STT_town = wx.StaticText(PNL_form, -1, _('Place'))
2422 STT_town.SetForegroundColour('orange')
2423 self.PRW_town = cUrbPhraseWheel(parent = PNL_form, id = -1)
2424 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/..."))
2425
2426
2427 STT_state = wx.StaticText(PNL_form, -1, _('Region'))
2428 STT_state.SetForegroundColour('orange')
2429 self.PRW_state = cStateSelectionPhraseWheel(parent=PNL_form, id=-1)
2430 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/..."))
2431
2432
2433 STT_country = wx.StaticText(PNL_form, -1, _('Country'))
2434 STT_country.SetForegroundColour('orange')
2435 self.PRW_country = cCountryPhraseWheel(parent = PNL_form, id = -1)
2436 self.PRW_country.SetToolTipString(_("primary/home address: country"))
2437
2438
2439 STT_phone = wx.StaticText(PNL_form, -1, _('Phone'))
2440 self.TTC_phone = wx.TextCtrl(PNL_form, -1)
2441 self.TTC_phone.SetToolTipString(_("phone number at home"))
2442
2443
2444 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
2445 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
2446
2447
2448 STT_comment = wx.StaticText(PNL_form, -1, _('Comment'))
2449 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1)
2450 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.'))
2451
2452
2453 self.form_DTD = cFormDTD(fields = self.__class__.form_fields)
2454 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD))
2455
2456
2457 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4)
2458 SZR_input.AddGrowableCol(1)
2459 SZR_input.Add(STT_lastname, 0, wx.SHAPED)
2460 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND)
2461 SZR_input.Add(STT_firstname, 0, wx.SHAPED)
2462 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND)
2463 SZR_input.Add(STT_nick, 0, wx.SHAPED)
2464 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND)
2465 SZR_input.Add(STT_dob, 0, wx.SHAPED)
2466 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND)
2467 SZR_input.Add(STT_gender, 0, wx.SHAPED)
2468 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND)
2469 SZR_input.Add(STT_title, 0, wx.SHAPED)
2470 SZR_input.Add(self.PRW_title, 1, wx.EXPAND)
2471 SZR_input.Add(STT_zip_code, 0, wx.SHAPED)
2472 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND)
2473 SZR_input.Add(STT_street, 0, wx.SHAPED)
2474 SZR_input.Add(self.PRW_street, 1, wx.EXPAND)
2475 SZR_input.Add(STT_address_number, 0, wx.SHAPED)
2476 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND)
2477 SZR_input.Add(STT_town, 0, wx.SHAPED)
2478 SZR_input.Add(self.PRW_town, 1, wx.EXPAND)
2479 SZR_input.Add(STT_state, 0, wx.SHAPED)
2480 SZR_input.Add(self.PRW_state, 1, wx.EXPAND)
2481 SZR_input.Add(STT_country, 0, wx.SHAPED)
2482 SZR_input.Add(self.PRW_country, 1, wx.EXPAND)
2483 SZR_input.Add(STT_phone, 0, wx.SHAPED)
2484 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND)
2485 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
2486 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
2487 SZR_input.Add(STT_comment, 0, wx.SHAPED)
2488 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND)
2489
2490 PNL_form.SetSizerAndFit(SZR_input)
2491
2492
2493 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title)
2494 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2495
2496
2497
2502
2503 - def on_country_selected(self, data):
2504 """Set the states according to entered country."""
2505 self.PRW_state.set_context(context=u'country', val=data)
2506 return True
2507
2508 - def on_name_set(self):
2509 """Set the gender according to entered firstname.
2510
2511 Matches are fetched from existing records in backend.
2512 """
2513 firstname = self.PRW_firstname.GetValue().strip()
2514 rows, idx = gmPG2.run_ro_queries(queries = [{
2515 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
2516 'args': [firstname]
2517 }])
2518 if len(rows) == 0:
2519 return True
2520 wx.CallAfter(self.PRW_gender.SetData, rows[0][0])
2521 return True
2522
2523 - def on_zip_set(self):
2524 """Set the street, town, state and country according to entered zip code."""
2525 zip_code = self.PRW_zip_code.GetValue().strip()
2526 self.PRW_street.set_context(context=u'zip', val=zip_code)
2527 self.PRW_town.set_context(context=u'zip', val=zip_code)
2528 self.PRW_state.set_context(context=u'zip', val=zip_code)
2529 self.PRW_country.set_context(context=u'zip', val=zip_code)
2530 return True
2531
2533 """
2534 Wizard to create a new patient.
2535
2536 TODO:
2537 - write pages for different "themes" of patient creation
2538 - make it configurable which pages are loaded
2539 - make available sets of pages that apply to a country
2540 - make loading of some pages depend upon values in earlier pages, eg
2541 when the patient is female and older than 13 include a page about
2542 "female" data (number of kids etc)
2543
2544 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable()
2545 """
2546
2547 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2548 """
2549 Creates a new instance of NewPatientWizard
2550 @param parent - The parent widget
2551 @type parent - A wx.Window instance
2552 """
2553 id_wiz = wx.NewId()
2554 wx.wizard.Wizard.__init__(self, parent, id_wiz, title)
2555 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
2556 self.__subtitle = subtitle
2557 self.__do_layout()
2558
2560 """Create new patient.
2561
2562 activate, too, if told to do so (and patient successfully created)
2563 """
2564 while True:
2565
2566 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details):
2567 return False
2568
2569 try:
2570
2571 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD)
2572 except:
2573 _log.exception('cannot add new patient - missing identity fields')
2574 gmGuiHelpers.gm_show_error (
2575 _('Cannot create new patient.\n'
2576 'Missing parts of the identity.'
2577 ),
2578 _('Adding new patient')
2579 )
2580 continue
2581
2582 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2583
2584 try:
2585 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2586 except:
2587 _log.exception('cannot finalize new patient - missing address fields')
2588 gmGuiHelpers.gm_show_error (
2589 _('Cannot add address for the new patient.\n'
2590 'You must either enter all of the address fields or\n'
2591 'none at all. The relevant fields are marked in yellow.\n'
2592 '\n'
2593 'You will need to add the address details in the\n'
2594 'demographics module.'
2595 ),
2596 _('Adding new patient')
2597 )
2598 break
2599
2600 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2601
2602 break
2603
2604 if activate:
2605 from Gnumed.wxpython import gmPatSearchWidgets
2606 gmPatSearchWidgets.set_active_patient(patient = ident)
2607
2608 return ident
2609
2610
2611
2613 """Arrange widgets.
2614 """
2615
2616 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle )
2617 self.FitToPage(self.basic_pat_details)
2618
2620 """
2621 This validator is used to ensure that the user has entered all
2622 the required conditional values in the page (eg., to properly
2623 create an address, all the related fields must be filled).
2624 """
2625
2626 - def __init__(self, dtd):
2627 """
2628 Validator initialization.
2629 @param dtd The object containing the data model.
2630 @type dtd A cFormDTD instance
2631 """
2632
2633 wx.PyValidator.__init__(self)
2634
2635 self.form_DTD = dtd
2636
2638 """
2639 Standard cloner.
2640 Note that every validator must implement the Clone() method.
2641 """
2642 return cBasicPatDetailsPageValidator(dtd = self.form_DTD)
2643
2644 - def Validate(self, parent = None):
2645 """
2646 Validate the contents of the given text control.
2647 """
2648 _pnl_form = self.GetWindow().GetParent()
2649
2650 error = False
2651
2652
2653 if _pnl_form.PRW_lastname.GetValue().strip() == '':
2654 error = True
2655 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2656 _pnl_form.PRW_lastname.SetBackgroundColour('pink')
2657 _pnl_form.PRW_lastname.Refresh()
2658 else:
2659 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2660 _pnl_form.PRW_lastname.Refresh()
2661
2662 if _pnl_form.PRW_firstname.GetValue().strip() == '':
2663 error = True
2664 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2665 _pnl_form.PRW_firstname.SetBackgroundColour('pink')
2666 _pnl_form.PRW_firstname.Refresh()
2667 else:
2668 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2669 _pnl_form.PRW_firstname.Refresh()
2670
2671
2672 if _pnl_form.PRW_gender.GetData() is None:
2673 error = True
2674 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2675 _pnl_form.PRW_gender.SetBackgroundColour('pink')
2676 _pnl_form.PRW_gender.Refresh()
2677 else:
2678 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2679 _pnl_form.PRW_gender.Refresh()
2680
2681
2682 if (
2683 (_pnl_form.PRW_dob.GetValue().strip() == u'')
2684 or (not _pnl_form.PRW_dob.is_valid_timestamp())
2685 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900)
2686 ):
2687 error = True
2688 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue()
2689 gmDispatcher.send(signal = 'statustext', msg = msg)
2690 _pnl_form.PRW_dob.SetBackgroundColour('pink')
2691 _pnl_form.PRW_dob.Refresh()
2692 else:
2693 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2694 _pnl_form.PRW_dob.Refresh()
2695
2696
2697 is_any_field_filled = False
2698 address_fields = (
2699 _pnl_form.TTC_address_number,
2700 _pnl_form.PRW_zip_code,
2701 _pnl_form.PRW_street,
2702 _pnl_form.PRW_town
2703 )
2704 for field in address_fields:
2705 if field.GetValue().strip() == u'':
2706 if is_any_field_filled:
2707 error = True
2708 msg = _('To properly create an address, all the related fields must be filled in.')
2709 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2710 field.SetBackgroundColour('pink')
2711 field.SetFocus()
2712 field.Refresh()
2713 else:
2714 is_any_field_filled = True
2715 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2716 field.Refresh()
2717
2718 address_fields = (
2719 _pnl_form.PRW_state,
2720 _pnl_form.PRW_country
2721 )
2722 for field in address_fields:
2723 if field.GetData() is None:
2724 if is_any_field_filled:
2725 error = True
2726 msg = _('To properly create an address, all the related fields must be filled in.')
2727 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2728 field.SetBackgroundColour('pink')
2729 field.SetFocus()
2730 field.Refresh()
2731 else:
2732 is_any_field_filled = True
2733 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2734 field.Refresh()
2735
2736 return (not error)
2737
2738 - def TransferToWindow(self):
2739 """
2740 Transfer data from validator to window.
2741 The default implementation returns False, indicating that an error
2742 occurred. We simply return True, as we don't do any data transfer.
2743 """
2744 _pnl_form = self.GetWindow().GetParent()
2745
2746 _pnl_form.PRW_gender.SetData(self.form_DTD['gender'])
2747 _pnl_form.PRW_dob.SetText(self.form_DTD['dob'])
2748 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames'])
2749 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames'])
2750 _pnl_form.PRW_title.SetText(self.form_DTD['title'])
2751 _pnl_form.PRW_nick.SetText(self.form_DTD['nick'])
2752 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation'])
2753 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number'])
2754 _pnl_form.PRW_street.SetText(self.form_DTD['street'])
2755 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code'])
2756 _pnl_form.PRW_town.SetText(self.form_DTD['town'])
2757 _pnl_form.PRW_state.SetData(self.form_DTD['state'])
2758 _pnl_form.PRW_country.SetData(self.form_DTD['country'])
2759 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone'])
2760 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment'])
2761 return True
2762
2764 """
2765 Transfer data from window to validator.
2766 The default implementation returns False, indicating that an error
2767 occurred. We simply return True, as we don't do any data transfer.
2768 """
2769
2770 if not self.GetWindow().GetParent().Validate():
2771 return False
2772 try:
2773 _pnl_form = self.GetWindow().GetParent()
2774
2775 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData()
2776 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData()
2777
2778 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue()
2779 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue()
2780 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue()
2781 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue()
2782
2783 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue()
2784
2785 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue()
2786 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue()
2787 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue()
2788 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue()
2789 self.form_DTD['state'] = _pnl_form.PRW_state.GetData()
2790 self.form_DTD['country'] = _pnl_form.PRW_country.GetData()
2791
2792 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue()
2793
2794 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue()
2795 except:
2796 return False
2797 return True
2798
2843
2844
2845
2847 """Notebook displaying demographics editing pages:
2848
2849 - Identity
2850 - Contacts (addresses, phone numbers, etc)
2851
2852 Does NOT act on/listen to the current patient.
2853 """
2854
2856
2857 wx.Notebook.__init__ (
2858 self,
2859 parent = parent,
2860 id = id,
2861 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
2862 name = self.__class__.__name__
2863 )
2864
2865 self.__identity = None
2866 self.__do_layout()
2867 self.SetSelection(0)
2868
2869
2870
2872 """Populate fields in pages with data from model."""
2873 for page_idx in range(self.GetPageCount()):
2874 page = self.GetPage(page_idx)
2875 page.identity = self.__identity
2876
2877 return True
2878
2879
2880
2882 """Build patient edition notebook pages."""
2883
2884 new_page = cPersonContactsManagerPnl(self, -1)
2885 new_page.identity = self.__identity
2886 self.AddPage (
2887 page = new_page,
2888 text = _('Contacts'),
2889 select = True
2890 )
2891
2892
2893 new_page = cPersonIdentityManagerPnl(self, -1)
2894 new_page.identity = self.__identity
2895 self.AddPage (
2896 page = new_page,
2897 text = _('Identity'),
2898 select = False
2899 )
2900
2901
2902
2904 return self.__identity
2905
2908
2909 identity = property(_get_identity, _set_identity)
2910
2911
2912
2913
2915 """Page containing patient occupations edition fields.
2916 """
2917 - def __init__(self, parent, id, ident=None):
2918 """
2919 Creates a new instance of BasicPatDetailsPage
2920 @param parent - The parent widget
2921 @type parent - A wx.Window instance
2922 @param id - The widget id
2923 @type id - An integer
2924 """
2925 wx.Panel.__init__(self, parent, id)
2926 self.__ident = ident
2927 self.__do_layout()
2928
2930 PNL_form = wx.Panel(self, -1)
2931
2932 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
2933 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
2934 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
2935
2936 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
2937 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
2938
2939
2940 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
2941 SZR_input.AddGrowableCol(1)
2942 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
2943 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
2944 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
2945 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
2946 PNL_form.SetSizerAndFit(SZR_input)
2947
2948
2949 SZR_main = wx.BoxSizer(wx.VERTICAL)
2950 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2951 self.SetSizer(SZR_main)
2952
2955
2956 - def refresh(self, identity=None):
2957 if identity is not None:
2958 self.__ident = identity
2959 jobs = self.__ident.get_occupations()
2960 if len(jobs) > 0:
2961 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
2962 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
2963 return True
2964
2966 if self.PRW_occupation.IsModified():
2967 new_job = self.PRW_occupation.GetValue().strip()
2968 jobs = self.__ident.get_occupations()
2969 for job in jobs:
2970 if job['l10n_occupation'] == new_job:
2971 continue
2972 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
2973 self.__ident.link_occupation(occupation = new_job)
2974 return True
2975
2977 """Patient demographics plugin for main notebook.
2978
2979 Hosts another notebook with pages for Identity, Contacts, etc.
2980
2981 Acts on/listens to the currently active patient.
2982 """
2983
2989
2990
2991
2992
2993
2994
2996 """Arrange widgets."""
2997 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
2998
2999 szr_main = wx.BoxSizer(wx.VERTICAL)
3000 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
3001 self.SetSizerAndFit(szr_main)
3002
3003
3004
3006 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
3007 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
3008
3010 self._schedule_data_reget()
3011
3013 self._schedule_data_reget()
3014
3015
3016
3026
3028 """
3029 Register a new patient, given the data supplied in the
3030 Data Transfer Dictionary object.
3031
3032 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3033 supplied data.
3034 @type basic_details_DTD A cFormDTD instance.
3035 """
3036 new_identity = gmPerson.create_identity (
3037 gender = dtd['gender'],
3038 dob = dtd['dob'].get_pydt(),
3039 lastnames = dtd['lastnames'],
3040 firstnames = dtd['firstnames']
3041 )
3042 if new_identity is None:
3043 _log.error('cannot create identity from %s' % str(dtd))
3044 return None
3045 _log.debug('identity created: %s' % new_identity)
3046
3047 if dtd['comment'] is not None:
3048 if dtd['comment'].strip() != u'':
3049 name = new_identity.get_active_name()
3050 name['comment'] = dtd['comment']
3051 name.save_payload()
3052
3053 return new_identity
3054
3056 """
3057 Update patient details with data supplied by
3058 Data Transfer Dictionary object.
3059
3060 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3061 supplied data.
3062 @type basic_details_DTD A cFormDTD instance.
3063 """
3064
3065 if identity['gender'] != dtd['gender']:
3066 identity['gender'] = dtd['gender']
3067 if identity['dob'] != dtd['dob'].get_pydt():
3068 identity['dob'] = dtd['dob'].get_pydt()
3069 if len(dtd['title']) > 0 and identity['title'] != dtd['title']:
3070 identity['title'] = dtd['title']
3071
3072
3073
3074
3075 identity.save_payload()
3076
3077
3078
3079 if identity['firstnames'] != dtd['firstnames'] or identity['lastnames'] != dtd['lastnames']:
3080 new_name = identity.add_name(firstnames = dtd['firstnames'], lastnames = dtd['lastnames'], active = True)
3081
3082 if len(dtd['nick']) > 0 and identity['preferred'] != dtd['nick']:
3083 identity.set_nickname(nickname = dtd['nick'])
3084
3085 return True
3086
3130
3132 """
3133 Update patient details with data supplied by
3134 Data Transfer Dictionary object.
3135
3136 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3137 supplied data.
3138 @type basic_details_DTD A cFormDTD instance.
3139 """
3140 identity.link_occupation(occupation = dtd['occupation'])
3141
3142 return True
3143
3145 """
3146 Utility class to test the new patient wizard.
3147 """
3148
3150 """
3151 Create a new instance of TestPanel.
3152 @param parent The parent widget
3153 @type parent A wx.Window instance
3154 """
3155 wx.Panel.__init__(self, parent, id)
3156 wizard = cNewPatientWizard(self)
3157 print wizard.RunWizard()
3158
3159 if __name__ == "__main__":
3160
3161
3163 app = wx.PyWidgetTester(size = (200, 50))
3164 pw = cZipcodePhraseWheel(app.frame, -1)
3165 app.frame.Show(True)
3166 app.MainLoop()
3167
3169 app = wx.PyWidgetTester(size = (200, 50))
3170 pw = cStateSelectionPhraseWheel(app.frame, -1)
3171
3172
3173 app.frame.Show(True)
3174 app.MainLoop()
3175
3177 app = wx.PyWidgetTester(size = (200, 50))
3178 pw = cUrbPhraseWheel(app.frame, -1)
3179 app.frame.Show(True)
3180 pw.set_context(context = u'zip', val = u'04317')
3181 app.MainLoop()
3182
3184 app = wx.PyWidgetTester(size = (200, 50))
3185 pw = cSuburbPhraseWheel(app.frame, -1)
3186 app.frame.Show(True)
3187 app.MainLoop()
3188
3190 app = wx.PyWidgetTester(size = (200, 50))
3191 pw = cAddressTypePhraseWheel(app.frame, -1)
3192 app.frame.Show(True)
3193 app.MainLoop()
3194
3196 app = wx.PyWidgetTester(size = (200, 50))
3197 pw = cAddressPhraseWheel(app.frame, -1)
3198 app.frame.Show(True)
3199 app.MainLoop()
3200
3202 app = wx.PyWidgetTester(size = (200, 50))
3203 pw = cStreetPhraseWheel(app.frame, -1)
3204
3205 app.frame.Show(True)
3206 app.MainLoop()
3207
3209 app = wx.PyWidgetTester(size = (600, 400))
3210 app.SetWidget(cKOrganizerSchedulePnl)
3211 app.MainLoop()
3212
3214 app = wx.PyWidgetTester(size = (600, 400))
3215 widget = cPersonNamesManagerPnl(app.frame, -1)
3216 widget.identity = activate_patient()
3217 app.frame.Show(True)
3218 app.MainLoop()
3219
3221 app = wx.PyWidgetTester(size = (600, 400))
3222 widget = cPersonIDsManagerPnl(app.frame, -1)
3223 widget.identity = activate_patient()
3224 app.frame.Show(True)
3225 app.MainLoop()
3226
3228 app = wx.PyWidgetTester(size = (600, 400))
3229 widget = cPersonIdentityManagerPnl(app.frame, -1)
3230 widget.identity = activate_patient()
3231 app.frame.Show(True)
3232 app.MainLoop()
3233
3238
3243
3245 app = wx.PyWidgetTester(size = (600, 400))
3246 widget = cPersonAddressesManagerPnl(app.frame, -1)
3247 widget.identity = activate_patient()
3248 app.frame.Show(True)
3249 app.MainLoop()
3250
3252 app = wx.PyWidgetTester(size = (600, 400))
3253 widget = cPersonCommsManagerPnl(app.frame, -1)
3254 widget.identity = activate_patient()
3255 app.frame.Show(True)
3256 app.MainLoop()
3257
3264
3266 app = wx.PyWidgetTester(size = (600, 400))
3267 widget = cPersonDemographicsEditorNb(app.frame, -1)
3268 widget.identity = activate_patient()
3269 widget.refresh()
3270 app.frame.Show(True)
3271 app.MainLoop()
3272
3281
3282 if len(sys.argv) > 1 and sys.argv[1] == 'test':
3283
3284 gmI18N.activate_locale()
3285 gmI18N.install_domain(domain='gnumed')
3286 gmPG2.get_connection()
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303 test_urb_prw()
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223