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 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
2129
2137
2138
2139
2141
2142 tt = _("Link another person in this database as the emergency contact.")
2143
2144 if self.__identity is None:
2145 self._TCTRL_er_contact.SetValue(u'')
2146 self._TCTRL_person.person = None
2147 self._TCTRL_person.SetToolTipString(tt)
2148 return
2149
2150 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u''))
2151 if self.__identity['pk_emergency_contact'] is not None:
2152 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact'])
2153 self._TCTRL_person.person = ident
2154 tt = u'%s\n\n%s\n\n%s' % (
2155 tt,
2156 ident['description_gender'],
2157 u'\n'.join([
2158 u'%s: %s%s' % (
2159 c['l10n_comm_type'],
2160 c['url'],
2161 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'')
2162 )
2163 for c in ident.get_comm_channels()
2164 ])
2165 )
2166 else:
2167 self._TCTRL_person.person = None
2168
2169 self._TCTRL_person.SetToolTipString(tt)
2170
2171
2172
2174 return self.__identity
2175
2179
2180 identity = property(_get_identity, _set_identity)
2181
2182
2183
2191
2200
2201
2202
2204
2205 dbcfg = gmCfg.cCfgSQL()
2206
2207 def_region = dbcfg.get2 (
2208 option = u'person.create.default_region',
2209 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2210 bias = u'user'
2211 )
2212
2213 if def_region is None:
2214 def_country = dbcfg.get2 (
2215 option = u'person.create.default_country',
2216 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2217 bias = u'user'
2218 )
2219 else:
2220 countries = gmDemographicRecord.get_country_for_region(region = def_region)
2221 if len(countries) == 1:
2222 def_country = countries[0]['l10n_country']
2223
2224 if parent is None:
2225 parent = wx.GetApp().GetTopWindow()
2226
2227 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region)
2228 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
2229 dlg.SetTitle(_('Adding new person'))
2230 ea._PRW_lastname.SetFocus()
2231 result = dlg.ShowModal()
2232 pat = ea.data
2233 dlg.Destroy()
2234
2235 if result != wx.ID_OK:
2236 return False
2237
2238 if activate:
2239 from Gnumed.wxpython import gmPatSearchWidgets
2240 gmPatSearchWidgets.set_active_patient(patient = pat)
2241
2242 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin')
2243
2244 return True
2245
2246 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl
2247
2248 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
2249
2251
2252 try:
2253 self.default_region = kwargs['region']
2254 del kwargs['region']
2255 except KeyError:
2256 self.default_region = None
2257
2258 try:
2259 self.default_country = kwargs['country']
2260 del kwargs['country']
2261 except KeyError:
2262 self.default_country = None
2263
2264 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs)
2265 gmEditArea.cGenericEditAreaMixin.__init__(self)
2266
2267 self.mode = 'new'
2268 self.data = None
2269 self._address = None
2270
2271 self.__init_ui()
2272 self.__register_interests()
2273
2274
2275
2277 self._PRW_lastname.final_regex = '.+'
2278 self._PRW_firstnames.final_regex = '.+'
2279 self._PRW_address_searcher.selection_only = False
2280 low = wx.DateTimeFromDMY(1,0,1900)
2281 hi = wx.DateTime()
2282 self._DP_dob.SetRange(low, hi.SetToCurrent())
2283
2284
2285
2286 if self.default_country is not None:
2287 self._PRW_country.SetText(value = self.default_country)
2288
2289 if self.default_region is not None:
2290 self._PRW_region.SetText(value = self.default_region)
2291
2293
2294 adr = self._PRW_address_searcher.get_address()
2295 if adr is None:
2296 return True
2297
2298 if ctrl.GetValue().strip() != adr[field]:
2299 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None)
2300 return True
2301
2302 return False
2303
2305 adr = self._PRW_address_searcher.get_address()
2306 if adr is None:
2307 return True
2308
2309 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode'])
2310
2311 self._PRW_street.SetText(value = adr['street'], data = adr['street'])
2312 self._PRW_street.set_context(context = u'zip', val = adr['postcode'])
2313
2314 self._TCTRL_number.SetValue(adr['number'])
2315
2316 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb'])
2317 self._PRW_urb.set_context(context = u'zip', val = adr['postcode'])
2318
2319 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state'])
2320 self._PRW_region.set_context(context = u'zip', val = adr['postcode'])
2321
2322 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country'])
2323 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
2324
2326 error = False
2327
2328
2329 if self._PRW_lastname.GetValue().strip() == u'':
2330 error = True
2331 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2332 self._PRW_lastname.display_as_valid(False)
2333 else:
2334 self._PRW_lastname.display_as_valid(True)
2335
2336 if self._PRW_firstnames.GetValue().strip() == '':
2337 error = True
2338 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2339 self._PRW_firstnames.display_as_valid(False)
2340 else:
2341 self._PRW_firstnames.display_as_valid(True)
2342
2343
2344 if self._PRW_gender.GetData() is None:
2345 error = True
2346 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2347 self._PRW_gender.display_as_valid(False)
2348 else:
2349 self._PRW_gender.display_as_valid(True)
2350
2351
2352 if not self._DP_dob.is_valid_timestamp():
2353
2354 gmDispatcher.send(signal = 'statustext', msg = _('Cannot use this date of birth. Does it lie before 1900 ?'))
2355
2356 do_it_anyway = gmGuiHelpers.gm_show_question (
2357 _(
2358 'Are you sure you want to register this person\n'
2359 'without a valid date of birth ?\n'
2360 '\n'
2361 'This can be useful for temporary staff members\n'
2362 'but will provoke nag screens if this person\n'
2363 'becomes a patient.\n'
2364 '\n'
2365 'Note that the date of birth cannot technically\n'
2366 'be before 1900, either :-(\n'
2367 ),
2368 _('Registering new person')
2369 )
2370
2371 if not do_it_anyway:
2372 error = True
2373
2374 if self._DP_dob.GetValue() is None:
2375 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2376 elif self._DP_dob.GetValue().GetYear() < 1900:
2377 error = True
2378 gmDispatcher.send(signal = 'statustext', msg = _('The year of birth must lie after 1900.'), beep = True)
2379 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2380 self._DP_dob.SetFocus()
2381 else:
2382 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2383 self._DP_dob.Refresh()
2384
2385
2386
2387
2388 return (not error)
2389
2391
2392
2393 if self._PRW_address_searcher.GetData() is not None:
2394 wx.CallAfter(self.__set_fields_from_address_searcher)
2395 return True
2396
2397
2398 fields_to_fill = (
2399 self._TCTRL_number,
2400 self._PRW_zip,
2401 self._PRW_street,
2402 self._PRW_urb,
2403 self._PRW_region,
2404 self._PRW_country
2405 )
2406 no_of_filled_fields = 0
2407
2408 for field in fields_to_fill:
2409 if field.GetValue().strip() != u'':
2410 no_of_filled_fields += 1
2411 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2412 field.Refresh()
2413
2414
2415 if no_of_filled_fields == 0:
2416 if empty_address_is_valid:
2417 return True
2418 else:
2419 return None
2420
2421
2422 if no_of_filled_fields != len(fields_to_fill):
2423 for field in fields_to_fill:
2424 if field.GetValue().strip() == u'':
2425 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2426 field.SetFocus()
2427 field.Refresh()
2428 msg = _('To properly create an address, all the related fields must be filled in.')
2429 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2430 return False
2431
2432
2433
2434
2435 strict_fields = (
2436 self._PRW_region,
2437 self._PRW_country
2438 )
2439 error = False
2440 for field in strict_fields:
2441 if field.GetData() is None:
2442 error = True
2443 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2444 field.SetFocus()
2445 else:
2446 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2447 field.Refresh()
2448
2449 if error:
2450 msg = _('This field must contain an item selected from the dropdown list.')
2451 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2452 return False
2453
2454 return True
2455
2472
2473
2474
2476 """Set the gender according to entered firstname.
2477
2478 Matches are fetched from existing records in backend.
2479 """
2480
2481
2482 if self._PRW_gender.GetData() is not None:
2483 return True
2484
2485 firstname = self._PRW_firstnames.GetValue().strip()
2486 if firstname == u'':
2487 return True
2488
2489 gender = gmPerson.map_firstnames2gender(firstnames = firstname)
2490 if gender is None:
2491 return True
2492
2493 wx.CallAfter(self._PRW_gender.SetData, gender)
2494 return True
2495
2497 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode')
2498
2499 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'')
2500 self._PRW_street.set_context(context = u'zip', val = zip_code)
2501 self._PRW_urb.set_context(context = u'zip', val = zip_code)
2502 self._PRW_region.set_context(context = u'zip', val = zip_code)
2503 self._PRW_country.set_context(context = u'zip', val = zip_code)
2504
2505 return True
2506
2508 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country')
2509
2510 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'')
2511 self._PRW_region.set_context(context = u'country', val = country)
2512
2513 return True
2514
2516 mapping = [
2517 (self._PRW_street, 'street'),
2518 (self._TCTRL_number, 'number'),
2519 (self._PRW_urb, 'urb'),
2520 (self._PRW_region, 'l10n_state')
2521 ]
2522
2523
2524 for ctrl, field in mapping:
2525 if self.__perhaps_invalidate_address_searcher(ctrl, field):
2526 return True
2527
2528 return True
2529
2531 adr = self._PRW_address_searcher.get_address()
2532 if adr is None:
2533 return True
2534
2535 wx.CallAfter(self.__set_fields_from_address_searcher)
2536 return True
2537
2538
2539
2541 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
2542
2544
2545
2546 new_identity = gmPerson.create_identity (
2547 gender = self._PRW_gender.GetData(),
2548 dob = self._DP_dob.get_pydt(),
2549 lastnames = self._PRW_lastname.GetValue().strip(),
2550 firstnames = self._PRW_firstnames.GetValue().strip()
2551 )
2552 _log.debug('identity created: %s' % new_identity)
2553
2554 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
2555 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
2556
2557 new_identity.save()
2558
2559 name = new_identity.get_active_name()
2560 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
2561 name.save()
2562
2563
2564 is_valid = self.__address_valid_for_save(empty_address_is_valid = False)
2565 if is_valid is True:
2566
2567
2568 try:
2569 new_identity.link_address (
2570 number = self._TCTRL_number.GetValue().strip(),
2571 street = self._PRW_street.GetValue().strip(),
2572 postcode = self._PRW_zip.GetValue().strip(),
2573 urb = self._PRW_urb.GetValue().strip(),
2574 state = self._PRW_region.GetData(),
2575 country = self._PRW_country.GetData()
2576 )
2577 except psycopg2.InternalError:
2578
2579 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip())
2580 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip())
2581 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip())
2582 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip())
2583 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip())
2584 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip())
2585 _log.exception('cannot link address')
2586 gmGuiHelpers.gm_show_error (
2587 aTitle = _('Saving address'),
2588 aMessage = _(
2589 'Cannot save this address.\n'
2590 '\n'
2591 'You will have to add it via the Demographics plugin.\n'
2592 )
2593 )
2594 elif is_valid is False:
2595 gmGuiHelpers.gm_show_error (
2596 aTitle = _('Saving address'),
2597 aMessage = _(
2598 'Address not saved.\n'
2599 '\n'
2600 'You will have to add it via the Demographics plugin.\n'
2601 )
2602 )
2603
2604
2605
2606 new_identity.link_comm_channel (
2607 comm_medium = u'homephone',
2608 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''),
2609 is_confidential = False
2610 )
2611
2612
2613 pk_type = self._PRW_external_id_type.GetData()
2614 id_value = self._TCTRL_external_id_value.GetValue().strip()
2615 if (pk_type is not None) and (id_value != u''):
2616 new_identity.add_external_id(value = id_value, pk_type = pk_type)
2617
2618
2619 new_identity.link_occupation (
2620 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'')
2621 )
2622
2623 self.data = new_identity
2624 return True
2625
2627 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2628
2632
2635
2637 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2638
2639
2640
2641 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
2642 """
2643 Wizard page for entering patient's basic demographic information
2644 """
2645
2646 form_fields = (
2647 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation',
2648 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment'
2649 )
2650
2651 - def __init__(self, parent, title):
2652 """
2653 Creates a new instance of BasicPatDetailsPage
2654 @param parent - The parent widget
2655 @type parent - A wx.Window instance
2656 @param tile - The title of the page
2657 @type title - A StringType instance
2658 """
2659 wx.wizard.WizardPageSimple.__init__(self, parent)
2660 self.__title = title
2661 self.__do_layout()
2662 self.__register_interests()
2663
2664 - def __do_layout(self):
2665 PNL_form = wx.Panel(self, -1)
2666
2667
2668 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name'))
2669 STT_lastname.SetForegroundColour('red')
2670 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1)
2671 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)'))
2672
2673
2674 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)'))
2675 STT_firstname.SetForegroundColour('red')
2676 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1)
2677 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name'))
2678
2679
2680 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name'))
2681 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1)
2682
2683
2684 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth'))
2685 STT_dob.SetForegroundColour('red')
2686 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1)
2687 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one"))
2688
2689
2690 STT_gender = wx.StaticText(PNL_form, -1, _('Gender'))
2691 STT_gender.SetForegroundColour('red')
2692 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1)
2693 self.PRW_gender.SetToolTipString(_("Required: gender of patient"))
2694
2695
2696 STT_title = wx.StaticText(PNL_form, -1, _('Title'))
2697 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1)
2698
2699
2700 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code'))
2701 STT_zip_code.SetForegroundColour('orange')
2702 self.PRW_zip_code = cZipcodePhraseWheel(parent = PNL_form, id = -1)
2703 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code"))
2704
2705
2706 STT_street = wx.StaticText(PNL_form, -1, _('Street'))
2707 STT_street.SetForegroundColour('orange')
2708 self.PRW_street = cStreetPhraseWheel(parent = PNL_form, id = -1)
2709 self.PRW_street.SetToolTipString(_("primary/home address: name of street"))
2710
2711
2712 STT_address_number = wx.StaticText(PNL_form, -1, _('Number'))
2713 STT_address_number.SetForegroundColour('orange')
2714 self.TTC_address_number = wx.TextCtrl(PNL_form, -1)
2715 self.TTC_address_number.SetToolTipString(_("primary/home address: address number"))
2716
2717
2718 STT_town = wx.StaticText(PNL_form, -1, _('Place'))
2719 STT_town.SetForegroundColour('orange')
2720 self.PRW_town = cUrbPhraseWheel(parent = PNL_form, id = -1)
2721 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/..."))
2722
2723
2724 STT_state = wx.StaticText(PNL_form, -1, _('Region'))
2725 STT_state.SetForegroundColour('orange')
2726 self.PRW_state = cStateSelectionPhraseWheel(parent=PNL_form, id=-1)
2727 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/..."))
2728
2729
2730 STT_country = wx.StaticText(PNL_form, -1, _('Country'))
2731 STT_country.SetForegroundColour('orange')
2732 self.PRW_country = cCountryPhraseWheel(parent = PNL_form, id = -1)
2733 self.PRW_country.SetToolTipString(_("primary/home address: country"))
2734
2735
2736 STT_phone = wx.StaticText(PNL_form, -1, _('Phone'))
2737 self.TTC_phone = wx.TextCtrl(PNL_form, -1)
2738 self.TTC_phone.SetToolTipString(_("phone number at home"))
2739
2740
2741 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
2742 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
2743
2744
2745 STT_comment = wx.StaticText(PNL_form, -1, _('Comment'))
2746 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1)
2747 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.'))
2748
2749
2750 self.form_DTD = cFormDTD(fields = self.__class__.form_fields)
2751 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD))
2752
2753
2754 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4)
2755 SZR_input.AddGrowableCol(1)
2756 SZR_input.Add(STT_lastname, 0, wx.SHAPED)
2757 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND)
2758 SZR_input.Add(STT_firstname, 0, wx.SHAPED)
2759 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND)
2760 SZR_input.Add(STT_nick, 0, wx.SHAPED)
2761 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND)
2762 SZR_input.Add(STT_dob, 0, wx.SHAPED)
2763 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND)
2764 SZR_input.Add(STT_gender, 0, wx.SHAPED)
2765 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND)
2766 SZR_input.Add(STT_title, 0, wx.SHAPED)
2767 SZR_input.Add(self.PRW_title, 1, wx.EXPAND)
2768 SZR_input.Add(STT_zip_code, 0, wx.SHAPED)
2769 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND)
2770 SZR_input.Add(STT_street, 0, wx.SHAPED)
2771 SZR_input.Add(self.PRW_street, 1, wx.EXPAND)
2772 SZR_input.Add(STT_address_number, 0, wx.SHAPED)
2773 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND)
2774 SZR_input.Add(STT_town, 0, wx.SHAPED)
2775 SZR_input.Add(self.PRW_town, 1, wx.EXPAND)
2776 SZR_input.Add(STT_state, 0, wx.SHAPED)
2777 SZR_input.Add(self.PRW_state, 1, wx.EXPAND)
2778 SZR_input.Add(STT_country, 0, wx.SHAPED)
2779 SZR_input.Add(self.PRW_country, 1, wx.EXPAND)
2780 SZR_input.Add(STT_phone, 0, wx.SHAPED)
2781 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND)
2782 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
2783 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
2784 SZR_input.Add(STT_comment, 0, wx.SHAPED)
2785 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND)
2786
2787 PNL_form.SetSizerAndFit(SZR_input)
2788
2789
2790 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title)
2791 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2792
2793
2794
2799
2800 - def on_country_selected(self, data):
2801 """Set the states according to entered country."""
2802 self.PRW_state.set_context(context=u'country', val=data)
2803 return True
2804
2805 - def on_name_set(self):
2806 """Set the gender according to entered firstname.
2807
2808 Matches are fetched from existing records in backend.
2809 """
2810 firstname = self.PRW_firstname.GetValue().strip()
2811 rows, idx = gmPG2.run_ro_queries(queries = [{
2812 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
2813 'args': [firstname]
2814 }])
2815 if len(rows) == 0:
2816 return True
2817 wx.CallAfter(self.PRW_gender.SetData, rows[0][0])
2818 return True
2819
2820 - def on_zip_set(self):
2821 """Set the street, town, state and country according to entered zip code."""
2822 zip_code = self.PRW_zip_code.GetValue().strip()
2823 self.PRW_street.set_context(context=u'zip', val=zip_code)
2824 self.PRW_town.set_context(context=u'zip', val=zip_code)
2825 self.PRW_state.set_context(context=u'zip', val=zip_code)
2826 self.PRW_country.set_context(context=u'zip', val=zip_code)
2827 return True
2828
2830 """
2831 Wizard to create a new patient.
2832
2833 TODO:
2834 - write pages for different "themes" of patient creation
2835 - make it configurable which pages are loaded
2836 - make available sets of pages that apply to a country
2837 - make loading of some pages depend upon values in earlier pages, eg
2838 when the patient is female and older than 13 include a page about
2839 "female" data (number of kids etc)
2840
2841 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable()
2842 """
2843
2844 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2845 """
2846 Creates a new instance of NewPatientWizard
2847 @param parent - The parent widget
2848 @type parent - A wx.Window instance
2849 """
2850 id_wiz = wx.NewId()
2851 wx.wizard.Wizard.__init__(self, parent, id_wiz, title)
2852 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
2853 self.__subtitle = subtitle
2854 self.__do_layout()
2855
2857 """Create new patient.
2858
2859 activate, too, if told to do so (and patient successfully created)
2860 """
2861 while True:
2862
2863 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details):
2864 return False
2865
2866 try:
2867
2868 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD)
2869 except:
2870 _log.exception('cannot add new patient - missing identity fields')
2871 gmGuiHelpers.gm_show_error (
2872 _('Cannot create new patient.\n'
2873 'Missing parts of the identity.'
2874 ),
2875 _('Adding new patient')
2876 )
2877 continue
2878
2879 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2880
2881 try:
2882 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2883 except:
2884 _log.exception('cannot finalize new patient - missing address fields')
2885 gmGuiHelpers.gm_show_error (
2886 _('Cannot add address for the new patient.\n'
2887 'You must either enter all of the address fields or\n'
2888 'none at all. The relevant fields are marked in yellow.\n'
2889 '\n'
2890 'You will need to add the address details in the\n'
2891 'demographics module.'
2892 ),
2893 _('Adding new patient')
2894 )
2895 break
2896
2897 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2898
2899 break
2900
2901 if activate:
2902 from Gnumed.wxpython import gmPatSearchWidgets
2903 gmPatSearchWidgets.set_active_patient(patient = ident)
2904
2905 return ident
2906
2907
2908
2910 """Arrange widgets.
2911 """
2912
2913 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle )
2914 self.FitToPage(self.basic_pat_details)
2915
2916
2918 """
2919 This validator is used to ensure that the user has entered all
2920 the required conditional values in the page (eg., to properly
2921 create an address, all the related fields must be filled).
2922 """
2923
2924 - def __init__(self, dtd):
2925 """
2926 Validator initialization.
2927 @param dtd The object containing the data model.
2928 @type dtd A cFormDTD instance
2929 """
2930
2931 wx.PyValidator.__init__(self)
2932
2933 self.form_DTD = dtd
2934
2936 """
2937 Standard cloner.
2938 Note that every validator must implement the Clone() method.
2939 """
2940 return cBasicPatDetailsPageValidator(dtd = self.form_DTD)
2941
2942 - def Validate(self, parent = None):
2943 """
2944 Validate the contents of the given text control.
2945 """
2946 _pnl_form = self.GetWindow().GetParent()
2947
2948 error = False
2949
2950
2951 if _pnl_form.PRW_lastname.GetValue().strip() == '':
2952 error = True
2953 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2954 _pnl_form.PRW_lastname.SetBackgroundColour('pink')
2955 _pnl_form.PRW_lastname.Refresh()
2956 else:
2957 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2958 _pnl_form.PRW_lastname.Refresh()
2959
2960 if _pnl_form.PRW_firstname.GetValue().strip() == '':
2961 error = True
2962 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2963 _pnl_form.PRW_firstname.SetBackgroundColour('pink')
2964 _pnl_form.PRW_firstname.Refresh()
2965 else:
2966 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2967 _pnl_form.PRW_firstname.Refresh()
2968
2969
2970 if _pnl_form.PRW_gender.GetData() is None:
2971 error = True
2972 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2973 _pnl_form.PRW_gender.SetBackgroundColour('pink')
2974 _pnl_form.PRW_gender.Refresh()
2975 else:
2976 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2977 _pnl_form.PRW_gender.Refresh()
2978
2979
2980 if (
2981 (_pnl_form.PRW_dob.GetValue().strip() == u'')
2982 or (not _pnl_form.PRW_dob.is_valid_timestamp())
2983 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900)
2984 ):
2985 error = True
2986 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue()
2987 gmDispatcher.send(signal = 'statustext', msg = msg)
2988 _pnl_form.PRW_dob.SetBackgroundColour('pink')
2989 _pnl_form.PRW_dob.Refresh()
2990 else:
2991 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2992 _pnl_form.PRW_dob.Refresh()
2993
2994
2995 is_any_field_filled = False
2996 address_fields = (
2997 _pnl_form.TTC_address_number,
2998 _pnl_form.PRW_zip_code,
2999 _pnl_form.PRW_street,
3000 _pnl_form.PRW_town
3001 )
3002 for field in address_fields:
3003 if field.GetValue().strip() == u'':
3004 if is_any_field_filled:
3005 error = True
3006 msg = _('To properly create an address, all the related fields must be filled in.')
3007 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
3008 field.SetBackgroundColour('pink')
3009 field.SetFocus()
3010 field.Refresh()
3011 else:
3012 is_any_field_filled = True
3013 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
3014 field.Refresh()
3015
3016 address_fields = (
3017 _pnl_form.PRW_state,
3018 _pnl_form.PRW_country
3019 )
3020 for field in address_fields:
3021 if field.GetData() is None:
3022 if is_any_field_filled:
3023 error = True
3024 msg = _('To properly create an address, all the related fields must be filled in.')
3025 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
3026 field.SetBackgroundColour('pink')
3027 field.SetFocus()
3028 field.Refresh()
3029 else:
3030 is_any_field_filled = True
3031 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
3032 field.Refresh()
3033
3034 return (not error)
3035
3036 - def TransferToWindow(self):
3037 """
3038 Transfer data from validator to window.
3039 The default implementation returns False, indicating that an error
3040 occurred. We simply return True, as we don't do any data transfer.
3041 """
3042 _pnl_form = self.GetWindow().GetParent()
3043
3044 _pnl_form.PRW_gender.SetData(self.form_DTD['gender'])
3045 _pnl_form.PRW_dob.SetText(self.form_DTD['dob'])
3046 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames'])
3047 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames'])
3048 _pnl_form.PRW_title.SetText(self.form_DTD['title'])
3049 _pnl_form.PRW_nick.SetText(self.form_DTD['nick'])
3050 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation'])
3051 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number'])
3052 _pnl_form.PRW_street.SetText(self.form_DTD['street'])
3053 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code'])
3054 _pnl_form.PRW_town.SetText(self.form_DTD['town'])
3055 _pnl_form.PRW_state.SetData(self.form_DTD['state'])
3056 _pnl_form.PRW_country.SetData(self.form_DTD['country'])
3057 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone'])
3058 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment'])
3059 return True
3060
3062 """
3063 Transfer data from window to validator.
3064 The default implementation returns False, indicating that an error
3065 occurred. We simply return True, as we don't do any data transfer.
3066 """
3067
3068 if not self.GetWindow().GetParent().Validate():
3069 return False
3070 try:
3071 _pnl_form = self.GetWindow().GetParent()
3072
3073 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData()
3074 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData()
3075
3076 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue()
3077 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue()
3078 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue()
3079 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue()
3080
3081 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue()
3082
3083 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue()
3084 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue()
3085 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue()
3086 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue()
3087 self.form_DTD['state'] = _pnl_form.PRW_state.GetData()
3088 self.form_DTD['country'] = _pnl_form.PRW_country.GetData()
3089
3090 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue()
3091
3092 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue()
3093 except:
3094 return False
3095 return True
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3145 """Notebook displaying demographics editing pages:
3146
3147 - Identity
3148 - Contacts (addresses, phone numbers, etc)
3149 - Social Network (significant others, GP, etc)
3150
3151 Does NOT act on/listen to the current patient.
3152 """
3153
3155
3156 wx.Notebook.__init__ (
3157 self,
3158 parent = parent,
3159 id = id,
3160 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
3161 name = self.__class__.__name__
3162 )
3163
3164 self.__identity = None
3165 self.__do_layout()
3166 self.SetSelection(0)
3167
3168
3169
3171 """Populate fields in pages with data from model."""
3172 for page_idx in range(self.GetPageCount()):
3173 page = self.GetPage(page_idx)
3174 page.identity = self.__identity
3175
3176 return True
3177
3178
3179
3181 """Build patient edition notebook pages."""
3182
3183
3184 new_page = cPersonContactsManagerPnl(self, -1)
3185 new_page.identity = self.__identity
3186 self.AddPage (
3187 page = new_page,
3188 text = _('Contacts'),
3189 select = True
3190 )
3191
3192
3193 new_page = cPersonIdentityManagerPnl(self, -1)
3194 new_page.identity = self.__identity
3195 self.AddPage (
3196 page = new_page,
3197 text = _('Identity'),
3198 select = False
3199 )
3200
3201
3202 new_page = cPersonSocialNetworkManagerPnl(self, -1)
3203 new_page.identity = self.__identity
3204 self.AddPage (
3205 page = new_page,
3206 text = _('Social Network'),
3207 select = False
3208 )
3209
3210
3211
3213 return self.__identity
3214
3217
3218 identity = property(_get_identity, _set_identity)
3219
3220
3221
3222
3223
3225 """Page containing patient occupations edition fields.
3226 """
3227 - def __init__(self, parent, id, ident=None):
3228 """
3229 Creates a new instance of BasicPatDetailsPage
3230 @param parent - The parent widget
3231 @type parent - A wx.Window instance
3232 @param id - The widget id
3233 @type id - An integer
3234 """
3235 wx.Panel.__init__(self, parent, id)
3236 self.__ident = ident
3237 self.__do_layout()
3238
3240 PNL_form = wx.Panel(self, -1)
3241
3242 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
3243 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
3244 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
3245
3246 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
3247 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
3248
3249
3250 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
3251 SZR_input.AddGrowableCol(1)
3252 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
3253 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
3254 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
3255 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
3256 PNL_form.SetSizerAndFit(SZR_input)
3257
3258
3259 SZR_main = wx.BoxSizer(wx.VERTICAL)
3260 SZR_main.Add(PNL_form, 1, wx.EXPAND)
3261 self.SetSizer(SZR_main)
3262
3265
3266 - def refresh(self, identity=None):
3267 if identity is not None:
3268 self.__ident = identity
3269 jobs = self.__ident.get_occupations()
3270 if len(jobs) > 0:
3271 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
3272 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
3273 return True
3274
3276 if self.PRW_occupation.IsModified():
3277 new_job = self.PRW_occupation.GetValue().strip()
3278 jobs = self.__ident.get_occupations()
3279 for job in jobs:
3280 if job['l10n_occupation'] == new_job:
3281 continue
3282 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
3283 self.__ident.link_occupation(occupation = new_job)
3284 return True
3285
3287 """Patient demographics plugin for main notebook.
3288
3289 Hosts another notebook with pages for Identity, Contacts, etc.
3290
3291 Acts on/listens to the currently active patient.
3292 """
3293
3299
3300
3301
3302
3303
3304
3306 """Arrange widgets."""
3307 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
3308
3309 szr_main = wx.BoxSizer(wx.VERTICAL)
3310 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
3311 self.SetSizerAndFit(szr_main)
3312
3313
3314
3316 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
3317 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
3318
3320 self._schedule_data_reget()
3321
3323 self._schedule_data_reget()
3324
3325
3326
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3455 """
3456 Utility class to test the new patient wizard.
3457 """
3458
3460 """
3461 Create a new instance of TestPanel.
3462 @param parent The parent widget
3463 @type parent A wx.Window instance
3464 """
3465 wx.Panel.__init__(self, parent, id)
3466 wizard = cNewPatientWizard(self)
3467 print wizard.RunWizard()
3468
3469 if __name__ == "__main__":
3470
3471
3473 app = wx.PyWidgetTester(size = (200, 50))
3474 pw = cZipcodePhraseWheel(app.frame, -1)
3475 app.frame.Show(True)
3476 app.MainLoop()
3477
3479 app = wx.PyWidgetTester(size = (200, 50))
3480 pw = cStateSelectionPhraseWheel(app.frame, -1)
3481
3482
3483 app.frame.Show(True)
3484 app.MainLoop()
3485
3487 app = wx.PyWidgetTester(size = (200, 50))
3488 pw = cUrbPhraseWheel(app.frame, -1)
3489 app.frame.Show(True)
3490 pw.set_context(context = u'zip', val = u'04317')
3491 app.MainLoop()
3492
3494 app = wx.PyWidgetTester(size = (200, 50))
3495 pw = cSuburbPhraseWheel(app.frame, -1)
3496 app.frame.Show(True)
3497 app.MainLoop()
3498
3500 app = wx.PyWidgetTester(size = (200, 50))
3501 pw = cAddressTypePhraseWheel(app.frame, -1)
3502 app.frame.Show(True)
3503 app.MainLoop()
3504
3506 app = wx.PyWidgetTester(size = (200, 50))
3507 pw = cAddressPhraseWheel(app.frame, -1)
3508 app.frame.Show(True)
3509 app.MainLoop()
3510
3512 app = wx.PyWidgetTester(size = (200, 50))
3513 pw = cStreetPhraseWheel(app.frame, -1)
3514
3515 app.frame.Show(True)
3516 app.MainLoop()
3517
3519 app = wx.PyWidgetTester(size = (600, 400))
3520 app.SetWidget(cKOrganizerSchedulePnl)
3521 app.MainLoop()
3522
3524 app = wx.PyWidgetTester(size = (600, 400))
3525 widget = cPersonNamesManagerPnl(app.frame, -1)
3526 widget.identity = activate_patient()
3527 app.frame.Show(True)
3528 app.MainLoop()
3529
3531 app = wx.PyWidgetTester(size = (600, 400))
3532 widget = cPersonIDsManagerPnl(app.frame, -1)
3533 widget.identity = activate_patient()
3534 app.frame.Show(True)
3535 app.MainLoop()
3536
3538 app = wx.PyWidgetTester(size = (600, 400))
3539 widget = cPersonIdentityManagerPnl(app.frame, -1)
3540 widget.identity = activate_patient()
3541 app.frame.Show(True)
3542 app.MainLoop()
3543
3548
3553
3555 app = wx.PyWidgetTester(size = (600, 400))
3556 widget = cPersonAddressesManagerPnl(app.frame, -1)
3557 widget.identity = activate_patient()
3558 app.frame.Show(True)
3559 app.MainLoop()
3560
3562 app = wx.PyWidgetTester(size = (600, 400))
3563 widget = cPersonCommsManagerPnl(app.frame, -1)
3564 widget.identity = activate_patient()
3565 app.frame.Show(True)
3566 app.MainLoop()
3567
3574
3576 app = wx.PyWidgetTester(size = (600, 400))
3577 widget = cPersonDemographicsEditorNb(app.frame, -1)
3578 widget.identity = activate_patient()
3579 widget.refresh()
3580 app.frame.Show(True)
3581 app.MainLoop()
3582
3591
3592 if len(sys.argv) > 1 and sys.argv[1] == 'test':
3593
3594 gmI18N.activate_locale()
3595 gmI18N.install_domain(domain='gnumed')
3596 gmPG2.get_connection()
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613 test_urb_prw()
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631