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 wxgNameGenderDOBEditAreaPnl
1611
1613 """An edit area for editing/creating name/gender/dob.
1614
1615 Does NOT act on/listen to the current patient.
1616 """
1627
1628
1629
1631 if self.__name is None:
1632 return
1633
1634 self._PRW_title.SetText(gmTools.coalesce(self.__name['title'], u''))
1635 self._PRW_firstname.SetText(self.__name['firstnames'])
1636 self._PRW_lastname.SetText(self.__name['lastnames'])
1637 self._PRW_nick.SetText(gmTools.coalesce(self.__name['preferred'], u''))
1638 dob = self.__identity['dob']
1639 self._PRW_dob.SetText(value = dob.strftime('%Y-%m-%d %H:%M'), data = dob)
1640 self._PRW_gender.SetData(self.__name['gender'])
1641 self._CHBOX_active.SetValue(self.__name['active_name'])
1642 self._DP_dod.SetValue(self.__identity['deceased'])
1643 self._TCTRL_comment.SetValue(gmTools.coalesce(self.__name['comment'], u''))
1644
1645
1646
1647
1649
1650 if not self.__valid_for_save():
1651 return False
1652
1653 self.__identity['gender'] = self._PRW_gender.GetData()
1654 if self._PRW_dob.GetValue().strip() == u'':
1655 self.__identity['dob'] = None
1656 else:
1657 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt()
1658 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
1659 self.__identity['deceased'] = self._DP_dod.GetValue(as_pydt = True)
1660 self.__identity.save_payload()
1661
1662 active = self._CHBOX_active.GetValue()
1663 first = self._PRW_firstname.GetValue().strip()
1664 last = self._PRW_lastname.GetValue().strip()
1665 old_nick = self.__name['preferred']
1666
1667
1668 old_name = self.__name['firstnames'] + self.__name['lastnames']
1669 if (first + last) != old_name:
1670 self.__name = self.__identity.add_name(first, last, active)
1671
1672 self.__name['active_name'] = active
1673 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
1674 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1675
1676 self.__name.save_payload()
1677
1678 return True
1679
1680
1681
1684
1686 """Set the gender according to entered firstname.
1687
1688 Matches are fetched from existing records in backend.
1689 """
1690 firstname = self._PRW_firstname.GetValue().strip()
1691 if firstname == u'':
1692 return True
1693 rows, idx = gmPG2.run_ro_queries(queries = [{
1694 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
1695 'args': [firstname]
1696 }])
1697 if len(rows) == 0:
1698 return True
1699 wx.CallAfter(self._PRW_gender.SetData, rows[0][0])
1700 return True
1701
1702
1703
1705
1706 has_error = False
1707
1708 if self._PRW_gender.GetData() is None:
1709 self._PRW_gender.SetBackgroundColour('pink')
1710 self._PRW_gender.Refresh()
1711 self._PRW_gender.SetFocus()
1712 has_error = True
1713 else:
1714 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1715 self._PRW_gender.Refresh()
1716
1717 if not self._PRW_dob.is_valid_timestamp():
1718 val = self._PRW_dob.GetValue().strip()
1719 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
1720 self._PRW_dob.SetBackgroundColour('pink')
1721 self._PRW_dob.Refresh()
1722 self._PRW_dob.SetFocus()
1723 has_error = True
1724 else:
1725 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1726 self._PRW_dob.Refresh()
1727
1728 if not self._DP_dod.is_valid_timestamp():
1729 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
1730 self._DP_dod.SetBackgroundColour('pink')
1731 self._DP_dod.Refresh()
1732 self._DP_dod.SetFocus()
1733 has_error = True
1734 else:
1735 self._DP_dod.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1736 self._DP_dod.Refresh()
1737
1738 if self._PRW_lastname.GetValue().strip() == u'':
1739 self._PRW_lastname.SetBackgroundColour('pink')
1740 self._PRW_lastname.Refresh()
1741 self._PRW_lastname.SetFocus()
1742 has_error = True
1743 else:
1744 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1745 self._PRW_lastname.Refresh()
1746
1747 if self._PRW_firstname.GetValue().strip() == u'':
1748 self._PRW_firstname.SetBackgroundColour('pink')
1749 self._PRW_firstname.Refresh()
1750 self._PRW_firstname.SetFocus()
1751 has_error = True
1752 else:
1753 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1754 self._PRW_firstname.Refresh()
1755
1756 return (has_error is False)
1757
1758
1759
1761 """A list for managing a person's names.
1762
1763 Does NOT act on/listen to the current patient.
1764 """
1782
1783
1784
1785 - def refresh(self, *args, **kwargs):
1786 if self.__identity is None:
1787 self._LCTRL_items.set_string_items()
1788 return
1789
1790 names = self.__identity.get_names()
1791 self._LCTRL_items.set_string_items (
1792 items = [ [
1793 gmTools.bool2str(n['active_name'], 'X', ''),
1794 gmTools.coalesce(n['title'], gmPerson.map_gender2salutation(n['gender'])),
1795 n['lastnames'],
1796 n['firstnames'],
1797 gmTools.coalesce(n['preferred'], u''),
1798 gmTools.coalesce(n['comment'], u'')
1799 ] for n in names ]
1800 )
1801 self._LCTRL_items.set_column_widths()
1802 self._LCTRL_items.set_data(data = names)
1803
1804
1805
1807 self._LCTRL_items.set_columns(columns = [
1808 _('Active'),
1809 _('Title'),
1810 _('Lastname'),
1811 _('Firstname(s)'),
1812 _('Preferred Name'),
1813 _('Comment')
1814 ])
1815
1825
1835
1837
1838 if len(self.__identity.get_names()) == 1:
1839 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1840 return False
1841
1842 go_ahead = gmGuiHelpers.gm_show_question (
1843 _( 'It is often advisable to keep old names around and\n'
1844 'just create a new "currently active" name.\n'
1845 '\n'
1846 'This allows finding the patient by both the old\n'
1847 'and the new name (think before/after marriage).\n'
1848 '\n'
1849 'Do you still want to really delete\n'
1850 "this name from the patient ?"
1851 ),
1852 _('Deleting name')
1853 )
1854 if not go_ahead:
1855 return False
1856
1857 self.__identity.delete_name(name = name)
1858 return True
1859
1860
1861
1863 return self.__identity
1864
1868
1869 identity = property(_get_identity, _set_identity)
1870
1872 """A list for managing a person's external IDs.
1873
1874 Does NOT act on/listen to the current patient.
1875 """
1893
1894
1895
1896 - def refresh(self, *args, **kwargs):
1914
1915
1916
1918 self._LCTRL_items.set_columns(columns = [
1919 _('ID type'),
1920 _('Value'),
1921 _('Issuer'),
1922 _('Context'),
1923 _('Comment')
1924 ])
1925
1936
1947
1949 go_ahead = gmGuiHelpers.gm_show_question (
1950 _( 'Do you really want to delete this\n'
1951 'external ID from the patient ?'),
1952 _('Deleting external ID')
1953 )
1954 if not go_ahead:
1955 return False
1956 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
1957 return True
1958
1959
1960
1962 return self.__identity
1963
1967
1968 identity = property(_get_identity, _set_identity)
1969
1970
1971
1973 """A panel for editing identity data for a person.
1974
1975 - provides access to:
1976 - name
1977 - external IDs
1978
1979 Does NOT act on/listen to the current patient.
1980 """
1987
1988
1989
1991 self._PNL_names.identity = self.__identity
1992 self._PNL_ids.identity = self.__identity
1993
1994
1995
1997 return self.__identity
1998
2002
2003 identity = property(_get_identity, _set_identity)
2004
2005
2006
2008
2009 dbcfg = gmCfg.cCfgSQL()
2010
2011 def_region = dbcfg.get2 (
2012 option = u'person.create.default_region',
2013 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2014 bias = u'user'
2015 )
2016
2017 if def_region is None:
2018 def_country = dbcfg.get2 (
2019 option = u'person.create.default_country',
2020 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2021 bias = u'user'
2022 )
2023 else:
2024 countries = gmDemographicRecord.get_country_for_region(region = def_region)
2025 if len(countries) == 1:
2026 def_country = countries[0]['l10n_country']
2027
2028 if parent is None:
2029 parent = wx.GetApp().GetTopWindow()
2030
2031 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region)
2032 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
2033 dlg.SetTitle(_('Adding new person'))
2034 ea._PRW_lastname.SetFocus()
2035 result = dlg.ShowModal()
2036 pat = ea.data
2037 dlg.Destroy()
2038
2039 if result != wx.ID_OK:
2040 return False
2041
2042 if activate:
2043 from Gnumed.wxpython import gmPatSearchWidgets
2044 gmPatSearchWidgets.set_active_patient(patient = pat)
2045
2046 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin')
2047
2048 return True
2049
2050 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl
2051
2052 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
2053
2055
2056 try:
2057 self.default_region = kwargs['region']
2058 del kwargs['region']
2059 except KeyError:
2060 self.default_region = None
2061
2062 try:
2063 self.default_country = kwargs['country']
2064 del kwargs['country']
2065 except KeyError:
2066 self.default_country = None
2067
2068 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs)
2069 gmEditArea.cGenericEditAreaMixin.__init__(self)
2070
2071 self.mode = 'new'
2072 self.data = None
2073 self._address = None
2074
2075 self.__init_ui()
2076 self.__register_interests()
2077
2078
2079
2081 self._PRW_lastname.final_regex = '.+'
2082 self._PRW_firstnames.final_regex = '.+'
2083 self._PRW_address_searcher.selection_only = False
2084 low = wx.DateTimeFromDMY(1,0,1900)
2085 hi = wx.DateTime()
2086 self._DP_dob.SetRange(low, hi.SetToCurrent())
2087
2088
2089
2090 if self.default_country is not None:
2091 self._PRW_country.SetText(value = self.default_country)
2092
2093 if self.default_region is not None:
2094 self._PRW_region.SetText(value = self.default_region)
2095
2097
2098 adr = self._PRW_address_searcher.get_address()
2099 if adr is None:
2100 return True
2101
2102 if ctrl.GetValue().strip() != adr[field]:
2103 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None)
2104 return True
2105
2106 return False
2107
2109 adr = self._PRW_address_searcher.get_address()
2110 if adr is None:
2111 return True
2112
2113 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode'])
2114
2115 self._PRW_street.SetText(value = adr['street'], data = adr['street'])
2116 self._PRW_street.set_context(context = u'zip', val = adr['postcode'])
2117
2118 self._TCTRL_number.SetValue(adr['number'])
2119
2120 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb'])
2121 self._PRW_urb.set_context(context = u'zip', val = adr['postcode'])
2122
2123 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state'])
2124 self._PRW_region.set_context(context = u'zip', val = adr['postcode'])
2125
2126 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country'])
2127 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
2128
2130 error = False
2131
2132
2133 if self._PRW_lastname.GetValue().strip() == u'':
2134 error = True
2135 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2136 self._PRW_lastname.display_as_valid(False)
2137 else:
2138 self._PRW_lastname.display_as_valid(True)
2139
2140 if self._PRW_firstnames.GetValue().strip() == '':
2141 error = True
2142 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2143 self._PRW_firstnames.display_as_valid(False)
2144 else:
2145 self._PRW_firstnames.display_as_valid(True)
2146
2147
2148 if self._PRW_gender.GetData() is None:
2149 error = True
2150 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2151 self._PRW_gender.display_as_valid(False)
2152 else:
2153 self._PRW_gender.display_as_valid(True)
2154
2155
2156 if not self._DP_dob.is_valid_timestamp():
2157
2158 gmDispatcher.send(signal = 'statustext', msg = _('Cannot use this date of birth. Does it lie before 1900 ?'))
2159
2160 do_it_anyway = gmGuiHelpers.gm_show_question (
2161 _(
2162 'Are you sure you want to register this person\n'
2163 'without a valid date of birth ?\n'
2164 '\n'
2165 'This can be useful for temporary staff members\n'
2166 'but will provoke nag screens if this person\n'
2167 'becomes a patient.\n'
2168 '\n'
2169 'Note that the date of birth cannot technically\n'
2170 'be before 1900, either :-(\n'
2171 ),
2172 _('Registering new person')
2173 )
2174
2175 if not do_it_anyway:
2176 error = True
2177
2178 if self._DP_dob.GetValue() is None:
2179 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2180 elif self._DP_dob.GetValue().GetYear() < 1900:
2181 error = True
2182 gmDispatcher.send(signal = 'statustext', msg = _('The year of birth must lie after 1900.'), beep = True)
2183 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2184 self._DP_dob.SetFocus()
2185 else:
2186 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2187 self._DP_dob.Refresh()
2188
2189
2190
2191
2192 return (not error)
2193
2195
2196
2197 if self._PRW_address_searcher.GetData() is not None:
2198 wx.CallAfter(self.__set_fields_from_address_searcher)
2199 return True
2200
2201
2202 fields_to_fill = (
2203 self._TCTRL_number,
2204 self._PRW_zip,
2205 self._PRW_street,
2206 self._PRW_urb,
2207 self._PRW_region,
2208 self._PRW_country
2209 )
2210 no_of_filled_fields = 0
2211
2212 for field in fields_to_fill:
2213 if field.GetValue().strip() != u'':
2214 no_of_filled_fields += 1
2215 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2216 field.Refresh()
2217
2218
2219 if no_of_filled_fields == 0:
2220 if empty_address_is_valid:
2221 return True
2222 else:
2223 return None
2224
2225
2226 if no_of_filled_fields != len(fields_to_fill):
2227 for field in fields_to_fill:
2228 if field.GetValue().strip() == u'':
2229 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2230 field.SetFocus()
2231 field.Refresh()
2232 msg = _('To properly create an address, all the related fields must be filled in.')
2233 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2234 return False
2235
2236
2237
2238
2239 strict_fields = (
2240 self._PRW_region,
2241 self._PRW_country
2242 )
2243 error = False
2244 for field in strict_fields:
2245 if field.GetData() is None:
2246 error = True
2247 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2248 field.SetFocus()
2249 else:
2250 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2251 field.Refresh()
2252
2253 if error:
2254 msg = _('This field must contain an item selected from the dropdown list.')
2255 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2256 return False
2257
2258 return True
2259
2276
2277
2278
2280 """Set the gender according to entered firstname.
2281
2282 Matches are fetched from existing records in backend.
2283 """
2284
2285
2286 if self._PRW_gender.GetData() is not None:
2287 return True
2288
2289 firstname = self._PRW_firstnames.GetValue().strip()
2290 if firstname == u'':
2291 return True
2292
2293 gender = gmPerson.map_firstnames2gender(firstnames = firstname)
2294 if gender is None:
2295 return True
2296
2297 wx.CallAfter(self._PRW_gender.SetData, gender)
2298 return True
2299
2301 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode')
2302
2303 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'')
2304 self._PRW_street.set_context(context = u'zip', val = zip_code)
2305 self._PRW_urb.set_context(context = u'zip', val = zip_code)
2306 self._PRW_region.set_context(context = u'zip', val = zip_code)
2307 self._PRW_country.set_context(context = u'zip', val = zip_code)
2308
2309 return True
2310
2312 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country')
2313
2314 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'')
2315 self._PRW_region.set_context(context = u'country', val = country)
2316
2317 return True
2318
2320 mapping = [
2321 (self._PRW_street, 'street'),
2322 (self._TCTRL_number, 'number'),
2323 (self._PRW_urb, 'urb'),
2324 (self._PRW_region, 'l10n_state')
2325 ]
2326
2327
2328 for ctrl, field in mapping:
2329 if self.__perhaps_invalidate_address_searcher(ctrl, field):
2330 return True
2331
2332 return True
2333
2335 adr = self._PRW_address_searcher.get_address()
2336 if adr is None:
2337 return True
2338
2339 wx.CallAfter(self.__set_fields_from_address_searcher)
2340 return True
2341
2342
2343
2345 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
2346
2348
2349
2350 new_identity = gmPerson.create_identity (
2351 gender = self._PRW_gender.GetData(),
2352 dob = self._DP_dob.get_pydt(),
2353 lastnames = self._PRW_lastname.GetValue().strip(),
2354 firstnames = self._PRW_firstnames.GetValue().strip()
2355 )
2356 _log.debug('identity created: %s' % new_identity)
2357
2358 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
2359 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
2360
2361 new_identity.save()
2362
2363 name = new_identity.get_active_name()
2364 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
2365 name.save()
2366
2367
2368 is_valid = self.__address_valid_for_save(empty_address_is_valid = False)
2369 if is_valid is True:
2370
2371
2372 try:
2373 new_identity.link_address (
2374 number = self._TCTRL_number.GetValue().strip(),
2375 street = self._PRW_street.GetValue().strip(),
2376 postcode = self._PRW_zip.GetValue().strip(),
2377 urb = self._PRW_urb.GetValue().strip(),
2378 state = self._PRW_region.GetData(),
2379 country = self._PRW_country.GetData()
2380 )
2381 except psycopg2.InternalError:
2382
2383 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip())
2384 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip())
2385 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip())
2386 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip())
2387 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip())
2388 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip())
2389 _log.exception('cannot link address')
2390 gmGuiHelpers.gm_show_error (
2391 aTitle = _('Saving address'),
2392 aMessage = _(
2393 'Cannot save this address.\n'
2394 '\n'
2395 'You will have to add it via the Demographics plugin.\n'
2396 )
2397 )
2398 elif is_valid is False:
2399 gmGuiHelpers.gm_show_error (
2400 aTitle = _('Saving address'),
2401 aMessage = _(
2402 'Address not saved.\n'
2403 '\n'
2404 'You will have to add it via the Demographics plugin.\n'
2405 )
2406 )
2407
2408
2409
2410 new_identity.link_comm_channel (
2411 comm_medium = u'homephone',
2412 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''),
2413 is_confidential = False
2414 )
2415
2416
2417 pk_type = self._PRW_external_id_type.GetData()
2418 id_value = self._TCTRL_external_id_value.GetValue().strip()
2419 if (pk_type is not None) and (id_value != u''):
2420 new_identity.add_external_id(value = id_value, pk_type = pk_type)
2421
2422
2423 new_identity.link_occupation (
2424 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'')
2425 )
2426
2427 self.data = new_identity
2428 return True
2429
2431 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2432
2436
2439
2441 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2442
2443
2444
2445 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
2446 """
2447 Wizard page for entering patient's basic demographic information
2448 """
2449
2450 form_fields = (
2451 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation',
2452 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment'
2453 )
2454
2455 - def __init__(self, parent, title):
2456 """
2457 Creates a new instance of BasicPatDetailsPage
2458 @param parent - The parent widget
2459 @type parent - A wx.Window instance
2460 @param tile - The title of the page
2461 @type title - A StringType instance
2462 """
2463 wx.wizard.WizardPageSimple.__init__(self, parent)
2464 self.__title = title
2465 self.__do_layout()
2466 self.__register_interests()
2467
2468 - def __do_layout(self):
2469 PNL_form = wx.Panel(self, -1)
2470
2471
2472 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name'))
2473 STT_lastname.SetForegroundColour('red')
2474 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1)
2475 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)'))
2476
2477
2478 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)'))
2479 STT_firstname.SetForegroundColour('red')
2480 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1)
2481 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name'))
2482
2483
2484 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name'))
2485 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1)
2486
2487
2488 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth'))
2489 STT_dob.SetForegroundColour('red')
2490 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1)
2491 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one"))
2492
2493
2494 STT_gender = wx.StaticText(PNL_form, -1, _('Gender'))
2495 STT_gender.SetForegroundColour('red')
2496 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1)
2497 self.PRW_gender.SetToolTipString(_("Required: gender of patient"))
2498
2499
2500 STT_title = wx.StaticText(PNL_form, -1, _('Title'))
2501 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1)
2502
2503
2504 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code'))
2505 STT_zip_code.SetForegroundColour('orange')
2506 self.PRW_zip_code = cZipcodePhraseWheel(parent = PNL_form, id = -1)
2507 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code"))
2508
2509
2510 STT_street = wx.StaticText(PNL_form, -1, _('Street'))
2511 STT_street.SetForegroundColour('orange')
2512 self.PRW_street = cStreetPhraseWheel(parent = PNL_form, id = -1)
2513 self.PRW_street.SetToolTipString(_("primary/home address: name of street"))
2514
2515
2516 STT_address_number = wx.StaticText(PNL_form, -1, _('Number'))
2517 STT_address_number.SetForegroundColour('orange')
2518 self.TTC_address_number = wx.TextCtrl(PNL_form, -1)
2519 self.TTC_address_number.SetToolTipString(_("primary/home address: address number"))
2520
2521
2522 STT_town = wx.StaticText(PNL_form, -1, _('Place'))
2523 STT_town.SetForegroundColour('orange')
2524 self.PRW_town = cUrbPhraseWheel(parent = PNL_form, id = -1)
2525 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/..."))
2526
2527
2528 STT_state = wx.StaticText(PNL_form, -1, _('Region'))
2529 STT_state.SetForegroundColour('orange')
2530 self.PRW_state = cStateSelectionPhraseWheel(parent=PNL_form, id=-1)
2531 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/..."))
2532
2533
2534 STT_country = wx.StaticText(PNL_form, -1, _('Country'))
2535 STT_country.SetForegroundColour('orange')
2536 self.PRW_country = cCountryPhraseWheel(parent = PNL_form, id = -1)
2537 self.PRW_country.SetToolTipString(_("primary/home address: country"))
2538
2539
2540 STT_phone = wx.StaticText(PNL_form, -1, _('Phone'))
2541 self.TTC_phone = wx.TextCtrl(PNL_form, -1)
2542 self.TTC_phone.SetToolTipString(_("phone number at home"))
2543
2544
2545 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
2546 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
2547
2548
2549 STT_comment = wx.StaticText(PNL_form, -1, _('Comment'))
2550 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1)
2551 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.'))
2552
2553
2554 self.form_DTD = cFormDTD(fields = self.__class__.form_fields)
2555 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD))
2556
2557
2558 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4)
2559 SZR_input.AddGrowableCol(1)
2560 SZR_input.Add(STT_lastname, 0, wx.SHAPED)
2561 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND)
2562 SZR_input.Add(STT_firstname, 0, wx.SHAPED)
2563 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND)
2564 SZR_input.Add(STT_nick, 0, wx.SHAPED)
2565 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND)
2566 SZR_input.Add(STT_dob, 0, wx.SHAPED)
2567 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND)
2568 SZR_input.Add(STT_gender, 0, wx.SHAPED)
2569 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND)
2570 SZR_input.Add(STT_title, 0, wx.SHAPED)
2571 SZR_input.Add(self.PRW_title, 1, wx.EXPAND)
2572 SZR_input.Add(STT_zip_code, 0, wx.SHAPED)
2573 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND)
2574 SZR_input.Add(STT_street, 0, wx.SHAPED)
2575 SZR_input.Add(self.PRW_street, 1, wx.EXPAND)
2576 SZR_input.Add(STT_address_number, 0, wx.SHAPED)
2577 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND)
2578 SZR_input.Add(STT_town, 0, wx.SHAPED)
2579 SZR_input.Add(self.PRW_town, 1, wx.EXPAND)
2580 SZR_input.Add(STT_state, 0, wx.SHAPED)
2581 SZR_input.Add(self.PRW_state, 1, wx.EXPAND)
2582 SZR_input.Add(STT_country, 0, wx.SHAPED)
2583 SZR_input.Add(self.PRW_country, 1, wx.EXPAND)
2584 SZR_input.Add(STT_phone, 0, wx.SHAPED)
2585 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND)
2586 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
2587 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
2588 SZR_input.Add(STT_comment, 0, wx.SHAPED)
2589 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND)
2590
2591 PNL_form.SetSizerAndFit(SZR_input)
2592
2593
2594 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title)
2595 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2596
2597
2598
2603
2604 - def on_country_selected(self, data):
2605 """Set the states according to entered country."""
2606 self.PRW_state.set_context(context=u'country', val=data)
2607 return True
2608
2609 - def on_name_set(self):
2610 """Set the gender according to entered firstname.
2611
2612 Matches are fetched from existing records in backend.
2613 """
2614 firstname = self.PRW_firstname.GetValue().strip()
2615 rows, idx = gmPG2.run_ro_queries(queries = [{
2616 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
2617 'args': [firstname]
2618 }])
2619 if len(rows) == 0:
2620 return True
2621 wx.CallAfter(self.PRW_gender.SetData, rows[0][0])
2622 return True
2623
2624 - def on_zip_set(self):
2625 """Set the street, town, state and country according to entered zip code."""
2626 zip_code = self.PRW_zip_code.GetValue().strip()
2627 self.PRW_street.set_context(context=u'zip', val=zip_code)
2628 self.PRW_town.set_context(context=u'zip', val=zip_code)
2629 self.PRW_state.set_context(context=u'zip', val=zip_code)
2630 self.PRW_country.set_context(context=u'zip', val=zip_code)
2631 return True
2632
2634 """
2635 Wizard to create a new patient.
2636
2637 TODO:
2638 - write pages for different "themes" of patient creation
2639 - make it configurable which pages are loaded
2640 - make available sets of pages that apply to a country
2641 - make loading of some pages depend upon values in earlier pages, eg
2642 when the patient is female and older than 13 include a page about
2643 "female" data (number of kids etc)
2644
2645 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable()
2646 """
2647
2648 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2649 """
2650 Creates a new instance of NewPatientWizard
2651 @param parent - The parent widget
2652 @type parent - A wx.Window instance
2653 """
2654 id_wiz = wx.NewId()
2655 wx.wizard.Wizard.__init__(self, parent, id_wiz, title)
2656 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
2657 self.__subtitle = subtitle
2658 self.__do_layout()
2659
2661 """Create new patient.
2662
2663 activate, too, if told to do so (and patient successfully created)
2664 """
2665 while True:
2666
2667 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details):
2668 return False
2669
2670 try:
2671
2672 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD)
2673 except:
2674 _log.exception('cannot add new patient - missing identity fields')
2675 gmGuiHelpers.gm_show_error (
2676 _('Cannot create new patient.\n'
2677 'Missing parts of the identity.'
2678 ),
2679 _('Adding new patient')
2680 )
2681 continue
2682
2683 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2684
2685 try:
2686 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2687 except:
2688 _log.exception('cannot finalize new patient - missing address fields')
2689 gmGuiHelpers.gm_show_error (
2690 _('Cannot add address for the new patient.\n'
2691 'You must either enter all of the address fields or\n'
2692 'none at all. The relevant fields are marked in yellow.\n'
2693 '\n'
2694 'You will need to add the address details in the\n'
2695 'demographics module.'
2696 ),
2697 _('Adding new patient')
2698 )
2699 break
2700
2701 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2702
2703 break
2704
2705 if activate:
2706 from Gnumed.wxpython import gmPatSearchWidgets
2707 gmPatSearchWidgets.set_active_patient(patient = ident)
2708
2709 return ident
2710
2711
2712
2714 """Arrange widgets.
2715 """
2716
2717 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle )
2718 self.FitToPage(self.basic_pat_details)
2719
2721 """
2722 This validator is used to ensure that the user has entered all
2723 the required conditional values in the page (eg., to properly
2724 create an address, all the related fields must be filled).
2725 """
2726
2727 - def __init__(self, dtd):
2728 """
2729 Validator initialization.
2730 @param dtd The object containing the data model.
2731 @type dtd A cFormDTD instance
2732 """
2733
2734 wx.PyValidator.__init__(self)
2735
2736 self.form_DTD = dtd
2737
2739 """
2740 Standard cloner.
2741 Note that every validator must implement the Clone() method.
2742 """
2743 return cBasicPatDetailsPageValidator(dtd = self.form_DTD)
2744
2745 - def Validate(self, parent = None):
2746 """
2747 Validate the contents of the given text control.
2748 """
2749 _pnl_form = self.GetWindow().GetParent()
2750
2751 error = False
2752
2753
2754 if _pnl_form.PRW_lastname.GetValue().strip() == '':
2755 error = True
2756 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2757 _pnl_form.PRW_lastname.SetBackgroundColour('pink')
2758 _pnl_form.PRW_lastname.Refresh()
2759 else:
2760 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2761 _pnl_form.PRW_lastname.Refresh()
2762
2763 if _pnl_form.PRW_firstname.GetValue().strip() == '':
2764 error = True
2765 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2766 _pnl_form.PRW_firstname.SetBackgroundColour('pink')
2767 _pnl_form.PRW_firstname.Refresh()
2768 else:
2769 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2770 _pnl_form.PRW_firstname.Refresh()
2771
2772
2773 if _pnl_form.PRW_gender.GetData() is None:
2774 error = True
2775 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2776 _pnl_form.PRW_gender.SetBackgroundColour('pink')
2777 _pnl_form.PRW_gender.Refresh()
2778 else:
2779 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2780 _pnl_form.PRW_gender.Refresh()
2781
2782
2783 if (
2784 (_pnl_form.PRW_dob.GetValue().strip() == u'')
2785 or (not _pnl_form.PRW_dob.is_valid_timestamp())
2786 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900)
2787 ):
2788 error = True
2789 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue()
2790 gmDispatcher.send(signal = 'statustext', msg = msg)
2791 _pnl_form.PRW_dob.SetBackgroundColour('pink')
2792 _pnl_form.PRW_dob.Refresh()
2793 else:
2794 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2795 _pnl_form.PRW_dob.Refresh()
2796
2797
2798 is_any_field_filled = False
2799 address_fields = (
2800 _pnl_form.TTC_address_number,
2801 _pnl_form.PRW_zip_code,
2802 _pnl_form.PRW_street,
2803 _pnl_form.PRW_town
2804 )
2805 for field in address_fields:
2806 if field.GetValue().strip() == u'':
2807 if is_any_field_filled:
2808 error = True
2809 msg = _('To properly create an address, all the related fields must be filled in.')
2810 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2811 field.SetBackgroundColour('pink')
2812 field.SetFocus()
2813 field.Refresh()
2814 else:
2815 is_any_field_filled = True
2816 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2817 field.Refresh()
2818
2819 address_fields = (
2820 _pnl_form.PRW_state,
2821 _pnl_form.PRW_country
2822 )
2823 for field in address_fields:
2824 if field.GetData() is None:
2825 if is_any_field_filled:
2826 error = True
2827 msg = _('To properly create an address, all the related fields must be filled in.')
2828 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2829 field.SetBackgroundColour('pink')
2830 field.SetFocus()
2831 field.Refresh()
2832 else:
2833 is_any_field_filled = True
2834 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2835 field.Refresh()
2836
2837 return (not error)
2838
2839 - def TransferToWindow(self):
2840 """
2841 Transfer data from validator to window.
2842 The default implementation returns False, indicating that an error
2843 occurred. We simply return True, as we don't do any data transfer.
2844 """
2845 _pnl_form = self.GetWindow().GetParent()
2846
2847 _pnl_form.PRW_gender.SetData(self.form_DTD['gender'])
2848 _pnl_form.PRW_dob.SetText(self.form_DTD['dob'])
2849 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames'])
2850 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames'])
2851 _pnl_form.PRW_title.SetText(self.form_DTD['title'])
2852 _pnl_form.PRW_nick.SetText(self.form_DTD['nick'])
2853 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation'])
2854 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number'])
2855 _pnl_form.PRW_street.SetText(self.form_DTD['street'])
2856 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code'])
2857 _pnl_form.PRW_town.SetText(self.form_DTD['town'])
2858 _pnl_form.PRW_state.SetData(self.form_DTD['state'])
2859 _pnl_form.PRW_country.SetData(self.form_DTD['country'])
2860 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone'])
2861 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment'])
2862 return True
2863
2865 """
2866 Transfer data from window to validator.
2867 The default implementation returns False, indicating that an error
2868 occurred. We simply return True, as we don't do any data transfer.
2869 """
2870
2871 if not self.GetWindow().GetParent().Validate():
2872 return False
2873 try:
2874 _pnl_form = self.GetWindow().GetParent()
2875
2876 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData()
2877 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData()
2878
2879 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue()
2880 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue()
2881 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue()
2882 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue()
2883
2884 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue()
2885
2886 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue()
2887 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue()
2888 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue()
2889 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue()
2890 self.form_DTD['state'] = _pnl_form.PRW_state.GetData()
2891 self.form_DTD['country'] = _pnl_form.PRW_country.GetData()
2892
2893 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue()
2894
2895 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue()
2896 except:
2897 return False
2898 return True
2899
2944
2945
2946
2948 """Notebook displaying demographics editing pages:
2949
2950 - Identity
2951 - Contacts (addresses, phone numbers, etc)
2952
2953 Does NOT act on/listen to the current patient.
2954 """
2955
2957
2958 wx.Notebook.__init__ (
2959 self,
2960 parent = parent,
2961 id = id,
2962 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
2963 name = self.__class__.__name__
2964 )
2965
2966 self.__identity = None
2967 self.__do_layout()
2968 self.SetSelection(0)
2969
2970
2971
2973 """Populate fields in pages with data from model."""
2974 for page_idx in range(self.GetPageCount()):
2975 page = self.GetPage(page_idx)
2976 page.identity = self.__identity
2977
2978 return True
2979
2980
2981
2983 """Build patient edition notebook pages."""
2984
2985 new_page = cPersonContactsManagerPnl(self, -1)
2986 new_page.identity = self.__identity
2987 self.AddPage (
2988 page = new_page,
2989 text = _('Contacts'),
2990 select = True
2991 )
2992
2993
2994 new_page = cPersonIdentityManagerPnl(self, -1)
2995 new_page.identity = self.__identity
2996 self.AddPage (
2997 page = new_page,
2998 text = _('Identity'),
2999 select = False
3000 )
3001
3002
3003
3005 return self.__identity
3006
3009
3010 identity = property(_get_identity, _set_identity)
3011
3012
3013
3014
3016 """Page containing patient occupations edition fields.
3017 """
3018 - def __init__(self, parent, id, ident=None):
3019 """
3020 Creates a new instance of BasicPatDetailsPage
3021 @param parent - The parent widget
3022 @type parent - A wx.Window instance
3023 @param id - The widget id
3024 @type id - An integer
3025 """
3026 wx.Panel.__init__(self, parent, id)
3027 self.__ident = ident
3028 self.__do_layout()
3029
3031 PNL_form = wx.Panel(self, -1)
3032
3033 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
3034 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
3035 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
3036
3037 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
3038 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
3039
3040
3041 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
3042 SZR_input.AddGrowableCol(1)
3043 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
3044 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
3045 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
3046 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
3047 PNL_form.SetSizerAndFit(SZR_input)
3048
3049
3050 SZR_main = wx.BoxSizer(wx.VERTICAL)
3051 SZR_main.Add(PNL_form, 1, wx.EXPAND)
3052 self.SetSizer(SZR_main)
3053
3056
3057 - def refresh(self, identity=None):
3058 if identity is not None:
3059 self.__ident = identity
3060 jobs = self.__ident.get_occupations()
3061 if len(jobs) > 0:
3062 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
3063 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
3064 return True
3065
3067 if self.PRW_occupation.IsModified():
3068 new_job = self.PRW_occupation.GetValue().strip()
3069 jobs = self.__ident.get_occupations()
3070 for job in jobs:
3071 if job['l10n_occupation'] == new_job:
3072 continue
3073 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
3074 self.__ident.link_occupation(occupation = new_job)
3075 return True
3076
3078 """Patient demographics plugin for main notebook.
3079
3080 Hosts another notebook with pages for Identity, Contacts, etc.
3081
3082 Acts on/listens to the currently active patient.
3083 """
3084
3090
3091
3092
3093
3094
3095
3097 """Arrange widgets."""
3098 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
3099
3100 szr_main = wx.BoxSizer(wx.VERTICAL)
3101 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
3102 self.SetSizerAndFit(szr_main)
3103
3104
3105
3107 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
3108 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
3109
3111 self._schedule_data_reget()
3112
3114 self._schedule_data_reget()
3115
3116
3117
3127
3129 """
3130 Register a new patient, given the data supplied in the
3131 Data Transfer Dictionary object.
3132
3133 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3134 supplied data.
3135 @type basic_details_DTD A cFormDTD instance.
3136 """
3137 new_identity = gmPerson.create_identity (
3138 gender = dtd['gender'],
3139 dob = dtd['dob'].get_pydt(),
3140 lastnames = dtd['lastnames'],
3141 firstnames = dtd['firstnames']
3142 )
3143 if new_identity is None:
3144 _log.error('cannot create identity from %s' % str(dtd))
3145 return None
3146 _log.debug('identity created: %s' % new_identity)
3147
3148 if dtd['comment'] is not None:
3149 if dtd['comment'].strip() != u'':
3150 name = new_identity.get_active_name()
3151 name['comment'] = dtd['comment']
3152 name.save_payload()
3153
3154 return new_identity
3155
3157 """
3158 Update patient details with data supplied by
3159 Data Transfer Dictionary object.
3160
3161 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3162 supplied data.
3163 @type basic_details_DTD A cFormDTD instance.
3164 """
3165
3166 if identity['gender'] != dtd['gender']:
3167 identity['gender'] = dtd['gender']
3168 if identity['dob'] != dtd['dob'].get_pydt():
3169 identity['dob'] = dtd['dob'].get_pydt()
3170 if len(dtd['title']) > 0 and identity['title'] != dtd['title']:
3171 identity['title'] = dtd['title']
3172
3173
3174
3175
3176 identity.save_payload()
3177
3178
3179
3180 if identity['firstnames'] != dtd['firstnames'] or identity['lastnames'] != dtd['lastnames']:
3181 new_name = identity.add_name(firstnames = dtd['firstnames'], lastnames = dtd['lastnames'], active = True)
3182
3183 if len(dtd['nick']) > 0 and identity['preferred'] != dtd['nick']:
3184 identity.set_nickname(nickname = dtd['nick'])
3185
3186 return True
3187
3231
3233 """
3234 Update patient details with data supplied by
3235 Data Transfer Dictionary object.
3236
3237 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3238 supplied data.
3239 @type basic_details_DTD A cFormDTD instance.
3240 """
3241 identity.link_occupation(occupation = dtd['occupation'])
3242
3243 return True
3244
3246 """
3247 Utility class to test the new patient wizard.
3248 """
3249
3251 """
3252 Create a new instance of TestPanel.
3253 @param parent The parent widget
3254 @type parent A wx.Window instance
3255 """
3256 wx.Panel.__init__(self, parent, id)
3257 wizard = cNewPatientWizard(self)
3258 print wizard.RunWizard()
3259
3260 if __name__ == "__main__":
3261
3262
3264 app = wx.PyWidgetTester(size = (200, 50))
3265 pw = cZipcodePhraseWheel(app.frame, -1)
3266 app.frame.Show(True)
3267 app.MainLoop()
3268
3270 app = wx.PyWidgetTester(size = (200, 50))
3271 pw = cStateSelectionPhraseWheel(app.frame, -1)
3272
3273
3274 app.frame.Show(True)
3275 app.MainLoop()
3276
3278 app = wx.PyWidgetTester(size = (200, 50))
3279 pw = cUrbPhraseWheel(app.frame, -1)
3280 app.frame.Show(True)
3281 pw.set_context(context = u'zip', val = u'04317')
3282 app.MainLoop()
3283
3285 app = wx.PyWidgetTester(size = (200, 50))
3286 pw = cSuburbPhraseWheel(app.frame, -1)
3287 app.frame.Show(True)
3288 app.MainLoop()
3289
3291 app = wx.PyWidgetTester(size = (200, 50))
3292 pw = cAddressTypePhraseWheel(app.frame, -1)
3293 app.frame.Show(True)
3294 app.MainLoop()
3295
3297 app = wx.PyWidgetTester(size = (200, 50))
3298 pw = cAddressPhraseWheel(app.frame, -1)
3299 app.frame.Show(True)
3300 app.MainLoop()
3301
3303 app = wx.PyWidgetTester(size = (200, 50))
3304 pw = cStreetPhraseWheel(app.frame, -1)
3305
3306 app.frame.Show(True)
3307 app.MainLoop()
3308
3310 app = wx.PyWidgetTester(size = (600, 400))
3311 app.SetWidget(cKOrganizerSchedulePnl)
3312 app.MainLoop()
3313
3315 app = wx.PyWidgetTester(size = (600, 400))
3316 widget = cPersonNamesManagerPnl(app.frame, -1)
3317 widget.identity = activate_patient()
3318 app.frame.Show(True)
3319 app.MainLoop()
3320
3322 app = wx.PyWidgetTester(size = (600, 400))
3323 widget = cPersonIDsManagerPnl(app.frame, -1)
3324 widget.identity = activate_patient()
3325 app.frame.Show(True)
3326 app.MainLoop()
3327
3329 app = wx.PyWidgetTester(size = (600, 400))
3330 widget = cPersonIdentityManagerPnl(app.frame, -1)
3331 widget.identity = activate_patient()
3332 app.frame.Show(True)
3333 app.MainLoop()
3334
3339
3344
3346 app = wx.PyWidgetTester(size = (600, 400))
3347 widget = cPersonAddressesManagerPnl(app.frame, -1)
3348 widget.identity = activate_patient()
3349 app.frame.Show(True)
3350 app.MainLoop()
3351
3353 app = wx.PyWidgetTester(size = (600, 400))
3354 widget = cPersonCommsManagerPnl(app.frame, -1)
3355 widget.identity = activate_patient()
3356 app.frame.Show(True)
3357 app.MainLoop()
3358
3365
3367 app = wx.PyWidgetTester(size = (600, 400))
3368 widget = cPersonDemographicsEditorNb(app.frame, -1)
3369 widget.identity = activate_patient()
3370 widget.refresh()
3371 app.frame.Show(True)
3372 app.MainLoop()
3373
3382
3383 if len(sys.argv) > 1 and sys.argv[1] == 'test':
3384
3385 gmI18N.activate_locale()
3386 gmI18N.install_domain(domain='gnumed')
3387 gmPG2.get_connection()
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404 test_urb_prw()
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422