1 """Widgets dealing with patient demographics."""
2
3 __version__ = "$Revision: 1.175 $"
4 __author__ = "R.Terry, SJ Tan, I Haywood, Carlos Moro <cfmoro1976@yahoo.es>"
5 __license__ = 'GPL (details at http://www.gnu.org)'
6
7
8 import time, string, sys, os, datetime as pyDT, csv, codecs, re as regex, psycopg2, logging
9
10
11 import wx
12 import wx.wizard
13
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18 from Gnumed.pycommon import gmDispatcher, gmI18N, gmMatchProvider, gmPG2, gmTools, gmCfg
19 from Gnumed.pycommon import gmDateTime, gmShellAPI
20 from Gnumed.business import gmDemographicRecord, gmPerson, gmSurgery
21 from Gnumed.wxpython import gmPlugin, gmPhraseWheel, gmGuiHelpers, gmDateTimeInput
22 from Gnumed.wxpython import gmRegetMixin, gmDataMiningWidgets, gmListWidgets, gmEditArea
23 from Gnumed.wxpython import gmAuthWidgets, gmCfgWidgets
24 from Gnumed.wxGladeWidgets import wxgGenericAddressEditAreaPnl, wxgPersonContactsManagerPnl, wxgPersonIdentityManagerPnl
25 from Gnumed.wxGladeWidgets import wxgCommChannelEditAreaPnl, wxgExternalIDEditAreaPnl
26
27
28
29 _log = logging.getLogger('gm.ui')
30
31
32 try:
33 _('dummy-no-need-to-translate-but-make-epydoc-happy')
34 except NameError:
35 _ = lambda x:x
36
37
38
39
56
58
60
61 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
62
63 context = {
64 u'ctxt_zip': {
65 u'where_part': u'and zip ilike %(zip)s',
66 u'placeholder': u'zip'
67 }
68 }
69 query = u"""
70 select code, name from (
71 select distinct on (code, name) code, (name || ' (' || code || ')') as name, rank from (
72
73 -- localized to user
74
75 select
76 code_country as code, l10n_country as name, 1 as rank
77 from dem.v_zip2data
78 where
79 l10n_country %(fragment_condition)s
80 %(ctxt_zip)s
81
82 union all
83
84 select
85 code as code, _(name) as name, 2 as rank
86 from dem.country
87 where
88 _(name) %(fragment_condition)s
89
90 union all
91
92 -- non-localized
93
94 select
95 code_country as code, country as name, 3 as rank
96 from dem.v_zip2data
97 where
98 country %(fragment_condition)s
99 %(ctxt_zip)s
100
101 union all
102
103 select
104 code as code, name as name, 4 as rank
105 from dem.country
106 where
107 name %(fragment_condition)s
108
109 union all
110
111 -- abbreviation
112
113 select
114 code as code, name as name, 5 as rank
115 from dem.country
116 where
117 code %(fragment_condition)s
118
119 ) as q2
120 ) as q1 order by rank, name limit 25"""
121 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context)
122 mp.setThresholds(2, 5, 9)
123 self.matcher = mp
124
125 self.unset_context(context = u'zip')
126 self.SetToolTipString(_('Type or select a country.'))
127 self.capitalisation_mode = gmTools.CAPS_FIRST
128 self.selection_only = True
129
130
131
132
149
151 ea = cProvinceEAPnl(parent = parent, id = -1, province = province)
152 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (province is not None))
153 dlg.SetTitle(gmTools.coalesce(province, _('Adding province'), _('Editing province')))
154 result = dlg.ShowModal()
155 dlg.Destroy()
156 return (result == wx.ID_OK)
157
159
160 msg = _(
161 'Are you sure you want to delete this province ?\n'
162 '\n'
163 'Deletion will only work if this province is not\n'
164 'yet in use in any patient addresses.'
165 )
166
167 tt = _(
168 'Also delete any towns/cities/villages known\n'
169 'to be situated in this state as long as\n'
170 'no patients are recorded to live there.'
171 )
172
173 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
174 parent,
175 -1,
176 caption = _('Deleting province'),
177 question = msg,
178 show_checkbox = True,
179 checkbox_msg = _('delete related townships'),
180 checkbox_tooltip = tt,
181 button_defs = [
182 {'label': _('Yes, delete'), 'tooltip': _('Delete province and possibly related townships.'), 'default': False},
183 {'label': _('No'), 'tooltip': _('No, do NOT delete anything.'), 'default': True}
184 ]
185 )
186
187 decision = dlg.ShowModal()
188 if decision != wx.ID_YES:
189 dlg.Destroy()
190 return False
191
192 include_urbs = dlg.checkbox_is_checked()
193 dlg.Destroy()
194
195 return gmDemographicRecord.delete_province(province = province, delete_urbs = include_urbs)
196
198
199 if parent is None:
200 parent = wx.GetApp().GetTopWindow()
201
202
203 def delete(province=None):
204 return delete_province(parent = parent, province = province['pk_state'])
205
206 def edit(province=None):
207 return edit_province(parent = parent, province = province)
208
209 def refresh(lctrl):
210 wx.BeginBusyCursor()
211 provinces = gmDemographicRecord.get_provinces()
212 lctrl.set_string_items([ (p['l10n_country'], p['l10n_state']) for p in provinces ])
213 lctrl.set_data(provinces)
214 wx.EndBusyCursor()
215
216 msg = _(
217 '\n'
218 'This list shows the provinces known to GNUmed.\n'
219 '\n'
220 'In your jurisdiction "province" may correspond to either of "state",\n'
221 '"county", "region", "territory", or some such term.\n'
222 '\n'
223 'Select the province you want to edit !\n'
224 )
225
226 gmListWidgets.get_choices_from_list (
227 parent = parent,
228 msg = msg,
229 caption = _('Editing provinces ...'),
230 columns = [_('Country'), _('Province')],
231 single_selection = True,
232 new_callback = edit,
233
234 delete_callback = delete,
235 refresh_callback = refresh
236 )
237
239
241
242 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
243
244 context = {
245 u'ctxt_country_name': {
246 u'where_part': u'and l10n_country ilike %(country_name)s or country ilike %(country_name)s',
247 u'placeholder': u'country_name'
248 },
249 u'ctxt_zip': {
250 u'where_part': u'and zip ilike %(zip)s',
251 u'placeholder': u'zip'
252 },
253 u'ctxt_country_code': {
254 u'where_part': u'and country in (select code from dem.country where _(name) ilike %(country_name)s or name ilike %(country_name)s)',
255 u'placeholder': u'country_name'
256 }
257 }
258
259 query = u"""
260 select code, name from (
261 select distinct on (name) code, name, rank from (
262 -- 1: find states based on name, context: zip and country name
263 select
264 code_state as code, state as name, 1 as rank
265 from dem.v_zip2data
266 where
267 state %(fragment_condition)s
268 %(ctxt_country_name)s
269 %(ctxt_zip)s
270
271 union all
272
273 -- 2: find states based on code, context: zip and country name
274 select
275 code_state as code, state as name, 2 as rank
276 from dem.v_zip2data
277 where
278 code_state %(fragment_condition)s
279 %(ctxt_country_name)s
280 %(ctxt_zip)s
281
282 union all
283
284 -- 3: find states based on name, context: country
285 select
286 code as code, name as name, 3 as rank
287 from dem.state
288 where
289 name %(fragment_condition)s
290 %(ctxt_country_code)s
291
292 union all
293
294 -- 4: find states based on code, context: country
295 select
296 code as code, name as name, 3 as rank
297 from dem.state
298 where
299 code %(fragment_condition)s
300 %(ctxt_country_code)s
301
302 ) as q2
303 ) as q1 order by rank, name limit 50"""
304
305 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context)
306 mp.setThresholds(2, 5, 6)
307 mp.word_separators = u'[ \t]+'
308 self.matcher = mp
309
310 self.unset_context(context = u'zip')
311 self.unset_context(context = u'country_name')
312 self.SetToolTipString(_('Type or select a state/region/province/territory.'))
313 self.capitalisation_mode = gmTools.CAPS_FIRST
314 self.selection_only = True
315
316 from Gnumed.wxGladeWidgets import wxgProvinceEAPnl
317
318 -class cProvinceEAPnl(wxgProvinceEAPnl.wxgProvinceEAPnl, gmEditArea.cGenericEditAreaMixin):
319
337
339 self._PRW_province.selection_only = False
340
341
342
370
386
388
389
390
391
392
393
394
395 return True
396
403
405 self._PRW_province.SetText(self.data['l10n_state'], self.data['code_state'])
406 self._TCTRL_code.SetValue(self.data['code_state'])
407 self._PRW_country.SetText(self.data['l10n_country'], self.data['code_country'])
408
409 self._PRW_province.SetFocus()
410
417
418
420
422
423 kwargs['message'] = _("Today's KOrganizer appointments ...")
424 kwargs['button_defs'] = [
425 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')},
426 {'label': u''},
427 {'label': u''},
428 {'label': u''},
429 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')}
430 ]
431 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs)
432
433 self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv'))
434 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
435
436
440
450
452 try: os.remove(self.fname)
453 except OSError: pass
454 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True)
455 try:
456 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace')
457 except IOError:
458 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True)
459 return
460
461 csv_lines = gmTools.unicode_csv_reader (
462 csv_file,
463 delimiter = ','
464 )
465
466 self._LCTRL_items.set_columns ([
467 _('Place'),
468 _('Start'),
469 u'',
470 u'',
471 _('Patient'),
472 _('Comment')
473 ])
474 items = []
475 data = []
476 for line in csv_lines:
477 items.append([line[5], line[0], line[1], line[3], line[4], line[6]])
478 data.append([line[4], line[7]])
479
480 self._LCTRL_items.set_string_items(items = items)
481 self._LCTRL_items.set_column_widths()
482 self._LCTRL_items.set_data(data = data)
483 self._LCTRL_items.patient_key = 0
484
485
486
489
491
492 pat = gmPerson.gmCurrentPatient()
493 curr_jobs = pat.get_occupations()
494 if len(curr_jobs) > 0:
495 old_job = curr_jobs[0]['l10n_occupation']
496 update = curr_jobs[0]['modified_when'].strftime('%m/%Y')
497 else:
498 old_job = u''
499 update = u''
500
501 msg = _(
502 'Please enter the primary occupation of the patient.\n'
503 '\n'
504 'Currently recorded:\n'
505 '\n'
506 ' %s (last updated %s)'
507 ) % (old_job, update)
508
509 new_job = wx.GetTextFromUser (
510 message = msg,
511 caption = _('Editing primary occupation'),
512 default_value = old_job,
513 parent = None
514 )
515 if new_job.strip() == u'':
516 return
517
518 for job in curr_jobs:
519
520 if job['l10n_occupation'] != new_job:
521 pat.unlink_occupation(occupation = job['l10n_occupation'])
522
523 pat.link_occupation(occupation = new_job)
524
526
527 go_ahead = gmGuiHelpers.gm_show_question (
528 _('Are you sure you really, positively want\n'
529 'to disable the following person ?\n'
530 '\n'
531 ' %s %s %s\n'
532 ' born %s\n'
533 '\n'
534 '%s\n'
535 ) % (
536 identity['firstnames'],
537 identity['lastnames'],
538 identity['gender'],
539 identity['dob'],
540 gmTools.bool2subst (
541 identity.is_patient,
542 _('This patient DID receive care.'),
543 _('This person did NOT receive care.')
544 )
545 ),
546 _('Disabling person')
547 )
548 if not go_ahead:
549 return True
550
551
552 conn = gmAuthWidgets.get_dbowner_connection (
553 procedure = _('Disabling patient')
554 )
555
556 if conn is False:
557 return True
558
559 if conn is None:
560 return False
561
562
563 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}])
564
565 return True
566
567
568
570 """A list for managing a person's addresses.
571
572 Does NOT act on/listen to the current patient.
573 """
591
592
593
594 - def refresh(self, *args, **kwargs):
595 if self.__identity is None:
596 self._LCTRL_items.set_string_items()
597 return
598
599 adrs = self.__identity.get_addresses()
600 self._LCTRL_items.set_string_items (
601 items = [ [
602 a['l10n_address_type'],
603 a['street'],
604 gmTools.coalesce(a['notes_street'], u''),
605 a['number'],
606 gmTools.coalesce(a['subunit'], u''),
607 a['postcode'],
608 a['urb'],
609 gmTools.coalesce(a['suburb'], u''),
610 a['l10n_state'],
611 a['l10n_country'],
612 gmTools.coalesce(a['notes_subunit'], u'')
613 ] for a in adrs
614 ]
615 )
616 self._LCTRL_items.set_column_widths()
617 self._LCTRL_items.set_data(data = adrs)
618
619
620
622 self._LCTRL_items.SetToolTipString(_('List of known addresses.'))
623 self._LCTRL_items.set_columns(columns = [
624 _('Type'),
625 _('Street'),
626 _('Street info'),
627 _('Number'),
628 _('Subunit'),
629 _('Postal code'),
630 _('Place'),
631 _('Suburb'),
632 _('Region'),
633 _('Country'),
634 _('Comment')
635 ])
636
645
647 ea = cAddressEditAreaPnl(self, -1, address = address)
648 ea.identity = self.__identity
649 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
650 dlg.SetTitle(_('Editing address'))
651 if dlg.ShowModal() == wx.ID_OK:
652
653
654 if ea.address['pk_address'] != address['pk_address']:
655 self.__identity.unlink_address(address = address)
656 return True
657 return False
658
660 go_ahead = gmGuiHelpers.gm_show_question (
661 _( 'Are you sure you want to remove this\n'
662 "address from the patient's addresses ?\n"
663 '\n'
664 'The address itself will not be deleted\n'
665 'but it will no longer be associated with\n'
666 'this patient.'
667 ),
668 _('Removing address')
669 )
670 if not go_ahead:
671 return False
672 self.__identity.unlink_address(address = address)
673 return True
674
675
676
678 return self.__identity
679
683
684 identity = property(_get_identity, _set_identity)
685
718
720 """An edit area for editing/creating an address.
721
722 Does NOT act on/listen to the current patient.
723 """
737
738
739
740 - def refresh(self, address = None):
741 if address is not None:
742 self.address = address
743
744 if self.address is not None:
745 self._PRW_type.SetText(self.address['l10n_address_type'])
746 self._PRW_zip.SetText(self.address['postcode'])
747 self._PRW_street.SetText(self.address['street'], data = self.address['street'])
748 self._TCTRL_notes_street.SetValue(gmTools.coalesce(self.address['notes_street'], ''))
749 self._TCTRL_number.SetValue(self.address['number'])
750 self._TCTRL_subunit.SetValue(gmTools.coalesce(self.address['subunit'], ''))
751 self._PRW_suburb.SetText(gmTools.coalesce(self.address['suburb'], ''))
752 self._PRW_urb.SetText(self.address['urb'], data = self.address['urb'])
753 self._PRW_state.SetText(self.address['l10n_state'], data = self.address['code_state'])
754 self._PRW_country.SetText(self.address['l10n_country'], data = self.address['code_country'])
755 self._TCTRL_notes_subunit.SetValue(gmTools.coalesce(self.address['notes_subunit'], ''))
756
757
758
759
761 """Links address to patient, creating new address if necessary"""
762
763 if not self.__valid_for_save():
764 return False
765
766
767 try:
768 adr = self.identity.link_address (
769 number = self._TCTRL_number.GetValue().strip(),
770 street = self._PRW_street.GetValue().strip(),
771 postcode = self._PRW_zip.GetValue().strip(),
772 urb = self._PRW_urb.GetValue().strip(),
773 state = self._PRW_state.GetData(),
774 country = self._PRW_country.GetData(),
775 subunit = gmTools.none_if(self._TCTRL_subunit.GetValue().strip(), u''),
776 suburb = gmTools.none_if(self._PRW_suburb.GetValue().strip(), u''),
777 id_type = self._PRW_type.GetData()
778 )
779 except:
780 _log.exception('cannot save address')
781 gmGuiHelpers.gm_show_error (
782 _('Cannot save address.\n\n'
783 'Does the state [%s]\n'
784 'exist in country [%s] ?'
785 ) % (
786 self._PRW_state.GetValue().strip(),
787 self._PRW_country.GetValue().strip()
788 ),
789 _('Saving address')
790 )
791 return False
792
793 notes = self._TCTRL_notes_street.GetValue().strip()
794 if notes != u'':
795 adr['notes_street'] = notes
796 notes = self._TCTRL_notes_subunit.GetValue().strip()
797 if notes != u'':
798 adr['notes_subunit'] = notes
799 adr.save_payload()
800
801 self.address = adr
802
803 return True
804
805
806
810
812 """Set the street, town, state and country according to entered zip code."""
813 zip_code = self._PRW_zip.GetValue()
814 if zip_code.strip() == u'':
815 self._PRW_street.unset_context(context = u'zip')
816 self._PRW_urb.unset_context(context = u'zip')
817 self._PRW_state.unset_context(context = u'zip')
818 self._PRW_country.unset_context(context = u'zip')
819 else:
820 self._PRW_street.set_context(context = u'zip', val = zip_code)
821 self._PRW_urb.set_context(context = u'zip', val = zip_code)
822 self._PRW_state.set_context(context = u'zip', val = zip_code)
823 self._PRW_country.set_context(context = u'zip', val = zip_code)
824
826 """Set the states according to entered country."""
827 country = self._PRW_country.GetData()
828 if country is None:
829 self._PRW_state.unset_context(context = 'country')
830 else:
831 self._PRW_state.set_context(context = 'country', val = country)
832
833
834
836
837
838 is_any_field_filled = False
839
840 required_fields = (
841 self._PRW_type,
842 self._PRW_zip,
843 self._PRW_street,
844 self._TCTRL_number,
845 self._PRW_urb
846 )
847 for field in required_fields:
848 if len(field.GetValue().strip()) == 0:
849 if is_any_field_filled:
850 field.SetBackgroundColour('pink')
851 field.SetFocus()
852 field.Refresh()
853 gmGuiHelpers.gm_show_error (
854 _('Address details must be filled in completely or not at all.'),
855 _('Saving contact data')
856 )
857 return False
858 else:
859 is_any_field_filled = True
860 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
861 field.Refresh()
862
863 required_fields = (
864 self._PRW_state,
865 self._PRW_country
866 )
867 for field in required_fields:
868 if field.GetData() is None:
869 if is_any_field_filled:
870 field.SetBackgroundColour('pink')
871 field.SetFocus()
872 field.Refresh()
873 gmGuiHelpers.gm_show_error (
874 _('Address details must be filled in completely or not at all.'),
875 _('Saving contact data')
876 )
877 return False
878 else:
879 is_any_field_filled = True
880 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
881 field.Refresh()
882
883 return True
884
886
888
889 query = u"""
890 select * from (
891 (select
892 pk_address,
893 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', '
894 || urb || coalesce(' (' || suburb || ')', '') || ', '
895 || postcode
896 || coalesce(', ' || notes_street, '')
897 || coalesce(', ' || notes_subunit, '')
898 ) as address
899 from
900 dem.v_address
901 where
902 street %(fragment_condition)s
903
904 ) union (
905
906 select
907 pk_address,
908 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', '
909 || urb || coalesce(' (' || suburb || ')', '') || ', '
910 || postcode
911 || coalesce(', ' || notes_street, '')
912 || coalesce(', ' || notes_subunit, '')
913 ) as address
914 from
915 dem.v_address
916 where
917 postcode_street %(fragment_condition)s
918
919 ) union (
920
921 select
922 pk_address,
923 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', '
924 || urb || coalesce(' (' || suburb || ')', '') || ', '
925 || postcode
926 || coalesce(', ' || notes_street, '')
927 || coalesce(', ' || notes_subunit, '')
928 ) as address
929 from
930 dem.v_address
931 where
932 postcode_urb %(fragment_condition)s
933 )
934 ) as union_result
935 order by union_result.address limit 50"""
936
937 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = query)
938
939 self.setThresholds(2, 4, 6)
940
941
942
944
958
960
961 pk = self.GetData()
962
963 if pk is None:
964 self.__address = None
965 return None
966
967 if self.__address is None:
968 self.__old_pk = pk
969 self.__address = gmDemographicRecord.cAddress(aPK_obj = pk)
970 else:
971 if pk != self.__old_pk:
972 self.__old_pk = pk
973 self.__address = gmDemographicRecord.cAddress(aPK_obj = pk)
974
975 return self.__address
976
978
980
981 query = u"""
982 select id, type from ((
983 select id, _(name) as type, 1 as rank
984 from dem.address_type
985 where _(name) %(fragment_condition)s
986 ) union (
987 select id, name as type, 2 as rank
988 from dem.address_type
989 where name %(fragment_condition)s
990 )) as ur
991 order by
992 ur.rank, ur.type
993 """
994 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
995 mp.setThresholds(1, 2, 4)
996 mp.word_separators = u'[ \t]+'
997 gmPhraseWheel.cPhraseWheel.__init__ (
998 self,
999 *args,
1000 **kwargs
1001 )
1002 self.matcher = mp
1003 self.SetToolTipString(_('Select the type of address.'))
1004
1005 self.selection_only = True
1006
1007
1008
1009
1010
1011
1012
1014
1016
1017 query = u"""
1018 (select distinct postcode, postcode from dem.street where postcode %(fragment_condition)s limit 20)
1019 union
1020 (select distinct postcode, postcode from dem.urb where postcode %(fragment_condition)s limit 20)"""
1021 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1022 mp.setThresholds(2, 3, 15)
1023 gmPhraseWheel.cPhraseWheel.__init__ (
1024 self,
1025 *args,
1026 **kwargs
1027 )
1028 self.SetToolTipString(_("Type or select a zip code (postcode)."))
1029 self.matcher = mp
1030
1032
1034 context = {
1035 u'ctxt_zip': {
1036 u'where_part': u'and zip ilike %(zip)s',
1037 u'placeholder': u'zip'
1038 }
1039 }
1040 query = u"""
1041 select s1, s2 from (
1042 select s1, s2, rank from (
1043 select distinct on (street)
1044 street as s1, street as s2, 1 as rank
1045 from dem.v_zip2data
1046 where
1047 street %(fragment_condition)s
1048 %(ctxt_zip)s
1049
1050 union all
1051
1052 select distinct on (name)
1053 name as s1, name as s2, 2 as rank
1054 from dem.street
1055 where
1056 name %(fragment_condition)s
1057
1058 ) as q2
1059 ) as q1 order by rank, s2 limit 50"""
1060 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context)
1061 mp.setThresholds(3, 5, 8)
1062 gmPhraseWheel.cPhraseWheel.__init__ (
1063 self,
1064 *args,
1065 **kwargs
1066 )
1067 self.unset_context(context = u'zip')
1068
1069 self.SetToolTipString(_('Type or select a street.'))
1070 self.capitalisation_mode = gmTools.CAPS_FIRST
1071 self.matcher = mp
1072
1074
1076
1077 query = """
1078 select distinct on (suburb) suburb, suburb
1079 from dem.street
1080 where suburb %(fragment_condition)s
1081 order by suburb
1082 limit 50
1083 """
1084 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1085 mp.setThresholds(2, 3, 6)
1086 gmPhraseWheel.cPhraseWheel.__init__ (
1087 self,
1088 *args,
1089 **kwargs
1090 )
1091
1092 self.SetToolTipString(_('Type or select the suburb.'))
1093 self.capitalisation_mode = gmTools.CAPS_FIRST
1094 self.matcher = mp
1095
1097
1099 context = {
1100 u'ctxt_zip': {
1101 u'where_part': u'and zip ilike %(zip)s',
1102 u'placeholder': u'zip'
1103 }
1104 }
1105 query = u"""
1106 select u1, u2 from (
1107 select distinct on (rank, u1)
1108 u1, u2, rank
1109 from (
1110 select
1111 urb as u1, urb as u2, 1 as rank
1112 from dem.v_zip2data
1113 where
1114 urb %(fragment_condition)s
1115 %(ctxt_zip)s
1116
1117 union all
1118
1119 select
1120 name as u1, name as u2, 2 as rank
1121 from dem.urb
1122 where
1123 name %(fragment_condition)s
1124 ) as union_result
1125 order by rank, u1
1126 ) as distincted_union
1127 limit 50
1128 """
1129 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context)
1130 mp.setThresholds(3, 5, 7)
1131 gmPhraseWheel.cPhraseWheel.__init__ (
1132 self,
1133 *args,
1134 **kwargs
1135 )
1136 self.unset_context(context = u'zip')
1137
1138 self.SetToolTipString(_('Type or select a city/town/village/dwelling.'))
1139 self.capitalisation_mode = gmTools.CAPS_FIRST
1140 self.matcher = mp
1141
1142
1143
1145
1147
1148 query = u"""
1149 select pk, type from ((
1150 select pk, _(description) as type, 1 as rank
1151 from dem.enum_comm_types
1152 where _(description) %(fragment_condition)s
1153 ) union (
1154 select pk, description as type, 2 as rank
1155 from dem.enum_comm_types
1156 where description %(fragment_condition)s
1157 )) as ur
1158 order by
1159 ur.rank, ur.type
1160 """
1161 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1162 mp.setThresholds(1, 2, 4)
1163 mp.word_separators = u'[ \t]+'
1164 gmPhraseWheel.cPhraseWheel.__init__ (
1165 self,
1166 *args,
1167 **kwargs
1168 )
1169 self.matcher = mp
1170 self.SetToolTipString(_('Select the type of communications channel.'))
1171 self.selection_only = True
1172
1174 """An edit area for editing/creating a comms channel.
1175
1176 Does NOT act on/listen to the current patient.
1177 """
1190
1191
1192
1193 - def refresh(self, comm_channel = None):
1194 if comm_channel is not None:
1195 self.channel = comm_channel
1196
1197 if self.channel is None:
1198 self._PRW_type.SetText(u'')
1199 self._TCTRL_url.SetValue(u'')
1200 self._PRW_address.SetText(value = u'', data = None)
1201 self._CHBOX_confidential.SetValue(False)
1202 else:
1203 self._PRW_type.SetText(self.channel['l10n_comm_type'])
1204 self._TCTRL_url.SetValue(self.channel['url'])
1205 self._PRW_address.SetData(data = self.channel['pk_address'])
1206 self._CHBOX_confidential.SetValue(self.channel['is_confidential'])
1207
1209 """Links comm channel to patient."""
1210 if self.channel is None:
1211 if not self.__valid_for_save():
1212 return False
1213 try:
1214 self.channel = self.identity.link_comm_channel (
1215 pk_channel_type = self._PRW_type.GetData(),
1216 url = self._TCTRL_url.GetValue().strip(),
1217 is_confidential = self._CHBOX_confidential.GetValue(),
1218 )
1219 except psycopg2.IntegrityError:
1220 _log.exception('error saving comm channel')
1221 gmDispatcher.send(signal = u'statustext', msg = _('Cannot save communications channel.'), beep = True)
1222 return False
1223 else:
1224 comm_type = self._PRW_type.GetValue().strip()
1225 if comm_type != u'':
1226 self.channel['comm_type'] = comm_type
1227 url = self._TCTRL_url.GetValue().strip()
1228 if url != u'':
1229 self.channel['url'] = url
1230 self.channel['is_confidential'] = self._CHBOX_confidential.GetValue()
1231
1232 self.channel['pk_address'] = self._PRW_address.GetData()
1233 self.channel.save_payload()
1234
1235 return True
1236
1237
1238
1240
1241 no_errors = True
1242
1243 if self._PRW_type.GetData() is None:
1244 self._PRW_type.SetBackgroundColour('pink')
1245 self._PRW_type.SetFocus()
1246 self._PRW_type.Refresh()
1247 no_errors = False
1248 else:
1249 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1250 self._PRW_type.Refresh()
1251
1252 if self._TCTRL_url.GetValue().strip() == u'':
1253 self._TCTRL_url.SetBackgroundColour('pink')
1254 self._TCTRL_url.SetFocus()
1255 self._TCTRL_url.Refresh()
1256 no_errors = False
1257 else:
1258 self._TCTRL_url.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1259 self._TCTRL_url.Refresh()
1260
1261 return no_errors
1262
1264 """A list for managing a person's comm channels.
1265
1266 Does NOT act on/listen to the current patient.
1267 """
1285
1286
1287
1288 - def refresh(self, *args, **kwargs):
1299
1300
1301
1303 self._LCTRL_items.SetToolTipString(_('List of known communication channels.'))
1304 self._LCTRL_items.set_columns(columns = [
1305 _('confidential'),
1306 _('Type'),
1307 _('Value')
1308 ])
1309
1318
1327
1329 go_ahead = gmGuiHelpers.gm_show_question (
1330 _( 'Are you sure this patient can no longer\n'
1331 "be contacted via this channel ?"
1332 ),
1333 _('Removing communication channel')
1334 )
1335 if not go_ahead:
1336 return False
1337 self.__identity.unlink_comm_channel(comm_channel = comm)
1338 return True
1339
1340
1341
1343 return self.__identity
1344
1348
1349 identity = property(_get_identity, _set_identity)
1350
1351
1352
1353
1354
1369
1371
1373 query = u"""
1374 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
1375 union
1376 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
1377 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1378 mp.setThresholds(3, 5, 9)
1379 gmPhraseWheel.cPhraseWheel.__init__ (
1380 self,
1381 *args,
1382 **kwargs
1383 )
1384 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name)."))
1385 self.capitalisation_mode = gmTools.CAPS_NAMES
1386 self.matcher = mp
1387
1389
1391 query = u"""
1392 (select distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20)
1393 union
1394 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
1395 union
1396 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
1397 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1398 mp.setThresholds(3, 5, 9)
1399 gmPhraseWheel.cPhraseWheel.__init__ (
1400 self,
1401 *args,
1402 **kwargs
1403 )
1404 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name)."))
1405
1406
1407 self.matcher = mp
1408
1410
1412 query = u"select distinct title, title from dem.identity where title %(fragment_condition)s"
1413 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1414 mp.setThresholds(1, 3, 9)
1415 gmPhraseWheel.cPhraseWheel.__init__ (
1416 self,
1417 *args,
1418 **kwargs
1419 )
1420 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !"))
1421 self.matcher = mp
1422
1424 """Let user select a gender."""
1425
1426 _gender_map = None
1427
1429
1430 if cGenderSelectionPhraseWheel._gender_map is None:
1431 cmd = u"""
1432 select tag, l10n_label, sort_weight
1433 from dem.v_gender_labels
1434 order by sort_weight desc"""
1435 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
1436 cGenderSelectionPhraseWheel._gender_map = {}
1437 for gender in rows:
1438 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = {
1439 'data': gender[idx['tag']],
1440 'label': gender[idx['l10n_label']],
1441 'weight': gender[idx['sort_weight']]
1442 }
1443
1444 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values())
1445 mp.setThresholds(1, 1, 3)
1446
1447 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1448 self.selection_only = True
1449 self.matcher = mp
1450 self.picklist_delay = 50
1451
1466
1468
1470 query = u"""
1471 select distinct pk, (name || coalesce(' (%s ' || issuer || ')', '')) as label
1472 from dem.enum_ext_id_types
1473 where name %%(fragment_condition)s
1474 order by label limit 25""" % _('issued by')
1475 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1476 mp.setThresholds(1, 3, 5)
1477 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1478 self.SetToolTipString(_("Enter or select a type for the external ID."))
1479 self.matcher = mp
1480
1495
1496
1497
1499 """An edit area for editing/creating external IDs.
1500
1501 Does NOT act on/listen to the current patient.
1502 """
1518
1519
1520
1522 if ext_id is not None:
1523 self.ext_id = ext_id
1524
1525 if self.ext_id is not None:
1526 self._PRW_type.SetText(value = self.ext_id['name'], data = self.ext_id['pk_type'])
1527 self._TCTRL_value.SetValue(self.ext_id['value'])
1528 self._PRW_issuer.SetText(self.ext_id['issuer'])
1529 self._TCTRL_comment.SetValue(gmTools.coalesce(self.ext_id['comment'], u''))
1530
1531
1532
1533
1535
1536 if not self.__valid_for_save():
1537 return False
1538
1539
1540 type = regex.split(' \(%s .+\)$' % _('issued by'), self._PRW_type.GetValue().strip(), 1)[0]
1541
1542
1543 if self.ext_id is None:
1544 self.identity.add_external_id (
1545 type_name = type,
1546 value = self._TCTRL_value.GetValue().strip(),
1547 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
1548 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1549 )
1550
1551 else:
1552 self.identity.update_external_id (
1553 pk_id = self.ext_id['pk_id'],
1554 type = type,
1555 value = self._TCTRL_value.GetValue().strip(),
1556 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
1557 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1558 )
1559
1560 return True
1561
1562
1563
1566
1568 """Set the issuer according to the selected type.
1569
1570 Matches are fetched from existing records in backend.
1571 """
1572 pk_curr_type = self._PRW_type.GetData()
1573 if pk_curr_type is None:
1574 return True
1575 rows, idx = gmPG2.run_ro_queries(queries = [{
1576 'cmd': u"select issuer from dem.enum_ext_id_types where pk = %s",
1577 'args': [pk_curr_type]
1578 }])
1579 if len(rows) == 0:
1580 return True
1581 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0])
1582 return True
1583
1585
1586 no_errors = True
1587
1588
1589
1590
1591 if self._PRW_type.GetValue().strip() == u'':
1592 self._PRW_type.SetBackgroundColour('pink')
1593 self._PRW_type.SetFocus()
1594 self._PRW_type.Refresh()
1595 else:
1596 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1597 self._PRW_type.Refresh()
1598
1599 if self._TCTRL_value.GetValue().strip() == u'':
1600 self._TCTRL_value.SetBackgroundColour('pink')
1601 self._TCTRL_value.SetFocus()
1602 self._TCTRL_value.Refresh()
1603 no_errors = False
1604 else:
1605 self._TCTRL_value.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1606 self._TCTRL_value.Refresh()
1607
1608 return no_errors
1609
1610 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl
1611
1612 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
1613 """An edit area for editing/creating title/gender/dob/dod etc."""
1614
1630
1631
1632
1633
1634
1635
1636
1637
1639
1640 has_error = False
1641
1642 if self._PRW_gender.GetData() is None:
1643 self._PRW_gender.SetFocus()
1644 has_error = True
1645
1646 if not self._PRW_dob.is_valid_timestamp():
1647 val = self._PRW_dob.GetValue().strip()
1648 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
1649 self._PRW_dob.SetBackgroundColour('pink')
1650 self._PRW_dob.Refresh()
1651 self._PRW_dob.SetFocus()
1652 has_error = True
1653 else:
1654 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1655 self._PRW_dob.Refresh()
1656
1657 if not self._DP_dod.is_valid_timestamp(allow_none=True):
1658 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
1659 self._DP_dod.SetFocus()
1660 has_error = True
1661
1662 return (has_error is False)
1663
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677 return False
1678 return True
1679
1694
1697
1712
1715
1716
1717
1718 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl
1719
1721 """An edit area for editing/creating name/gender/dob.
1722
1723 Does NOT act on/listen to the current patient.
1724 """
1735
1736
1737
1754
1755
1756
1757
1759
1760 if not self.__valid_for_save():
1761 return False
1762
1763 self.__identity['gender'] = self._PRW_gender.GetData()
1764 if self._PRW_dob.GetValue().strip() == u'':
1765 self.__identity['dob'] = None
1766 else:
1767 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt()
1768 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
1769 self.__identity['deceased'] = self._DP_dod.GetValue(as_pydt = True)
1770 self.__identity.save_payload()
1771
1772 active = self._CHBOX_active.GetValue()
1773 first = self._PRW_firstname.GetValue().strip()
1774 last = self._PRW_lastname.GetValue().strip()
1775 old_nick = self.__name['preferred']
1776
1777
1778 old_name = self.__name['firstnames'] + self.__name['lastnames']
1779 if (first + last) != old_name:
1780 self.__name = self.__identity.add_name(first, last, active)
1781
1782 self.__name['active_name'] = active
1783 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
1784 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1785
1786 self.__name.save_payload()
1787
1788 return True
1789
1790
1791
1794
1796 """Set the gender according to entered firstname.
1797
1798 Matches are fetched from existing records in backend.
1799 """
1800 firstname = self._PRW_firstname.GetValue().strip()
1801 if firstname == u'':
1802 return True
1803 rows, idx = gmPG2.run_ro_queries(queries = [{
1804 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
1805 'args': [firstname]
1806 }])
1807 if len(rows) == 0:
1808 return True
1809 wx.CallAfter(self._PRW_gender.SetData, rows[0][0])
1810 return True
1811
1812
1813
1815
1816 has_error = False
1817
1818 if self._PRW_gender.GetData() is None:
1819 self._PRW_gender.SetBackgroundColour('pink')
1820 self._PRW_gender.Refresh()
1821 self._PRW_gender.SetFocus()
1822 has_error = True
1823 else:
1824 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1825 self._PRW_gender.Refresh()
1826
1827 if not self._PRW_dob.is_valid_timestamp():
1828 val = self._PRW_dob.GetValue().strip()
1829 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
1830 self._PRW_dob.SetBackgroundColour('pink')
1831 self._PRW_dob.Refresh()
1832 self._PRW_dob.SetFocus()
1833 has_error = True
1834 else:
1835 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1836 self._PRW_dob.Refresh()
1837
1838 if not self._DP_dod.is_valid_timestamp():
1839 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
1840 self._DP_dod.SetBackgroundColour('pink')
1841 self._DP_dod.Refresh()
1842 self._DP_dod.SetFocus()
1843 has_error = True
1844 else:
1845 self._DP_dod.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1846 self._DP_dod.Refresh()
1847
1848 if self._PRW_lastname.GetValue().strip() == u'':
1849 self._PRW_lastname.SetBackgroundColour('pink')
1850 self._PRW_lastname.Refresh()
1851 self._PRW_lastname.SetFocus()
1852 has_error = True
1853 else:
1854 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1855 self._PRW_lastname.Refresh()
1856
1857 if self._PRW_firstname.GetValue().strip() == u'':
1858 self._PRW_firstname.SetBackgroundColour('pink')
1859 self._PRW_firstname.Refresh()
1860 self._PRW_firstname.SetFocus()
1861 has_error = True
1862 else:
1863 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1864 self._PRW_firstname.Refresh()
1865
1866 return (has_error is False)
1867
1868
1869
1871 """A list for managing a person's names.
1872
1873 Does NOT act on/listen to the current patient.
1874 """
1892
1893
1894
1895 - def refresh(self, *args, **kwargs):
1912
1913
1914
1916 self._LCTRL_items.set_columns(columns = [
1917 _('Active'),
1918 _('Lastname'),
1919 _('Firstname(s)'),
1920 _('Preferred Name'),
1921 _('Comment')
1922 ])
1923
1933
1943
1945
1946 if len(self.__identity.get_names()) == 1:
1947 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1948 return False
1949
1950 go_ahead = gmGuiHelpers.gm_show_question (
1951 _( 'It is often advisable to keep old names around and\n'
1952 'just create a new "currently active" name.\n'
1953 '\n'
1954 'This allows finding the patient by both the old\n'
1955 'and the new name (think before/after marriage).\n'
1956 '\n'
1957 'Do you still want to really delete\n'
1958 "this name from the patient ?"
1959 ),
1960 _('Deleting name')
1961 )
1962 if not go_ahead:
1963 return False
1964
1965 self.__identity.delete_name(name = name)
1966 return True
1967
1968
1969
1971 return self.__identity
1972
1976
1977 identity = property(_get_identity, _set_identity)
1978
1980 """A list for managing a person's external IDs.
1981
1982 Does NOT act on/listen to the current patient.
1983 """
2001
2002
2003
2004 - def refresh(self, *args, **kwargs):
2022
2023
2024
2026 self._LCTRL_items.set_columns(columns = [
2027 _('ID type'),
2028 _('Value'),
2029 _('Issuer'),
2030 _('Context'),
2031 _('Comment')
2032 ])
2033
2044
2055
2057 go_ahead = gmGuiHelpers.gm_show_question (
2058 _( 'Do you really want to delete this\n'
2059 'external ID from the patient ?'),
2060 _('Deleting external ID')
2061 )
2062 if not go_ahead:
2063 return False
2064 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
2065 return True
2066
2067
2068
2070 return self.__identity
2071
2075
2076 identity = property(_get_identity, _set_identity)
2077
2078
2079
2081 """A panel for editing identity data for a person.
2082
2083 - provides access to:
2084 - name
2085 - external IDs
2086
2087 Does NOT act on/listen to the current patient.
2088 """
2095
2096
2097
2099 self._PNL_names.identity = self.__identity
2100 self._PNL_ids.identity = self.__identity
2101
2102 self._PNL_identity.mode = 'new'
2103 self._PNL_identity.data = self.__identity
2104 if self.__identity is not None:
2105 self._PNL_identity.mode = 'edit'
2106
2107
2108
2110 return self.__identity
2111
2115
2116 identity = property(_get_identity, _set_identity)
2117
2118
2119
2123
2126
2127
2128
2130
2131 dbcfg = gmCfg.cCfgSQL()
2132
2133 def_region = dbcfg.get2 (
2134 option = u'person.create.default_region',
2135 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2136 bias = u'user'
2137 )
2138
2139 if def_region is None:
2140 def_country = dbcfg.get2 (
2141 option = u'person.create.default_country',
2142 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2143 bias = u'user'
2144 )
2145 else:
2146 countries = gmDemographicRecord.get_country_for_region(region = def_region)
2147 if len(countries) == 1:
2148 def_country = countries[0]['l10n_country']
2149
2150 if parent is None:
2151 parent = wx.GetApp().GetTopWindow()
2152
2153 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region)
2154 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
2155 dlg.SetTitle(_('Adding new person'))
2156 ea._PRW_lastname.SetFocus()
2157 result = dlg.ShowModal()
2158 pat = ea.data
2159 dlg.Destroy()
2160
2161 if result != wx.ID_OK:
2162 return False
2163
2164 if activate:
2165 from Gnumed.wxpython import gmPatSearchWidgets
2166 gmPatSearchWidgets.set_active_patient(patient = pat)
2167
2168 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin')
2169
2170 return True
2171
2172 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl
2173
2174 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
2175
2177
2178 try:
2179 self.default_region = kwargs['region']
2180 del kwargs['region']
2181 except KeyError:
2182 self.default_region = None
2183
2184 try:
2185 self.default_country = kwargs['country']
2186 del kwargs['country']
2187 except KeyError:
2188 self.default_country = None
2189
2190 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs)
2191 gmEditArea.cGenericEditAreaMixin.__init__(self)
2192
2193 self.mode = 'new'
2194 self.data = None
2195 self._address = None
2196
2197 self.__init_ui()
2198 self.__register_interests()
2199
2200
2201
2203 self._PRW_lastname.final_regex = '.+'
2204 self._PRW_firstnames.final_regex = '.+'
2205 self._PRW_address_searcher.selection_only = False
2206 low = wx.DateTimeFromDMY(1,0,1900)
2207 hi = wx.DateTime()
2208 self._DP_dob.SetRange(low, hi.SetToCurrent())
2209
2210
2211
2212 if self.default_country is not None:
2213 self._PRW_country.SetText(value = self.default_country)
2214
2215 if self.default_region is not None:
2216 self._PRW_region.SetText(value = self.default_region)
2217
2219
2220 adr = self._PRW_address_searcher.get_address()
2221 if adr is None:
2222 return True
2223
2224 if ctrl.GetValue().strip() != adr[field]:
2225 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None)
2226 return True
2227
2228 return False
2229
2231 adr = self._PRW_address_searcher.get_address()
2232 if adr is None:
2233 return True
2234
2235 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode'])
2236
2237 self._PRW_street.SetText(value = adr['street'], data = adr['street'])
2238 self._PRW_street.set_context(context = u'zip', val = adr['postcode'])
2239
2240 self._TCTRL_number.SetValue(adr['number'])
2241
2242 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb'])
2243 self._PRW_urb.set_context(context = u'zip', val = adr['postcode'])
2244
2245 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state'])
2246 self._PRW_region.set_context(context = u'zip', val = adr['postcode'])
2247
2248 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country'])
2249 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
2250
2252 error = False
2253
2254
2255 if self._PRW_lastname.GetValue().strip() == u'':
2256 error = True
2257 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2258 self._PRW_lastname.display_as_valid(False)
2259 else:
2260 self._PRW_lastname.display_as_valid(True)
2261
2262 if self._PRW_firstnames.GetValue().strip() == '':
2263 error = True
2264 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2265 self._PRW_firstnames.display_as_valid(False)
2266 else:
2267 self._PRW_firstnames.display_as_valid(True)
2268
2269
2270 if self._PRW_gender.GetData() is None:
2271 error = True
2272 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2273 self._PRW_gender.display_as_valid(False)
2274 else:
2275 self._PRW_gender.display_as_valid(True)
2276
2277
2278 if not self._DP_dob.is_valid_timestamp():
2279
2280 gmDispatcher.send(signal = 'statustext', msg = _('Cannot use this date of birth. Does it lie before 1900 ?'))
2281
2282 do_it_anyway = gmGuiHelpers.gm_show_question (
2283 _(
2284 'Are you sure you want to register this person\n'
2285 'without a valid date of birth ?\n'
2286 '\n'
2287 'This can be useful for temporary staff members\n'
2288 'but will provoke nag screens if this person\n'
2289 'becomes a patient.\n'
2290 '\n'
2291 'Note that the date of birth cannot technically\n'
2292 'be before 1900, either :-(\n'
2293 ),
2294 _('Registering new person')
2295 )
2296
2297 if not do_it_anyway:
2298 error = True
2299
2300 if self._DP_dob.GetValue() is None:
2301 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2302 elif self._DP_dob.GetValue().GetYear() < 1900:
2303 error = True
2304 gmDispatcher.send(signal = 'statustext', msg = _('The year of birth must lie after 1900.'), beep = True)
2305 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2306 self._DP_dob.SetFocus()
2307 else:
2308 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2309 self._DP_dob.Refresh()
2310
2311
2312
2313
2314 return (not error)
2315
2317
2318
2319 if self._PRW_address_searcher.GetData() is not None:
2320 wx.CallAfter(self.__set_fields_from_address_searcher)
2321 return True
2322
2323
2324 fields_to_fill = (
2325 self._TCTRL_number,
2326 self._PRW_zip,
2327 self._PRW_street,
2328 self._PRW_urb,
2329 self._PRW_region,
2330 self._PRW_country
2331 )
2332 no_of_filled_fields = 0
2333
2334 for field in fields_to_fill:
2335 if field.GetValue().strip() != u'':
2336 no_of_filled_fields += 1
2337 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2338 field.Refresh()
2339
2340
2341 if no_of_filled_fields == 0:
2342 if empty_address_is_valid:
2343 return True
2344 else:
2345 return None
2346
2347
2348 if no_of_filled_fields != len(fields_to_fill):
2349 for field in fields_to_fill:
2350 if field.GetValue().strip() == u'':
2351 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2352 field.SetFocus()
2353 field.Refresh()
2354 msg = _('To properly create an address, all the related fields must be filled in.')
2355 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2356 return False
2357
2358
2359
2360
2361 strict_fields = (
2362 self._PRW_region,
2363 self._PRW_country
2364 )
2365 error = False
2366 for field in strict_fields:
2367 if field.GetData() is None:
2368 error = True
2369 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2370 field.SetFocus()
2371 else:
2372 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2373 field.Refresh()
2374
2375 if error:
2376 msg = _('This field must contain an item selected from the dropdown list.')
2377 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2378 return False
2379
2380 return True
2381
2398
2399
2400
2402 """Set the gender according to entered firstname.
2403
2404 Matches are fetched from existing records in backend.
2405 """
2406
2407
2408 if self._PRW_gender.GetData() is not None:
2409 return True
2410
2411 firstname = self._PRW_firstnames.GetValue().strip()
2412 if firstname == u'':
2413 return True
2414
2415 gender = gmPerson.map_firstnames2gender(firstnames = firstname)
2416 if gender is None:
2417 return True
2418
2419 wx.CallAfter(self._PRW_gender.SetData, gender)
2420 return True
2421
2423 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode')
2424
2425 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'')
2426 self._PRW_street.set_context(context = u'zip', val = zip_code)
2427 self._PRW_urb.set_context(context = u'zip', val = zip_code)
2428 self._PRW_region.set_context(context = u'zip', val = zip_code)
2429 self._PRW_country.set_context(context = u'zip', val = zip_code)
2430
2431 return True
2432
2434 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country')
2435
2436 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'')
2437 self._PRW_region.set_context(context = u'country', val = country)
2438
2439 return True
2440
2442 mapping = [
2443 (self._PRW_street, 'street'),
2444 (self._TCTRL_number, 'number'),
2445 (self._PRW_urb, 'urb'),
2446 (self._PRW_region, 'l10n_state')
2447 ]
2448
2449
2450 for ctrl, field in mapping:
2451 if self.__perhaps_invalidate_address_searcher(ctrl, field):
2452 return True
2453
2454 return True
2455
2457 adr = self._PRW_address_searcher.get_address()
2458 if adr is None:
2459 return True
2460
2461 wx.CallAfter(self.__set_fields_from_address_searcher)
2462 return True
2463
2464
2465
2467 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
2468
2470
2471
2472 new_identity = gmPerson.create_identity (
2473 gender = self._PRW_gender.GetData(),
2474 dob = self._DP_dob.get_pydt(),
2475 lastnames = self._PRW_lastname.GetValue().strip(),
2476 firstnames = self._PRW_firstnames.GetValue().strip()
2477 )
2478 _log.debug('identity created: %s' % new_identity)
2479
2480 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
2481 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
2482
2483 new_identity.save()
2484
2485 name = new_identity.get_active_name()
2486 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
2487 name.save()
2488
2489
2490 is_valid = self.__address_valid_for_save(empty_address_is_valid = False)
2491 if is_valid is True:
2492
2493
2494 try:
2495 new_identity.link_address (
2496 number = self._TCTRL_number.GetValue().strip(),
2497 street = self._PRW_street.GetValue().strip(),
2498 postcode = self._PRW_zip.GetValue().strip(),
2499 urb = self._PRW_urb.GetValue().strip(),
2500 state = self._PRW_region.GetData(),
2501 country = self._PRW_country.GetData()
2502 )
2503 except psycopg2.InternalError:
2504
2505 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip())
2506 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip())
2507 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip())
2508 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip())
2509 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip())
2510 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip())
2511 _log.exception('cannot link address')
2512 gmGuiHelpers.gm_show_error (
2513 aTitle = _('Saving address'),
2514 aMessage = _(
2515 'Cannot save this address.\n'
2516 '\n'
2517 'You will have to add it via the Demographics plugin.\n'
2518 )
2519 )
2520 elif is_valid is False:
2521 gmGuiHelpers.gm_show_error (
2522 aTitle = _('Saving address'),
2523 aMessage = _(
2524 'Address not saved.\n'
2525 '\n'
2526 'You will have to add it via the Demographics plugin.\n'
2527 )
2528 )
2529
2530
2531
2532 new_identity.link_comm_channel (
2533 comm_medium = u'homephone',
2534 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''),
2535 is_confidential = False
2536 )
2537
2538
2539 pk_type = self._PRW_external_id_type.GetData()
2540 id_value = self._TCTRL_external_id_value.GetValue().strip()
2541 if (pk_type is not None) and (id_value != u''):
2542 new_identity.add_external_id(value = id_value, pk_type = pk_type)
2543
2544
2545 new_identity.link_occupation (
2546 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'')
2547 )
2548
2549 self.data = new_identity
2550 return True
2551
2553 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2554
2558
2561
2563 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2564
2565
2566
2567 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
2568 """
2569 Wizard page for entering patient's basic demographic information
2570 """
2571
2572 form_fields = (
2573 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation',
2574 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment'
2575 )
2576
2577 - def __init__(self, parent, title):
2578 """
2579 Creates a new instance of BasicPatDetailsPage
2580 @param parent - The parent widget
2581 @type parent - A wx.Window instance
2582 @param tile - The title of the page
2583 @type title - A StringType instance
2584 """
2585 wx.wizard.WizardPageSimple.__init__(self, parent)
2586 self.__title = title
2587 self.__do_layout()
2588 self.__register_interests()
2589
2590 - def __do_layout(self):
2591 PNL_form = wx.Panel(self, -1)
2592
2593
2594 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name'))
2595 STT_lastname.SetForegroundColour('red')
2596 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1)
2597 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)'))
2598
2599
2600 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)'))
2601 STT_firstname.SetForegroundColour('red')
2602 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1)
2603 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name'))
2604
2605
2606 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name'))
2607 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1)
2608
2609
2610 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth'))
2611 STT_dob.SetForegroundColour('red')
2612 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1)
2613 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one"))
2614
2615
2616 STT_gender = wx.StaticText(PNL_form, -1, _('Gender'))
2617 STT_gender.SetForegroundColour('red')
2618 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1)
2619 self.PRW_gender.SetToolTipString(_("Required: gender of patient"))
2620
2621
2622 STT_title = wx.StaticText(PNL_form, -1, _('Title'))
2623 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1)
2624
2625
2626 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code'))
2627 STT_zip_code.SetForegroundColour('orange')
2628 self.PRW_zip_code = cZipcodePhraseWheel(parent = PNL_form, id = -1)
2629 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code"))
2630
2631
2632 STT_street = wx.StaticText(PNL_form, -1, _('Street'))
2633 STT_street.SetForegroundColour('orange')
2634 self.PRW_street = cStreetPhraseWheel(parent = PNL_form, id = -1)
2635 self.PRW_street.SetToolTipString(_("primary/home address: name of street"))
2636
2637
2638 STT_address_number = wx.StaticText(PNL_form, -1, _('Number'))
2639 STT_address_number.SetForegroundColour('orange')
2640 self.TTC_address_number = wx.TextCtrl(PNL_form, -1)
2641 self.TTC_address_number.SetToolTipString(_("primary/home address: address number"))
2642
2643
2644 STT_town = wx.StaticText(PNL_form, -1, _('Place'))
2645 STT_town.SetForegroundColour('orange')
2646 self.PRW_town = cUrbPhraseWheel(parent = PNL_form, id = -1)
2647 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/..."))
2648
2649
2650 STT_state = wx.StaticText(PNL_form, -1, _('Region'))
2651 STT_state.SetForegroundColour('orange')
2652 self.PRW_state = cStateSelectionPhraseWheel(parent=PNL_form, id=-1)
2653 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/..."))
2654
2655
2656 STT_country = wx.StaticText(PNL_form, -1, _('Country'))
2657 STT_country.SetForegroundColour('orange')
2658 self.PRW_country = cCountryPhraseWheel(parent = PNL_form, id = -1)
2659 self.PRW_country.SetToolTipString(_("primary/home address: country"))
2660
2661
2662 STT_phone = wx.StaticText(PNL_form, -1, _('Phone'))
2663 self.TTC_phone = wx.TextCtrl(PNL_form, -1)
2664 self.TTC_phone.SetToolTipString(_("phone number at home"))
2665
2666
2667 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
2668 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
2669
2670
2671 STT_comment = wx.StaticText(PNL_form, -1, _('Comment'))
2672 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1)
2673 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.'))
2674
2675
2676 self.form_DTD = cFormDTD(fields = self.__class__.form_fields)
2677 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD))
2678
2679
2680 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4)
2681 SZR_input.AddGrowableCol(1)
2682 SZR_input.Add(STT_lastname, 0, wx.SHAPED)
2683 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND)
2684 SZR_input.Add(STT_firstname, 0, wx.SHAPED)
2685 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND)
2686 SZR_input.Add(STT_nick, 0, wx.SHAPED)
2687 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND)
2688 SZR_input.Add(STT_dob, 0, wx.SHAPED)
2689 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND)
2690 SZR_input.Add(STT_gender, 0, wx.SHAPED)
2691 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND)
2692 SZR_input.Add(STT_title, 0, wx.SHAPED)
2693 SZR_input.Add(self.PRW_title, 1, wx.EXPAND)
2694 SZR_input.Add(STT_zip_code, 0, wx.SHAPED)
2695 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND)
2696 SZR_input.Add(STT_street, 0, wx.SHAPED)
2697 SZR_input.Add(self.PRW_street, 1, wx.EXPAND)
2698 SZR_input.Add(STT_address_number, 0, wx.SHAPED)
2699 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND)
2700 SZR_input.Add(STT_town, 0, wx.SHAPED)
2701 SZR_input.Add(self.PRW_town, 1, wx.EXPAND)
2702 SZR_input.Add(STT_state, 0, wx.SHAPED)
2703 SZR_input.Add(self.PRW_state, 1, wx.EXPAND)
2704 SZR_input.Add(STT_country, 0, wx.SHAPED)
2705 SZR_input.Add(self.PRW_country, 1, wx.EXPAND)
2706 SZR_input.Add(STT_phone, 0, wx.SHAPED)
2707 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND)
2708 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
2709 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
2710 SZR_input.Add(STT_comment, 0, wx.SHAPED)
2711 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND)
2712
2713 PNL_form.SetSizerAndFit(SZR_input)
2714
2715
2716 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title)
2717 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2718
2719
2720
2725
2726 - def on_country_selected(self, data):
2727 """Set the states according to entered country."""
2728 self.PRW_state.set_context(context=u'country', val=data)
2729 return True
2730
2731 - def on_name_set(self):
2732 """Set the gender according to entered firstname.
2733
2734 Matches are fetched from existing records in backend.
2735 """
2736 firstname = self.PRW_firstname.GetValue().strip()
2737 rows, idx = gmPG2.run_ro_queries(queries = [{
2738 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
2739 'args': [firstname]
2740 }])
2741 if len(rows) == 0:
2742 return True
2743 wx.CallAfter(self.PRW_gender.SetData, rows[0][0])
2744 return True
2745
2746 - def on_zip_set(self):
2747 """Set the street, town, state and country according to entered zip code."""
2748 zip_code = self.PRW_zip_code.GetValue().strip()
2749 self.PRW_street.set_context(context=u'zip', val=zip_code)
2750 self.PRW_town.set_context(context=u'zip', val=zip_code)
2751 self.PRW_state.set_context(context=u'zip', val=zip_code)
2752 self.PRW_country.set_context(context=u'zip', val=zip_code)
2753 return True
2754
2756 """
2757 Wizard to create a new patient.
2758
2759 TODO:
2760 - write pages for different "themes" of patient creation
2761 - make it configurable which pages are loaded
2762 - make available sets of pages that apply to a country
2763 - make loading of some pages depend upon values in earlier pages, eg
2764 when the patient is female and older than 13 include a page about
2765 "female" data (number of kids etc)
2766
2767 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable()
2768 """
2769
2770 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2771 """
2772 Creates a new instance of NewPatientWizard
2773 @param parent - The parent widget
2774 @type parent - A wx.Window instance
2775 """
2776 id_wiz = wx.NewId()
2777 wx.wizard.Wizard.__init__(self, parent, id_wiz, title)
2778 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
2779 self.__subtitle = subtitle
2780 self.__do_layout()
2781
2783 """Create new patient.
2784
2785 activate, too, if told to do so (and patient successfully created)
2786 """
2787 while True:
2788
2789 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details):
2790 return False
2791
2792 try:
2793
2794 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD)
2795 except:
2796 _log.exception('cannot add new patient - missing identity fields')
2797 gmGuiHelpers.gm_show_error (
2798 _('Cannot create new patient.\n'
2799 'Missing parts of the identity.'
2800 ),
2801 _('Adding new patient')
2802 )
2803 continue
2804
2805 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2806
2807 try:
2808 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2809 except:
2810 _log.exception('cannot finalize new patient - missing address fields')
2811 gmGuiHelpers.gm_show_error (
2812 _('Cannot add address for the new patient.\n'
2813 'You must either enter all of the address fields or\n'
2814 'none at all. The relevant fields are marked in yellow.\n'
2815 '\n'
2816 'You will need to add the address details in the\n'
2817 'demographics module.'
2818 ),
2819 _('Adding new patient')
2820 )
2821 break
2822
2823 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2824
2825 break
2826
2827 if activate:
2828 from Gnumed.wxpython import gmPatSearchWidgets
2829 gmPatSearchWidgets.set_active_patient(patient = ident)
2830
2831 return ident
2832
2833
2834
2836 """Arrange widgets.
2837 """
2838
2839 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle )
2840 self.FitToPage(self.basic_pat_details)
2841
2843 """
2844 This validator is used to ensure that the user has entered all
2845 the required conditional values in the page (eg., to properly
2846 create an address, all the related fields must be filled).
2847 """
2848
2849 - def __init__(self, dtd):
2850 """
2851 Validator initialization.
2852 @param dtd The object containing the data model.
2853 @type dtd A cFormDTD instance
2854 """
2855
2856 wx.PyValidator.__init__(self)
2857
2858 self.form_DTD = dtd
2859
2861 """
2862 Standard cloner.
2863 Note that every validator must implement the Clone() method.
2864 """
2865 return cBasicPatDetailsPageValidator(dtd = self.form_DTD)
2866
2867 - def Validate(self, parent = None):
2868 """
2869 Validate the contents of the given text control.
2870 """
2871 _pnl_form = self.GetWindow().GetParent()
2872
2873 error = False
2874
2875
2876 if _pnl_form.PRW_lastname.GetValue().strip() == '':
2877 error = True
2878 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2879 _pnl_form.PRW_lastname.SetBackgroundColour('pink')
2880 _pnl_form.PRW_lastname.Refresh()
2881 else:
2882 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2883 _pnl_form.PRW_lastname.Refresh()
2884
2885 if _pnl_form.PRW_firstname.GetValue().strip() == '':
2886 error = True
2887 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2888 _pnl_form.PRW_firstname.SetBackgroundColour('pink')
2889 _pnl_form.PRW_firstname.Refresh()
2890 else:
2891 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2892 _pnl_form.PRW_firstname.Refresh()
2893
2894
2895 if _pnl_form.PRW_gender.GetData() is None:
2896 error = True
2897 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2898 _pnl_form.PRW_gender.SetBackgroundColour('pink')
2899 _pnl_form.PRW_gender.Refresh()
2900 else:
2901 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2902 _pnl_form.PRW_gender.Refresh()
2903
2904
2905 if (
2906 (_pnl_form.PRW_dob.GetValue().strip() == u'')
2907 or (not _pnl_form.PRW_dob.is_valid_timestamp())
2908 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900)
2909 ):
2910 error = True
2911 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue()
2912 gmDispatcher.send(signal = 'statustext', msg = msg)
2913 _pnl_form.PRW_dob.SetBackgroundColour('pink')
2914 _pnl_form.PRW_dob.Refresh()
2915 else:
2916 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2917 _pnl_form.PRW_dob.Refresh()
2918
2919
2920 is_any_field_filled = False
2921 address_fields = (
2922 _pnl_form.TTC_address_number,
2923 _pnl_form.PRW_zip_code,
2924 _pnl_form.PRW_street,
2925 _pnl_form.PRW_town
2926 )
2927 for field in address_fields:
2928 if field.GetValue().strip() == u'':
2929 if is_any_field_filled:
2930 error = True
2931 msg = _('To properly create an address, all the related fields must be filled in.')
2932 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2933 field.SetBackgroundColour('pink')
2934 field.SetFocus()
2935 field.Refresh()
2936 else:
2937 is_any_field_filled = True
2938 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2939 field.Refresh()
2940
2941 address_fields = (
2942 _pnl_form.PRW_state,
2943 _pnl_form.PRW_country
2944 )
2945 for field in address_fields:
2946 if field.GetData() is None:
2947 if is_any_field_filled:
2948 error = True
2949 msg = _('To properly create an address, all the related fields must be filled in.')
2950 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2951 field.SetBackgroundColour('pink')
2952 field.SetFocus()
2953 field.Refresh()
2954 else:
2955 is_any_field_filled = True
2956 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2957 field.Refresh()
2958
2959 return (not error)
2960
2961 - def TransferToWindow(self):
2962 """
2963 Transfer data from validator to window.
2964 The default implementation returns False, indicating that an error
2965 occurred. We simply return True, as we don't do any data transfer.
2966 """
2967 _pnl_form = self.GetWindow().GetParent()
2968
2969 _pnl_form.PRW_gender.SetData(self.form_DTD['gender'])
2970 _pnl_form.PRW_dob.SetText(self.form_DTD['dob'])
2971 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames'])
2972 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames'])
2973 _pnl_form.PRW_title.SetText(self.form_DTD['title'])
2974 _pnl_form.PRW_nick.SetText(self.form_DTD['nick'])
2975 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation'])
2976 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number'])
2977 _pnl_form.PRW_street.SetText(self.form_DTD['street'])
2978 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code'])
2979 _pnl_form.PRW_town.SetText(self.form_DTD['town'])
2980 _pnl_form.PRW_state.SetData(self.form_DTD['state'])
2981 _pnl_form.PRW_country.SetData(self.form_DTD['country'])
2982 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone'])
2983 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment'])
2984 return True
2985
2987 """
2988 Transfer data from window to validator.
2989 The default implementation returns False, indicating that an error
2990 occurred. We simply return True, as we don't do any data transfer.
2991 """
2992
2993 if not self.GetWindow().GetParent().Validate():
2994 return False
2995 try:
2996 _pnl_form = self.GetWindow().GetParent()
2997
2998 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData()
2999 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData()
3000
3001 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue()
3002 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue()
3003 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue()
3004 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue()
3005
3006 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue()
3007
3008 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue()
3009 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue()
3010 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue()
3011 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue()
3012 self.form_DTD['state'] = _pnl_form.PRW_state.GetData()
3013 self.form_DTD['country'] = _pnl_form.PRW_country.GetData()
3014
3015 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue()
3016
3017 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue()
3018 except:
3019 return False
3020 return True
3021
3066
3067
3068
3070 """Notebook displaying demographics editing pages:
3071
3072 - Identity
3073 - Contacts (addresses, phone numbers, etc)
3074 - Social Net
3075
3076 Does NOT act on/listen to the current patient.
3077 """
3078
3080
3081 wx.Notebook.__init__ (
3082 self,
3083 parent = parent,
3084 id = id,
3085 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
3086 name = self.__class__.__name__
3087 )
3088
3089 self.__identity = None
3090 self.__do_layout()
3091 self.SetSelection(0)
3092
3093
3094
3096 """Populate fields in pages with data from model."""
3097 for page_idx in range(self.GetPageCount()):
3098 page = self.GetPage(page_idx)
3099 page.identity = self.__identity
3100
3101 return True
3102
3103
3104
3106 """Build patient edition notebook pages."""
3107
3108 new_page = cPersonContactsManagerPnl(self, -1)
3109 new_page.identity = self.__identity
3110 self.AddPage (
3111 page = new_page,
3112 text = _('Contacts'),
3113 select = True
3114 )
3115
3116
3117 new_page = cPersonIdentityManagerPnl(self, -1)
3118 new_page.identity = self.__identity
3119 self.AddPage (
3120 page = new_page,
3121 text = _('Identity'),
3122 select = False
3123 )
3124
3125
3126
3128 return self.__identity
3129
3132
3133 identity = property(_get_identity, _set_identity)
3134
3135
3136
3137
3139 """Page containing patient occupations edition fields.
3140 """
3141 - def __init__(self, parent, id, ident=None):
3142 """
3143 Creates a new instance of BasicPatDetailsPage
3144 @param parent - The parent widget
3145 @type parent - A wx.Window instance
3146 @param id - The widget id
3147 @type id - An integer
3148 """
3149 wx.Panel.__init__(self, parent, id)
3150 self.__ident = ident
3151 self.__do_layout()
3152
3154 PNL_form = wx.Panel(self, -1)
3155
3156 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
3157 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
3158 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
3159
3160 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
3161 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
3162
3163
3164 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
3165 SZR_input.AddGrowableCol(1)
3166 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
3167 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
3168 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
3169 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
3170 PNL_form.SetSizerAndFit(SZR_input)
3171
3172
3173 SZR_main = wx.BoxSizer(wx.VERTICAL)
3174 SZR_main.Add(PNL_form, 1, wx.EXPAND)
3175 self.SetSizer(SZR_main)
3176
3179
3180 - def refresh(self, identity=None):
3181 if identity is not None:
3182 self.__ident = identity
3183 jobs = self.__ident.get_occupations()
3184 if len(jobs) > 0:
3185 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
3186 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
3187 return True
3188
3190 if self.PRW_occupation.IsModified():
3191 new_job = self.PRW_occupation.GetValue().strip()
3192 jobs = self.__ident.get_occupations()
3193 for job in jobs:
3194 if job['l10n_occupation'] == new_job:
3195 continue
3196 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
3197 self.__ident.link_occupation(occupation = new_job)
3198 return True
3199
3201 """Patient demographics plugin for main notebook.
3202
3203 Hosts another notebook with pages for Identity, Contacts, etc.
3204
3205 Acts on/listens to the currently active patient.
3206 """
3207
3213
3214
3215
3216
3217
3218
3220 """Arrange widgets."""
3221 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
3222
3223 szr_main = wx.BoxSizer(wx.VERTICAL)
3224 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
3225 self.SetSizerAndFit(szr_main)
3226
3227
3228
3230 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
3231 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
3232
3234 self._schedule_data_reget()
3235
3237 self._schedule_data_reget()
3238
3239
3240
3250
3252 """
3253 Register a new patient, given the data supplied in the
3254 Data Transfer Dictionary object.
3255
3256 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3257 supplied data.
3258 @type basic_details_DTD A cFormDTD instance.
3259 """
3260 new_identity = gmPerson.create_identity (
3261 gender = dtd['gender'],
3262 dob = dtd['dob'].get_pydt(),
3263 lastnames = dtd['lastnames'],
3264 firstnames = dtd['firstnames']
3265 )
3266 if new_identity is None:
3267 _log.error('cannot create identity from %s' % str(dtd))
3268 return None
3269 _log.debug('identity created: %s' % new_identity)
3270
3271 if dtd['comment'] is not None:
3272 if dtd['comment'].strip() != u'':
3273 name = new_identity.get_active_name()
3274 name['comment'] = dtd['comment']
3275 name.save_payload()
3276
3277 return new_identity
3278
3280 """
3281 Update patient details with data supplied by
3282 Data Transfer Dictionary object.
3283
3284 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3285 supplied data.
3286 @type basic_details_DTD A cFormDTD instance.
3287 """
3288
3289 if identity['gender'] != dtd['gender']:
3290 identity['gender'] = dtd['gender']
3291 if identity['dob'] != dtd['dob'].get_pydt():
3292 identity['dob'] = dtd['dob'].get_pydt()
3293 if len(dtd['title']) > 0 and identity['title'] != dtd['title']:
3294 identity['title'] = dtd['title']
3295
3296
3297
3298
3299 identity.save_payload()
3300
3301
3302
3303 if identity['firstnames'] != dtd['firstnames'] or identity['lastnames'] != dtd['lastnames']:
3304 new_name = identity.add_name(firstnames = dtd['firstnames'], lastnames = dtd['lastnames'], active = True)
3305
3306 if len(dtd['nick']) > 0 and identity['preferred'] != dtd['nick']:
3307 identity.set_nickname(nickname = dtd['nick'])
3308
3309 return True
3310
3354
3356 """
3357 Update patient details with data supplied by
3358 Data Transfer Dictionary object.
3359
3360 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3361 supplied data.
3362 @type basic_details_DTD A cFormDTD instance.
3363 """
3364 identity.link_occupation(occupation = dtd['occupation'])
3365
3366 return True
3367
3369 """
3370 Utility class to test the new patient wizard.
3371 """
3372
3374 """
3375 Create a new instance of TestPanel.
3376 @param parent The parent widget
3377 @type parent A wx.Window instance
3378 """
3379 wx.Panel.__init__(self, parent, id)
3380 wizard = cNewPatientWizard(self)
3381 print wizard.RunWizard()
3382
3383 if __name__ == "__main__":
3384
3385
3387 app = wx.PyWidgetTester(size = (200, 50))
3388 pw = cZipcodePhraseWheel(app.frame, -1)
3389 app.frame.Show(True)
3390 app.MainLoop()
3391
3393 app = wx.PyWidgetTester(size = (200, 50))
3394 pw = cStateSelectionPhraseWheel(app.frame, -1)
3395
3396
3397 app.frame.Show(True)
3398 app.MainLoop()
3399
3401 app = wx.PyWidgetTester(size = (200, 50))
3402 pw = cUrbPhraseWheel(app.frame, -1)
3403 app.frame.Show(True)
3404 pw.set_context(context = u'zip', val = u'04317')
3405 app.MainLoop()
3406
3408 app = wx.PyWidgetTester(size = (200, 50))
3409 pw = cSuburbPhraseWheel(app.frame, -1)
3410 app.frame.Show(True)
3411 app.MainLoop()
3412
3414 app = wx.PyWidgetTester(size = (200, 50))
3415 pw = cAddressTypePhraseWheel(app.frame, -1)
3416 app.frame.Show(True)
3417 app.MainLoop()
3418
3420 app = wx.PyWidgetTester(size = (200, 50))
3421 pw = cAddressPhraseWheel(app.frame, -1)
3422 app.frame.Show(True)
3423 app.MainLoop()
3424
3426 app = wx.PyWidgetTester(size = (200, 50))
3427 pw = cStreetPhraseWheel(app.frame, -1)
3428
3429 app.frame.Show(True)
3430 app.MainLoop()
3431
3433 app = wx.PyWidgetTester(size = (600, 400))
3434 app.SetWidget(cKOrganizerSchedulePnl)
3435 app.MainLoop()
3436
3438 app = wx.PyWidgetTester(size = (600, 400))
3439 widget = cPersonNamesManagerPnl(app.frame, -1)
3440 widget.identity = activate_patient()
3441 app.frame.Show(True)
3442 app.MainLoop()
3443
3445 app = wx.PyWidgetTester(size = (600, 400))
3446 widget = cPersonIDsManagerPnl(app.frame, -1)
3447 widget.identity = activate_patient()
3448 app.frame.Show(True)
3449 app.MainLoop()
3450
3452 app = wx.PyWidgetTester(size = (600, 400))
3453 widget = cPersonIdentityManagerPnl(app.frame, -1)
3454 widget.identity = activate_patient()
3455 app.frame.Show(True)
3456 app.MainLoop()
3457
3462
3467
3469 app = wx.PyWidgetTester(size = (600, 400))
3470 widget = cPersonAddressesManagerPnl(app.frame, -1)
3471 widget.identity = activate_patient()
3472 app.frame.Show(True)
3473 app.MainLoop()
3474
3476 app = wx.PyWidgetTester(size = (600, 400))
3477 widget = cPersonCommsManagerPnl(app.frame, -1)
3478 widget.identity = activate_patient()
3479 app.frame.Show(True)
3480 app.MainLoop()
3481
3488
3490 app = wx.PyWidgetTester(size = (600, 400))
3491 widget = cPersonDemographicsEditorNb(app.frame, -1)
3492 widget.identity = activate_patient()
3493 widget.refresh()
3494 app.frame.Show(True)
3495 app.MainLoop()
3496
3505
3506 if len(sys.argv) > 1 and sys.argv[1] == 'test':
3507
3508 gmI18N.activate_locale()
3509 gmI18N.install_domain(domain='gnumed')
3510 gmPG2.get_connection()
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527 test_urb_prw()
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545