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
1695
1698
1714
1717
1718
1719
1720 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl
1721
1723 """An edit area for editing/creating name/gender/dob.
1724
1725 Does NOT act on/listen to the current patient.
1726 """
1737
1738
1739
1756
1757
1758
1759
1761
1762 if not self.__valid_for_save():
1763 return False
1764
1765 self.__identity['gender'] = self._PRW_gender.GetData()
1766 if self._PRW_dob.GetValue().strip() == u'':
1767 self.__identity['dob'] = None
1768 else:
1769 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt()
1770 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
1771 self.__identity['deceased'] = self._DP_dod.GetValue(as_pydt = True)
1772 self.__identity.save_payload()
1773
1774 active = self._CHBOX_active.GetValue()
1775 first = self._PRW_firstname.GetValue().strip()
1776 last = self._PRW_lastname.GetValue().strip()
1777 old_nick = self.__name['preferred']
1778
1779
1780 old_name = self.__name['firstnames'] + self.__name['lastnames']
1781 if (first + last) != old_name:
1782 self.__name = self.__identity.add_name(first, last, active)
1783
1784 self.__name['active_name'] = active
1785 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
1786 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1787
1788 self.__name.save_payload()
1789
1790 return True
1791
1792
1793
1796
1798 """Set the gender according to entered firstname.
1799
1800 Matches are fetched from existing records in backend.
1801 """
1802 firstname = self._PRW_firstname.GetValue().strip()
1803 if firstname == u'':
1804 return True
1805 rows, idx = gmPG2.run_ro_queries(queries = [{
1806 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
1807 'args': [firstname]
1808 }])
1809 if len(rows) == 0:
1810 return True
1811 wx.CallAfter(self._PRW_gender.SetData, rows[0][0])
1812 return True
1813
1814
1815
1817
1818 has_error = False
1819
1820 if self._PRW_gender.GetData() is None:
1821 self._PRW_gender.SetBackgroundColour('pink')
1822 self._PRW_gender.Refresh()
1823 self._PRW_gender.SetFocus()
1824 has_error = True
1825 else:
1826 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1827 self._PRW_gender.Refresh()
1828
1829 if not self._PRW_dob.is_valid_timestamp():
1830 val = self._PRW_dob.GetValue().strip()
1831 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
1832 self._PRW_dob.SetBackgroundColour('pink')
1833 self._PRW_dob.Refresh()
1834 self._PRW_dob.SetFocus()
1835 has_error = True
1836 else:
1837 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1838 self._PRW_dob.Refresh()
1839
1840 if not self._DP_dod.is_valid_timestamp():
1841 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
1842 self._DP_dod.SetBackgroundColour('pink')
1843 self._DP_dod.Refresh()
1844 self._DP_dod.SetFocus()
1845 has_error = True
1846 else:
1847 self._DP_dod.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1848 self._DP_dod.Refresh()
1849
1850 if self._PRW_lastname.GetValue().strip() == u'':
1851 self._PRW_lastname.SetBackgroundColour('pink')
1852 self._PRW_lastname.Refresh()
1853 self._PRW_lastname.SetFocus()
1854 has_error = True
1855 else:
1856 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1857 self._PRW_lastname.Refresh()
1858
1859 if self._PRW_firstname.GetValue().strip() == u'':
1860 self._PRW_firstname.SetBackgroundColour('pink')
1861 self._PRW_firstname.Refresh()
1862 self._PRW_firstname.SetFocus()
1863 has_error = True
1864 else:
1865 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1866 self._PRW_firstname.Refresh()
1867
1868 return (has_error is False)
1869
1870
1871
1873 """A list for managing a person's names.
1874
1875 Does NOT act on/listen to the current patient.
1876 """
1894
1895
1896
1897 - def refresh(self, *args, **kwargs):
1914
1915
1916
1918 self._LCTRL_items.set_columns(columns = [
1919 _('Active'),
1920 _('Lastname'),
1921 _('Firstname(s)'),
1922 _('Preferred Name'),
1923 _('Comment')
1924 ])
1925
1935
1945
1947
1948 if len(self.__identity.get_names()) == 1:
1949 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1950 return False
1951
1952 go_ahead = gmGuiHelpers.gm_show_question (
1953 _( 'It is often advisable to keep old names around and\n'
1954 'just create a new "currently active" name.\n'
1955 '\n'
1956 'This allows finding the patient by both the old\n'
1957 'and the new name (think before/after marriage).\n'
1958 '\n'
1959 'Do you still want to really delete\n'
1960 "this name from the patient ?"
1961 ),
1962 _('Deleting name')
1963 )
1964 if not go_ahead:
1965 return False
1966
1967 self.__identity.delete_name(name = name)
1968 return True
1969
1970
1971
1973 return self.__identity
1974
1978
1979 identity = property(_get_identity, _set_identity)
1980
1982 """A list for managing a person's external IDs.
1983
1984 Does NOT act on/listen to the current patient.
1985 """
2003
2004
2005
2006 - def refresh(self, *args, **kwargs):
2024
2025
2026
2028 self._LCTRL_items.set_columns(columns = [
2029 _('ID type'),
2030 _('Value'),
2031 _('Issuer'),
2032 _('Context'),
2033 _('Comment')
2034 ])
2035
2046
2057
2059 go_ahead = gmGuiHelpers.gm_show_question (
2060 _( 'Do you really want to delete this\n'
2061 'external ID from the patient ?'),
2062 _('Deleting external ID')
2063 )
2064 if not go_ahead:
2065 return False
2066 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
2067 return True
2068
2069
2070
2072 return self.__identity
2073
2077
2078 identity = property(_get_identity, _set_identity)
2079
2080
2081
2083 """A panel for editing identity data for a person.
2084
2085 - provides access to:
2086 - name
2087 - external IDs
2088
2089 Does NOT act on/listen to the current patient.
2090 """
2097
2098
2099
2101 self._PNL_names.identity = self.__identity
2102 self._PNL_ids.identity = self.__identity
2103
2104 self._PNL_identity.mode = 'new'
2105 self._PNL_identity.data = self.__identity
2106 if self.__identity is not None:
2107 self._PNL_identity.mode = 'edit'
2108
2109
2110
2112 return self.__identity
2113
2117
2118 identity = property(_get_identity, _set_identity)
2119
2120
2121
2125
2128
2129
2130 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
2131
2139
2140
2141
2143
2144 tt = _("Link another person in this database as the emergency contact.")
2145
2146 if self.__identity is None:
2147 self._TCTRL_er_contact.SetValue(u'')
2148 self._TCTRL_person.person = None
2149 self._TCTRL_person.SetToolTipString(tt)
2150 return
2151
2152 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u''))
2153 if self.__identity['pk_emergency_contact'] is not None:
2154 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact'])
2155 self._TCTRL_person.person = ident
2156 tt = u'%s\n\n%s\n\n%s' % (
2157 tt,
2158 ident['description_gender'],
2159 u'\n'.join([
2160 u'%s: %s%s' % (
2161 c['l10n_comm_type'],
2162 c['url'],
2163 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'')
2164 )
2165 for c in ident.get_comm_channels()
2166 ])
2167 )
2168 else:
2169 self._TCTRL_person.person = None
2170
2171 self._TCTRL_person.SetToolTipString(tt)
2172
2173
2174
2176 return self.__identity
2177
2181
2182 identity = property(_get_identity, _set_identity)
2183
2184
2185
2194
2205
2213
2214
2215
2217
2218 dbcfg = gmCfg.cCfgSQL()
2219
2220 def_region = dbcfg.get2 (
2221 option = u'person.create.default_region',
2222 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2223 bias = u'user'
2224 )
2225
2226 if def_region is None:
2227 def_country = dbcfg.get2 (
2228 option = u'person.create.default_country',
2229 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2230 bias = u'user'
2231 )
2232 else:
2233 countries = gmDemographicRecord.get_country_for_region(region = def_region)
2234 if len(countries) == 1:
2235 def_country = countries[0]['l10n_country']
2236
2237 if parent is None:
2238 parent = wx.GetApp().GetTopWindow()
2239
2240 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region)
2241 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
2242 dlg.SetTitle(_('Adding new person'))
2243 ea._PRW_lastname.SetFocus()
2244 result = dlg.ShowModal()
2245 pat = ea.data
2246 dlg.Destroy()
2247
2248 if result != wx.ID_OK:
2249 return False
2250
2251 if activate:
2252 from Gnumed.wxpython import gmPatSearchWidgets
2253 gmPatSearchWidgets.set_active_patient(patient = pat)
2254
2255 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin')
2256
2257 return True
2258
2259 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl
2260
2261 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
2262
2264
2265 try:
2266 self.default_region = kwargs['region']
2267 del kwargs['region']
2268 except KeyError:
2269 self.default_region = None
2270
2271 try:
2272 self.default_country = kwargs['country']
2273 del kwargs['country']
2274 except KeyError:
2275 self.default_country = None
2276
2277 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs)
2278 gmEditArea.cGenericEditAreaMixin.__init__(self)
2279
2280 self.mode = 'new'
2281 self.data = None
2282 self._address = None
2283
2284 self.__init_ui()
2285 self.__register_interests()
2286
2287
2288
2290 self._PRW_lastname.final_regex = '.+'
2291 self._PRW_firstnames.final_regex = '.+'
2292 self._PRW_address_searcher.selection_only = False
2293 low = wx.DateTimeFromDMY(1,0,1900)
2294 hi = wx.DateTime()
2295 self._DP_dob.SetRange(low, hi.SetToCurrent())
2296
2297
2298
2299 if self.default_country is not None:
2300 self._PRW_country.SetText(value = self.default_country)
2301
2302 if self.default_region is not None:
2303 self._PRW_region.SetText(value = self.default_region)
2304
2306
2307 adr = self._PRW_address_searcher.get_address()
2308 if adr is None:
2309 return True
2310
2311 if ctrl.GetValue().strip() != adr[field]:
2312 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None)
2313 return True
2314
2315 return False
2316
2318 adr = self._PRW_address_searcher.get_address()
2319 if adr is None:
2320 return True
2321
2322 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode'])
2323
2324 self._PRW_street.SetText(value = adr['street'], data = adr['street'])
2325 self._PRW_street.set_context(context = u'zip', val = adr['postcode'])
2326
2327 self._TCTRL_number.SetValue(adr['number'])
2328
2329 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb'])
2330 self._PRW_urb.set_context(context = u'zip', val = adr['postcode'])
2331
2332 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state'])
2333 self._PRW_region.set_context(context = u'zip', val = adr['postcode'])
2334
2335 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country'])
2336 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
2337
2339 error = False
2340
2341
2342 if self._PRW_lastname.GetValue().strip() == u'':
2343 error = True
2344 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2345 self._PRW_lastname.display_as_valid(False)
2346 else:
2347 self._PRW_lastname.display_as_valid(True)
2348
2349 if self._PRW_firstnames.GetValue().strip() == '':
2350 error = True
2351 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2352 self._PRW_firstnames.display_as_valid(False)
2353 else:
2354 self._PRW_firstnames.display_as_valid(True)
2355
2356
2357 if self._PRW_gender.GetData() is None:
2358 error = True
2359 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2360 self._PRW_gender.display_as_valid(False)
2361 else:
2362 self._PRW_gender.display_as_valid(True)
2363
2364
2365 if not self._DP_dob.is_valid_timestamp():
2366
2367 gmDispatcher.send(signal = 'statustext', msg = _('Cannot use this date of birth. Does it lie before 1900 ?'))
2368
2369 do_it_anyway = gmGuiHelpers.gm_show_question (
2370 _(
2371 'Are you sure you want to register this person\n'
2372 'without a valid date of birth ?\n'
2373 '\n'
2374 'This can be useful for temporary staff members\n'
2375 'but will provoke nag screens if this person\n'
2376 'becomes a patient.\n'
2377 '\n'
2378 'Note that the date of birth cannot technically\n'
2379 'be before 1900, either :-(\n'
2380 ),
2381 _('Registering new person')
2382 )
2383
2384 if not do_it_anyway:
2385 error = True
2386
2387 if self._DP_dob.GetValue() is None:
2388 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2389 elif self._DP_dob.GetValue().GetYear() < 1900:
2390 error = True
2391 gmDispatcher.send(signal = 'statustext', msg = _('The year of birth must lie after 1900.'), beep = True)
2392 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2393 self._DP_dob.SetFocus()
2394 else:
2395 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2396 self._DP_dob.Refresh()
2397
2398
2399
2400
2401 return (not error)
2402
2404
2405
2406 if self._PRW_address_searcher.GetData() is not None:
2407 wx.CallAfter(self.__set_fields_from_address_searcher)
2408 return True
2409
2410
2411 fields_to_fill = (
2412 self._TCTRL_number,
2413 self._PRW_zip,
2414 self._PRW_street,
2415 self._PRW_urb,
2416 self._PRW_region,
2417 self._PRW_country
2418 )
2419 no_of_filled_fields = 0
2420
2421 for field in fields_to_fill:
2422 if field.GetValue().strip() != u'':
2423 no_of_filled_fields += 1
2424 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2425 field.Refresh()
2426
2427
2428 if no_of_filled_fields == 0:
2429 if empty_address_is_valid:
2430 return True
2431 else:
2432 return None
2433
2434
2435 if no_of_filled_fields != len(fields_to_fill):
2436 for field in fields_to_fill:
2437 if field.GetValue().strip() == u'':
2438 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2439 field.SetFocus()
2440 field.Refresh()
2441 msg = _('To properly create an address, all the related fields must be filled in.')
2442 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2443 return False
2444
2445
2446
2447
2448 strict_fields = (
2449 self._PRW_region,
2450 self._PRW_country
2451 )
2452 error = False
2453 for field in strict_fields:
2454 if field.GetData() is None:
2455 error = True
2456 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2457 field.SetFocus()
2458 else:
2459 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2460 field.Refresh()
2461
2462 if error:
2463 msg = _('This field must contain an item selected from the dropdown list.')
2464 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2465 return False
2466
2467 return True
2468
2485
2486
2487
2489 """Set the gender according to entered firstname.
2490
2491 Matches are fetched from existing records in backend.
2492 """
2493
2494
2495 if self._PRW_gender.GetData() is not None:
2496 return True
2497
2498 firstname = self._PRW_firstnames.GetValue().strip()
2499 if firstname == u'':
2500 return True
2501
2502 gender = gmPerson.map_firstnames2gender(firstnames = firstname)
2503 if gender is None:
2504 return True
2505
2506 wx.CallAfter(self._PRW_gender.SetData, gender)
2507 return True
2508
2510 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode')
2511
2512 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'')
2513 self._PRW_street.set_context(context = u'zip', val = zip_code)
2514 self._PRW_urb.set_context(context = u'zip', val = zip_code)
2515 self._PRW_region.set_context(context = u'zip', val = zip_code)
2516 self._PRW_country.set_context(context = u'zip', val = zip_code)
2517
2518 return True
2519
2521 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country')
2522
2523 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'')
2524 self._PRW_region.set_context(context = u'country', val = country)
2525
2526 return True
2527
2529 mapping = [
2530 (self._PRW_street, 'street'),
2531 (self._TCTRL_number, 'number'),
2532 (self._PRW_urb, 'urb'),
2533 (self._PRW_region, 'l10n_state')
2534 ]
2535
2536
2537 for ctrl, field in mapping:
2538 if self.__perhaps_invalidate_address_searcher(ctrl, field):
2539 return True
2540
2541 return True
2542
2544 adr = self._PRW_address_searcher.get_address()
2545 if adr is None:
2546 return True
2547
2548 wx.CallAfter(self.__set_fields_from_address_searcher)
2549 return True
2550
2551
2552
2554 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
2555
2557
2558
2559 new_identity = gmPerson.create_identity (
2560 gender = self._PRW_gender.GetData(),
2561 dob = self._DP_dob.get_pydt(),
2562 lastnames = self._PRW_lastname.GetValue().strip(),
2563 firstnames = self._PRW_firstnames.GetValue().strip()
2564 )
2565 _log.debug('identity created: %s' % new_identity)
2566
2567 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
2568 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
2569
2570 new_identity.save()
2571
2572 name = new_identity.get_active_name()
2573 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
2574 name.save()
2575
2576
2577 is_valid = self.__address_valid_for_save(empty_address_is_valid = False)
2578 if is_valid is True:
2579
2580
2581 try:
2582 new_identity.link_address (
2583 number = self._TCTRL_number.GetValue().strip(),
2584 street = self._PRW_street.GetValue().strip(),
2585 postcode = self._PRW_zip.GetValue().strip(),
2586 urb = self._PRW_urb.GetValue().strip(),
2587 state = self._PRW_region.GetData(),
2588 country = self._PRW_country.GetData()
2589 )
2590 except psycopg2.InternalError:
2591
2592 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip())
2593 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip())
2594 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip())
2595 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip())
2596 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip())
2597 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip())
2598 _log.exception('cannot link address')
2599 gmGuiHelpers.gm_show_error (
2600 aTitle = _('Saving address'),
2601 aMessage = _(
2602 'Cannot save this address.\n'
2603 '\n'
2604 'You will have to add it via the Demographics plugin.\n'
2605 )
2606 )
2607 elif is_valid is False:
2608 gmGuiHelpers.gm_show_error (
2609 aTitle = _('Saving address'),
2610 aMessage = _(
2611 'Address not saved.\n'
2612 '\n'
2613 'You will have to add it via the Demographics plugin.\n'
2614 )
2615 )
2616
2617
2618
2619 new_identity.link_comm_channel (
2620 comm_medium = u'homephone',
2621 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''),
2622 is_confidential = False
2623 )
2624
2625
2626 pk_type = self._PRW_external_id_type.GetData()
2627 id_value = self._TCTRL_external_id_value.GetValue().strip()
2628 if (pk_type is not None) and (id_value != u''):
2629 new_identity.add_external_id(value = id_value, pk_type = pk_type)
2630
2631
2632 new_identity.link_occupation (
2633 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'')
2634 )
2635
2636 self.data = new_identity
2637 return True
2638
2640 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2641
2645
2648
2650 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2651
2652
2653
2654 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
2655 """
2656 Wizard page for entering patient's basic demographic information
2657 """
2658
2659 form_fields = (
2660 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation',
2661 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment'
2662 )
2663
2664 - def __init__(self, parent, title):
2665 """
2666 Creates a new instance of BasicPatDetailsPage
2667 @param parent - The parent widget
2668 @type parent - A wx.Window instance
2669 @param tile - The title of the page
2670 @type title - A StringType instance
2671 """
2672 wx.wizard.WizardPageSimple.__init__(self, parent)
2673 self.__title = title
2674 self.__do_layout()
2675 self.__register_interests()
2676
2677 - def __do_layout(self):
2678 PNL_form = wx.Panel(self, -1)
2679
2680
2681 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name'))
2682 STT_lastname.SetForegroundColour('red')
2683 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1)
2684 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)'))
2685
2686
2687 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)'))
2688 STT_firstname.SetForegroundColour('red')
2689 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1)
2690 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name'))
2691
2692
2693 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name'))
2694 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1)
2695
2696
2697 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth'))
2698 STT_dob.SetForegroundColour('red')
2699 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1)
2700 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one"))
2701
2702
2703 STT_gender = wx.StaticText(PNL_form, -1, _('Gender'))
2704 STT_gender.SetForegroundColour('red')
2705 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1)
2706 self.PRW_gender.SetToolTipString(_("Required: gender of patient"))
2707
2708
2709 STT_title = wx.StaticText(PNL_form, -1, _('Title'))
2710 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1)
2711
2712
2713 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code'))
2714 STT_zip_code.SetForegroundColour('orange')
2715 self.PRW_zip_code = cZipcodePhraseWheel(parent = PNL_form, id = -1)
2716 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code"))
2717
2718
2719 STT_street = wx.StaticText(PNL_form, -1, _('Street'))
2720 STT_street.SetForegroundColour('orange')
2721 self.PRW_street = cStreetPhraseWheel(parent = PNL_form, id = -1)
2722 self.PRW_street.SetToolTipString(_("primary/home address: name of street"))
2723
2724
2725 STT_address_number = wx.StaticText(PNL_form, -1, _('Number'))
2726 STT_address_number.SetForegroundColour('orange')
2727 self.TTC_address_number = wx.TextCtrl(PNL_form, -1)
2728 self.TTC_address_number.SetToolTipString(_("primary/home address: address number"))
2729
2730
2731 STT_town = wx.StaticText(PNL_form, -1, _('Place'))
2732 STT_town.SetForegroundColour('orange')
2733 self.PRW_town = cUrbPhraseWheel(parent = PNL_form, id = -1)
2734 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/..."))
2735
2736
2737 STT_state = wx.StaticText(PNL_form, -1, _('Region'))
2738 STT_state.SetForegroundColour('orange')
2739 self.PRW_state = cStateSelectionPhraseWheel(parent=PNL_form, id=-1)
2740 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/..."))
2741
2742
2743 STT_country = wx.StaticText(PNL_form, -1, _('Country'))
2744 STT_country.SetForegroundColour('orange')
2745 self.PRW_country = cCountryPhraseWheel(parent = PNL_form, id = -1)
2746 self.PRW_country.SetToolTipString(_("primary/home address: country"))
2747
2748
2749 STT_phone = wx.StaticText(PNL_form, -1, _('Phone'))
2750 self.TTC_phone = wx.TextCtrl(PNL_form, -1)
2751 self.TTC_phone.SetToolTipString(_("phone number at home"))
2752
2753
2754 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
2755 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
2756
2757
2758 STT_comment = wx.StaticText(PNL_form, -1, _('Comment'))
2759 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1)
2760 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.'))
2761
2762
2763 self.form_DTD = cFormDTD(fields = self.__class__.form_fields)
2764 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD))
2765
2766
2767 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4)
2768 SZR_input.AddGrowableCol(1)
2769 SZR_input.Add(STT_lastname, 0, wx.SHAPED)
2770 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND)
2771 SZR_input.Add(STT_firstname, 0, wx.SHAPED)
2772 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND)
2773 SZR_input.Add(STT_nick, 0, wx.SHAPED)
2774 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND)
2775 SZR_input.Add(STT_dob, 0, wx.SHAPED)
2776 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND)
2777 SZR_input.Add(STT_gender, 0, wx.SHAPED)
2778 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND)
2779 SZR_input.Add(STT_title, 0, wx.SHAPED)
2780 SZR_input.Add(self.PRW_title, 1, wx.EXPAND)
2781 SZR_input.Add(STT_zip_code, 0, wx.SHAPED)
2782 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND)
2783 SZR_input.Add(STT_street, 0, wx.SHAPED)
2784 SZR_input.Add(self.PRW_street, 1, wx.EXPAND)
2785 SZR_input.Add(STT_address_number, 0, wx.SHAPED)
2786 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND)
2787 SZR_input.Add(STT_town, 0, wx.SHAPED)
2788 SZR_input.Add(self.PRW_town, 1, wx.EXPAND)
2789 SZR_input.Add(STT_state, 0, wx.SHAPED)
2790 SZR_input.Add(self.PRW_state, 1, wx.EXPAND)
2791 SZR_input.Add(STT_country, 0, wx.SHAPED)
2792 SZR_input.Add(self.PRW_country, 1, wx.EXPAND)
2793 SZR_input.Add(STT_phone, 0, wx.SHAPED)
2794 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND)
2795 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
2796 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
2797 SZR_input.Add(STT_comment, 0, wx.SHAPED)
2798 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND)
2799
2800 PNL_form.SetSizerAndFit(SZR_input)
2801
2802
2803 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title)
2804 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2805
2806
2807
2812
2813 - def on_country_selected(self, data):
2814 """Set the states according to entered country."""
2815 self.PRW_state.set_context(context=u'country', val=data)
2816 return True
2817
2818 - def on_name_set(self):
2819 """Set the gender according to entered firstname.
2820
2821 Matches are fetched from existing records in backend.
2822 """
2823 firstname = self.PRW_firstname.GetValue().strip()
2824 rows, idx = gmPG2.run_ro_queries(queries = [{
2825 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
2826 'args': [firstname]
2827 }])
2828 if len(rows) == 0:
2829 return True
2830 wx.CallAfter(self.PRW_gender.SetData, rows[0][0])
2831 return True
2832
2833 - def on_zip_set(self):
2834 """Set the street, town, state and country according to entered zip code."""
2835 zip_code = self.PRW_zip_code.GetValue().strip()
2836 self.PRW_street.set_context(context=u'zip', val=zip_code)
2837 self.PRW_town.set_context(context=u'zip', val=zip_code)
2838 self.PRW_state.set_context(context=u'zip', val=zip_code)
2839 self.PRW_country.set_context(context=u'zip', val=zip_code)
2840 return True
2841
2843 """
2844 Wizard to create a new patient.
2845
2846 TODO:
2847 - write pages for different "themes" of patient creation
2848 - make it configurable which pages are loaded
2849 - make available sets of pages that apply to a country
2850 - make loading of some pages depend upon values in earlier pages, eg
2851 when the patient is female and older than 13 include a page about
2852 "female" data (number of kids etc)
2853
2854 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable()
2855 """
2856
2857 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2858 """
2859 Creates a new instance of NewPatientWizard
2860 @param parent - The parent widget
2861 @type parent - A wx.Window instance
2862 """
2863 id_wiz = wx.NewId()
2864 wx.wizard.Wizard.__init__(self, parent, id_wiz, title)
2865 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
2866 self.__subtitle = subtitle
2867 self.__do_layout()
2868
2870 """Create new patient.
2871
2872 activate, too, if told to do so (and patient successfully created)
2873 """
2874 while True:
2875
2876 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details):
2877 return False
2878
2879 try:
2880
2881 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD)
2882 except:
2883 _log.exception('cannot add new patient - missing identity fields')
2884 gmGuiHelpers.gm_show_error (
2885 _('Cannot create new patient.\n'
2886 'Missing parts of the identity.'
2887 ),
2888 _('Adding new patient')
2889 )
2890 continue
2891
2892 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2893
2894 try:
2895 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2896 except:
2897 _log.exception('cannot finalize new patient - missing address fields')
2898 gmGuiHelpers.gm_show_error (
2899 _('Cannot add address for the new patient.\n'
2900 'You must either enter all of the address fields or\n'
2901 'none at all. The relevant fields are marked in yellow.\n'
2902 '\n'
2903 'You will need to add the address details in the\n'
2904 'demographics module.'
2905 ),
2906 _('Adding new patient')
2907 )
2908 break
2909
2910 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2911
2912 break
2913
2914 if activate:
2915 from Gnumed.wxpython import gmPatSearchWidgets
2916 gmPatSearchWidgets.set_active_patient(patient = ident)
2917
2918 return ident
2919
2920
2921
2923 """Arrange widgets.
2924 """
2925
2926 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle )
2927 self.FitToPage(self.basic_pat_details)
2928
2929
2931 """
2932 This validator is used to ensure that the user has entered all
2933 the required conditional values in the page (eg., to properly
2934 create an address, all the related fields must be filled).
2935 """
2936
2937 - def __init__(self, dtd):
2938 """
2939 Validator initialization.
2940 @param dtd The object containing the data model.
2941 @type dtd A cFormDTD instance
2942 """
2943
2944 wx.PyValidator.__init__(self)
2945
2946 self.form_DTD = dtd
2947
2949 """
2950 Standard cloner.
2951 Note that every validator must implement the Clone() method.
2952 """
2953 return cBasicPatDetailsPageValidator(dtd = self.form_DTD)
2954
2955 - def Validate(self, parent = None):
2956 """
2957 Validate the contents of the given text control.
2958 """
2959 _pnl_form = self.GetWindow().GetParent()
2960
2961 error = False
2962
2963
2964 if _pnl_form.PRW_lastname.GetValue().strip() == '':
2965 error = True
2966 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2967 _pnl_form.PRW_lastname.SetBackgroundColour('pink')
2968 _pnl_form.PRW_lastname.Refresh()
2969 else:
2970 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2971 _pnl_form.PRW_lastname.Refresh()
2972
2973 if _pnl_form.PRW_firstname.GetValue().strip() == '':
2974 error = True
2975 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2976 _pnl_form.PRW_firstname.SetBackgroundColour('pink')
2977 _pnl_form.PRW_firstname.Refresh()
2978 else:
2979 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2980 _pnl_form.PRW_firstname.Refresh()
2981
2982
2983 if _pnl_form.PRW_gender.GetData() is None:
2984 error = True
2985 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2986 _pnl_form.PRW_gender.SetBackgroundColour('pink')
2987 _pnl_form.PRW_gender.Refresh()
2988 else:
2989 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2990 _pnl_form.PRW_gender.Refresh()
2991
2992
2993 if (
2994 (_pnl_form.PRW_dob.GetValue().strip() == u'')
2995 or (not _pnl_form.PRW_dob.is_valid_timestamp())
2996 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900)
2997 ):
2998 error = True
2999 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue()
3000 gmDispatcher.send(signal = 'statustext', msg = msg)
3001 _pnl_form.PRW_dob.SetBackgroundColour('pink')
3002 _pnl_form.PRW_dob.Refresh()
3003 else:
3004 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
3005 _pnl_form.PRW_dob.Refresh()
3006
3007
3008 is_any_field_filled = False
3009 address_fields = (
3010 _pnl_form.TTC_address_number,
3011 _pnl_form.PRW_zip_code,
3012 _pnl_form.PRW_street,
3013 _pnl_form.PRW_town
3014 )
3015 for field in address_fields:
3016 if field.GetValue().strip() == u'':
3017 if is_any_field_filled:
3018 error = True
3019 msg = _('To properly create an address, all the related fields must be filled in.')
3020 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
3021 field.SetBackgroundColour('pink')
3022 field.SetFocus()
3023 field.Refresh()
3024 else:
3025 is_any_field_filled = True
3026 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
3027 field.Refresh()
3028
3029 address_fields = (
3030 _pnl_form.PRW_state,
3031 _pnl_form.PRW_country
3032 )
3033 for field in address_fields:
3034 if field.GetData() is None:
3035 if is_any_field_filled:
3036 error = True
3037 msg = _('To properly create an address, all the related fields must be filled in.')
3038 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
3039 field.SetBackgroundColour('pink')
3040 field.SetFocus()
3041 field.Refresh()
3042 else:
3043 is_any_field_filled = True
3044 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
3045 field.Refresh()
3046
3047 return (not error)
3048
3049 - def TransferToWindow(self):
3050 """
3051 Transfer data from validator to window.
3052 The default implementation returns False, indicating that an error
3053 occurred. We simply return True, as we don't do any data transfer.
3054 """
3055 _pnl_form = self.GetWindow().GetParent()
3056
3057 _pnl_form.PRW_gender.SetData(self.form_DTD['gender'])
3058 _pnl_form.PRW_dob.SetText(self.form_DTD['dob'])
3059 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames'])
3060 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames'])
3061 _pnl_form.PRW_title.SetText(self.form_DTD['title'])
3062 _pnl_form.PRW_nick.SetText(self.form_DTD['nick'])
3063 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation'])
3064 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number'])
3065 _pnl_form.PRW_street.SetText(self.form_DTD['street'])
3066 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code'])
3067 _pnl_form.PRW_town.SetText(self.form_DTD['town'])
3068 _pnl_form.PRW_state.SetData(self.form_DTD['state'])
3069 _pnl_form.PRW_country.SetData(self.form_DTD['country'])
3070 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone'])
3071 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment'])
3072 return True
3073
3075 """
3076 Transfer data from window to validator.
3077 The default implementation returns False, indicating that an error
3078 occurred. We simply return True, as we don't do any data transfer.
3079 """
3080
3081 if not self.GetWindow().GetParent().Validate():
3082 return False
3083 try:
3084 _pnl_form = self.GetWindow().GetParent()
3085
3086 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData()
3087 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData()
3088
3089 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue()
3090 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue()
3091 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue()
3092 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue()
3093
3094 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue()
3095
3096 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue()
3097 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue()
3098 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue()
3099 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue()
3100 self.form_DTD['state'] = _pnl_form.PRW_state.GetData()
3101 self.form_DTD['country'] = _pnl_form.PRW_country.GetData()
3102
3103 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue()
3104
3105 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue()
3106 except:
3107 return False
3108 return True
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
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3158 """Notebook displaying demographics editing pages:
3159
3160 - Identity
3161 - Contacts (addresses, phone numbers, etc)
3162 - Social Network (significant others, GP, etc)
3163
3164 Does NOT act on/listen to the current patient.
3165 """
3166
3168
3169 wx.Notebook.__init__ (
3170 self,
3171 parent = parent,
3172 id = id,
3173 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
3174 name = self.__class__.__name__
3175 )
3176
3177 self.__identity = None
3178 self.__do_layout()
3179 self.SetSelection(0)
3180
3181
3182
3184 """Populate fields in pages with data from model."""
3185 for page_idx in range(self.GetPageCount()):
3186 page = self.GetPage(page_idx)
3187 page.identity = self.__identity
3188
3189 return True
3190
3191
3192
3194 """Build patient edition notebook pages."""
3195
3196
3197 new_page = cPersonContactsManagerPnl(self, -1)
3198 new_page.identity = self.__identity
3199 self.AddPage (
3200 page = new_page,
3201 text = _('Contacts'),
3202 select = True
3203 )
3204
3205
3206 new_page = cPersonIdentityManagerPnl(self, -1)
3207 new_page.identity = self.__identity
3208 self.AddPage (
3209 page = new_page,
3210 text = _('Identity'),
3211 select = False
3212 )
3213
3214
3215 new_page = cPersonSocialNetworkManagerPnl(self, -1)
3216 new_page.identity = self.__identity
3217 self.AddPage (
3218 page = new_page,
3219 text = _('Social Network'),
3220 select = False
3221 )
3222
3223
3224
3226 return self.__identity
3227
3230
3231 identity = property(_get_identity, _set_identity)
3232
3233
3234
3235
3236
3238 """Page containing patient occupations edition fields.
3239 """
3240 - def __init__(self, parent, id, ident=None):
3241 """
3242 Creates a new instance of BasicPatDetailsPage
3243 @param parent - The parent widget
3244 @type parent - A wx.Window instance
3245 @param id - The widget id
3246 @type id - An integer
3247 """
3248 wx.Panel.__init__(self, parent, id)
3249 self.__ident = ident
3250 self.__do_layout()
3251
3253 PNL_form = wx.Panel(self, -1)
3254
3255 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
3256 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
3257 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
3258
3259 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
3260 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
3261
3262
3263 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
3264 SZR_input.AddGrowableCol(1)
3265 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
3266 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
3267 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
3268 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
3269 PNL_form.SetSizerAndFit(SZR_input)
3270
3271
3272 SZR_main = wx.BoxSizer(wx.VERTICAL)
3273 SZR_main.Add(PNL_form, 1, wx.EXPAND)
3274 self.SetSizer(SZR_main)
3275
3278
3279 - def refresh(self, identity=None):
3280 if identity is not None:
3281 self.__ident = identity
3282 jobs = self.__ident.get_occupations()
3283 if len(jobs) > 0:
3284 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
3285 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
3286 return True
3287
3289 if self.PRW_occupation.IsModified():
3290 new_job = self.PRW_occupation.GetValue().strip()
3291 jobs = self.__ident.get_occupations()
3292 for job in jobs:
3293 if job['l10n_occupation'] == new_job:
3294 continue
3295 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
3296 self.__ident.link_occupation(occupation = new_job)
3297 return True
3298
3300 """Patient demographics plugin for main notebook.
3301
3302 Hosts another notebook with pages for Identity, Contacts, etc.
3303
3304 Acts on/listens to the currently active patient.
3305 """
3306
3312
3313
3314
3315
3316
3317
3319 """Arrange widgets."""
3320 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
3321
3322 szr_main = wx.BoxSizer(wx.VERTICAL)
3323 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
3324 self.SetSizerAndFit(szr_main)
3325
3326
3327
3329 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
3330 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
3331
3333 self._schedule_data_reget()
3334
3336 self._schedule_data_reget()
3337
3338
3339
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3468 """
3469 Utility class to test the new patient wizard.
3470 """
3471
3473 """
3474 Create a new instance of TestPanel.
3475 @param parent The parent widget
3476 @type parent A wx.Window instance
3477 """
3478 wx.Panel.__init__(self, parent, id)
3479 wizard = cNewPatientWizard(self)
3480 print wizard.RunWizard()
3481
3482 if __name__ == "__main__":
3483
3484
3486 app = wx.PyWidgetTester(size = (200, 50))
3487 pw = cZipcodePhraseWheel(app.frame, -1)
3488 app.frame.Show(True)
3489 app.MainLoop()
3490
3492 app = wx.PyWidgetTester(size = (200, 50))
3493 pw = cStateSelectionPhraseWheel(app.frame, -1)
3494
3495
3496 app.frame.Show(True)
3497 app.MainLoop()
3498
3500 app = wx.PyWidgetTester(size = (200, 50))
3501 pw = cUrbPhraseWheel(app.frame, -1)
3502 app.frame.Show(True)
3503 pw.set_context(context = u'zip', val = u'04317')
3504 app.MainLoop()
3505
3507 app = wx.PyWidgetTester(size = (200, 50))
3508 pw = cSuburbPhraseWheel(app.frame, -1)
3509 app.frame.Show(True)
3510 app.MainLoop()
3511
3513 app = wx.PyWidgetTester(size = (200, 50))
3514 pw = cAddressTypePhraseWheel(app.frame, -1)
3515 app.frame.Show(True)
3516 app.MainLoop()
3517
3519 app = wx.PyWidgetTester(size = (200, 50))
3520 pw = cAddressPhraseWheel(app.frame, -1)
3521 app.frame.Show(True)
3522 app.MainLoop()
3523
3525 app = wx.PyWidgetTester(size = (200, 50))
3526 pw = cStreetPhraseWheel(app.frame, -1)
3527
3528 app.frame.Show(True)
3529 app.MainLoop()
3530
3532 app = wx.PyWidgetTester(size = (600, 400))
3533 app.SetWidget(cKOrganizerSchedulePnl)
3534 app.MainLoop()
3535
3537 app = wx.PyWidgetTester(size = (600, 400))
3538 widget = cPersonNamesManagerPnl(app.frame, -1)
3539 widget.identity = activate_patient()
3540 app.frame.Show(True)
3541 app.MainLoop()
3542
3544 app = wx.PyWidgetTester(size = (600, 400))
3545 widget = cPersonIDsManagerPnl(app.frame, -1)
3546 widget.identity = activate_patient()
3547 app.frame.Show(True)
3548 app.MainLoop()
3549
3551 app = wx.PyWidgetTester(size = (600, 400))
3552 widget = cPersonIdentityManagerPnl(app.frame, -1)
3553 widget.identity = activate_patient()
3554 app.frame.Show(True)
3555 app.MainLoop()
3556
3561
3566
3568 app = wx.PyWidgetTester(size = (600, 400))
3569 widget = cPersonAddressesManagerPnl(app.frame, -1)
3570 widget.identity = activate_patient()
3571 app.frame.Show(True)
3572 app.MainLoop()
3573
3575 app = wx.PyWidgetTester(size = (600, 400))
3576 widget = cPersonCommsManagerPnl(app.frame, -1)
3577 widget.identity = activate_patient()
3578 app.frame.Show(True)
3579 app.MainLoop()
3580
3587
3589 app = wx.PyWidgetTester(size = (600, 400))
3590 widget = cPersonDemographicsEditorNb(app.frame, -1)
3591 widget.identity = activate_patient()
3592 widget.refresh()
3593 app.frame.Show(True)
3594 app.MainLoop()
3595
3604
3605 if len(sys.argv) > 1 and sys.argv[1] == 'test':
3606
3607 gmI18N.activate_locale()
3608 gmI18N.install_domain(domain='gnumed')
3609 gmPG2.get_connection()
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626 test_urb_prw()
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644