1 /* -----------------------------------------------------------------------
2 ----------------------------------------------------------------------- */
3 const SC_FETCH_ALL = 'open-ils.circ:open-ils.circ.stat_cat.actor.retrieve.all';
4 const SC_CREATE_MAP = 'open-ils.circ:open-ils.circ.stat_cat.actor.user_map.create';
5 const SV_FETCH_ALL = 'open-ils.circ:open-ils.circ.survey.retrieve.all';
6 const FETCH_ID_TYPES = 'open-ils.actor:open-ils.actor.user.ident_types.retrieve';
7 const FETCH_GROUPS = 'open-ils.actor:open-ils.actor.groups.tree.retrieve';
8 const FETCH_NET_LEVELS = 'open-ils.actor:open-ils.actor.net_access_level.retrieve.all';
9 const UPDATE_PATRON = 'open-ils.actor:open-ils.actor.patron.update';
10 const PATRON_SEARCH = 'open-ils.actor:open-ils.actor.patron.search.advanced';
11 const ZIP_SEARCH = 'open-ils.search:open-ils.search.zip';
12 const FETCH_ADDR_MEMS = 'open-ils.actor:open-ils.actor.address.members';
13 const FETCH_GRP_MEMS = 'open-ils.actor:open-ils.actor.usergroup.members.retrieve';
14 const CREATE_USER_NOTE = 'open-ils.actor:open-ils.actor.note.create';
15 const CHECK_BARCODE = 'open-ils.actor:open-ils.actor.barcode.exists';
16 const defaultState = 'GA';
17 const defaultCountry = 'USA';
18 const defaultNetAccess = 'None';
19 const defaultNetLevel = 1;
20 const CSS_INVALID_DATA = 'invalid_value';
22 //const GUARDIAN_NOTE = 'SYSTEM: Parent/Guardian';
24 /* if they don't have these perms, they shouldn't be here */
27 'group_application.user',
28 'group_application.user.patron',
29 'group_application.user.staff',
30 'group_application.user.staff.circ',
31 'group_application.user.staff.cat',
32 'group_application.user.staff.admin.global_admin',
33 'group_application.user.staff.admin.local_admin',
34 'group_application.user.staff.admin.lib_manager',
35 'group_application.user.staff.cat.cat1',
36 'group_application.user.staff.supercat',
37 'group_application.user.sip_client',
38 'group_application.user.vendor'
42 const numRegex = /^\d+$/;
43 const wordRegex = /^[\w-]+$/;
44 const unameRegex = /^\w[\.\w\@-]*$/;
45 const ssnRegex = /^\d{3}-\d{2}-\d{4}$/;
46 const dlRegex = /^[a-zA-Z]{2}-\w+/; /* driver's license */
47 const phoneRegex = /^\d{3}-\d{3}-\d{4}(| ex\d+)$/i;
48 const nonumRegex = /^[a-zA-Z]\D*$/; /* no numbers, no beginning whitespace */
49 const dateRegex = /^\d{4}-\d{2}-\d{2}/;
50 const zipRegex = /^\d{5}(-\d{4}|-?$)/; /* 12345 or 12345-6789 */
52 var barredAlerted = false;
55 function uEditUsrnameBlur(field) {
56 var usrname = uEditNodeVal(field);
58 var req = new Request(CHECK_USERNAME, SESSION, usrname);
61 var res = r.getResultObject();
62 if( res && res != patron.id() ) {
63 field.widget.onblur = null; /* prevent alert storm */
64 alertId('ue_dup_username');
65 field.widget.onblur = uEditUsrnameBlur;
68 field.widget.node.focus();
69 field.widget.node.select();
79 function uEditBarcodeBlur(field) {
80 var barcode = uEditNodeVal(field);
82 _debug("blurring card with new value " + barcode);
83 var req = new Request(CHECK_BARCODE, SESSION, barcode);
86 var res = r.getResultObject();
87 if( res && res != patron.id() ) {
88 field.widget.onblur = null; /* prevent alert storm */
89 alertId('ue_dup_barcode');
90 field.widget.onblur = uEditBarcodeBlur;
93 field.widget.node.focus();
94 field.widget.node.select();
98 var node = uEditFindFieldByWId("ue_username");
99 if(!node.widget.node.value) {
100 node.widget.node.value = barcode;
101 node.widget.node.onchange();
110 function uEditDefineData(patron) {
115 object : patron.card(),
117 errkey : 'ue_bad_barcode',
122 onblur : uEditBarcodeBlur
129 errkey : 'ue_bad_username',
134 onblur : uEditUsrnameBlur
138 required : (patron.isnew()) ? true : false,
141 errkey : 'ue_bad_password',
145 onpostchange : function(field, newval) {
146 var pw2 = uEditFindFieldByWId('ue_password2');
147 /* tell the second passsword input to re-validate */
148 pw2.widget.node.onchange();
154 required : (patron.isnew()) ? true : false,
157 errkey : 'ue_bad_password',
161 onpostchange : function(field, newval) {
162 var pw1f = uEditFindFieldByWId('ue_password1');
163 var pw1 = uEditNodeVal(pw1f);
164 field.widget.regex = new RegExp('^'+pw1+'$');
165 if( pw1 ) field.required = true;
168 field.required = false;
176 key : 'first_given_name',
177 errkey : 'ue_bad_firstname',
182 onblur : function(field) {
183 uEditCheckNamesDup('first', field );
191 key : 'second_given_name',
192 errkey : 'ue_bad_middlename',
194 id : 'ue_middlename',
203 errkey : 'ue_bad_lastname',
208 onblur : function(field) {
209 uEditCheckNamesDup('last', field );
220 onload : function(val) {
221 setSelector($('ue_suffix_selector'), val);
222 $('ue_suffix_selector').onchange = function() {
223 uEditFindFieldByKey('suffix').widget.node.onchange();
232 errkey : 'ue_bad_dob',
237 onpostchange : function(field) { uEditCheckDOB(field); },
238 onblur : function(field) { uEditCheckDOB(field); }
245 errkey : 'ue_no_ident',
247 id : 'ue_primary_ident_type',
250 onpostchange : function(field, newval)
251 { _uEditIdentPostchange('primary', field, newval); }
259 id : 'ue_primary_ident',
261 onblur : function(field) {
262 uEditCheckIdentDup(field);
269 key : 'ident_value2',
271 id : 'ue_secondary_ident',
279 errkey : 'ue_bad_email',
283 regex : /.+\@.+\..+/, /* make me better */
284 onblur : function(field) {
285 var val = uEditNodeVal(field);
286 if( val && val != field.oldemail ) {
287 uEditRunDupeSearch('email',
288 { email : { value : val, group : 0 } });
289 field.oldemail = val;
298 errkey : 'ue_bad_phone',
308 key : 'evening_phone',
309 errkey : 'ue_bad_phone',
311 id : 'ue_night_phone',
320 errkey : 'ue_bad_phone',
322 id : 'ue_other_phone',
332 id : 'ue_org_selector',
341 errkey : 'ue_bad_expire',
364 onpostchange : function(field, val) {
365 var afield = uEditFindFieldByKey('alert_message');
367 if( !barredAlerted ) {
368 barredAlerted = true;
369 alertId('ue_made_barred');
371 afield.required = true;
373 afield.required = false;
382 errkey : 'ue_no_profile',
387 onpostchange : function(field, value) {
388 var type = groupsCache[value];
390 var interval = type.perm_interval();
392 /* interval_to_seconds expects 'M' for months, 'm' for minutes */
393 interval = interval.replace(/mon/, 'Mon');
394 var intsecs = parseInt(interval_to_seconds(interval));
396 var expdate = new Date();
397 var exptime = expdate.getTime();
398 exptime += intsecs * 1000;
399 expdate.setTime(exptime);
401 _debug("profile change (interval= '"+interval+"', seconds="+intsecs+")\n\tgenerated a date of " + expdate);
403 var year = expdate.getYear() + 1900;
404 var month = (expdate.getMonth() + 1) + '';
405 var day = (expdate.getDate()) + '';
407 if(!month.match(/\d{2}/)) month = '0' + month;
408 if(!day.match(/\d{2}/)) day = '0' + day;
411 var node = $('ue_expire');
412 node.value = year+'-'+month+'-'+day;
414 _debug("profile change formatted date to "+ node.value);
421 key : 'net_access_level',
430 key : 'master_account',
432 id : 'ue_group_lead',
439 key : 'claims_returned_count',
441 id : 'ue_claims_returned',
450 key : 'alert_message',
452 id : 'ue_alert_message',
458 for( var f in fields )
459 dataFields.push(fields[f]);
461 uEditBuildAddrs(patron);
462 uEditBuildPatronSCM(patron);
465 var uEditOldFirstName;
466 var uEditOldMiddleName; /* future */
467 var uEditOldLastName;
468 function uEditCheckNamesDup(type, field) {
469 var newval = uEditNodeVal(field);
472 var dosearch = false;
475 if( newval != uEditOldFirstName )
477 uEditOldFirstName = newval;
481 if( newval != uEditOldLastName )
483 uEditOldLastName = newval;
486 if( dosearch && uEditOldFirstName && uEditOldLastName ) {
487 var search_hash = {};
488 search_hash['first_given_name'] = { value : uEditOldFirstName, group : 0 };
489 search_hash['family_name'] = { value : uEditOldLastName, group : 0 };
490 uEditRunDupeSearch('names', search_hash);
494 var uEditOldIdentValue;
495 function uEditCheckIdentDup(field) {
496 var newval = uEditNodeVal(field);
497 if( newval && newval != uEditOldIdentValue ) {
498 /* searches all ident_value fields */
499 var search_hash = { ident : { value : newval, group : 2 } };
500 uEditRunDupeSearch('ident', search_hash);
501 uEditOldIdentValue = newval;
506 /* Adds all of the addresses attached to the patron object
507 to the fields array */
508 var uEditAddrTemplate;
509 function uEditBuildAddrs(patron) {
510 var tbody = $('ue_address_tbody');
511 if(!uEditAddrTemplate)
512 uEditAddrTemplate = tbody.removeChild($('ue_address_template'));
513 for( var a in patron.addresses() )
514 uEditBuildAddrFields( patron, patron.addresses()[a]);
518 function uEditDeleteAddr( tbody, row, address, detach ) {
519 if(!confirm($('ue_delete_addr_warn').innerHTML)) return;
520 if(address.isnew()) {
522 grep( patron.addresses(),
524 return (i.id() != address.id());
530 for( var f in dataFields ) {
531 if( dataFields[f].object == address ) {
532 dataFields[f] = null;
536 dataFields = compactArray(dataFields);
540 if( detach ) { /* remove the offending address from the list */
545 return (i.id() != address.id());
551 address.isdeleted(1);
555 tbody.removeChild(row);
557 var bid = patron.billing_address();
558 bid = (typeof bid == 'object') ? bid.id() : bid;
560 var mid = patron.mailing_address();
561 mid = (typeof mid == 'object') ? mid.id() : mid;
564 /* -----------------------------------------------------------------------
565 if we're deleting a billing or mailing address
566 make sure some other address is automatically
567 assigned as the billing or mailng address
568 ----------------------------------------------------------------------- */
570 if( bid == address.id() ) {
571 for( var a in patron.addresses() ) {
572 var addr = patron.addresses()[a];
573 if(!addr.isdeleted() && addr.id() != address.id()) {
574 var node = uEditFindAddrInput('billing', addr.id());
576 uEditAddrTypeClick(node, 'billing');
582 if( mid == address.id() ) {
583 for( var a in patron.addresses() ) {
584 var addr = patron.addresses()[a];
585 if(!addr.isdeleted() && addr.id() != address.id()) {
586 var node = uEditFindAddrInput('mailing', addr.id());
588 uEditAddrTypeClick(node, 'mailing');
597 function uEditFindAddrInput(type, id) {
598 var tbody = $('ue_address_tbody');
599 var rows = tbody.getElementsByTagName('tr');
600 for( var r in rows ) {
602 if(row.parentNode != tbody) continue;
603 var node = $n(row, 'ue_addr_'+type+'_yes');
604 if( node.getAttribute('address') == id )
610 function uEditAddrTypeClick(input, type) {
611 var tbody = $('ue_address_tbody');
612 var rows = tbody.getElementsByTagName('tr');
613 for( var r in rows ) {
615 if(row.parentNode != tbody) continue;
616 var node = $n(row, 'ue_addr_'+type+'_yes');
617 removeCSSClass(node.parentNode,'addr_info_checked');
620 addCSSClass(input.parentNode,'addr_info_checked');
621 patron[type+'_address'](input.getAttribute('address'));
628 /* Creates the field entries for an address object. */
629 function uEditBuildAddrFields(patron, address) {
631 var tbody = $('ue_address_tbody');
633 var row = tbody.appendChild(
634 uEditAddrTemplate.cloneNode(true));
636 uEditCheckSharedAddr(patron, address, tbody, row);
638 $n(row, 'ue_addr_delete').onclick =
639 function() { uEditDeleteAddr(tbody, row, address); }
641 if( patron.billing_address() &&
642 address.id() == patron.billing_address().id() )
643 $n(row, 'ue_addr_billing_yes').checked = true;
645 if( patron.mailing_address() &&
646 address.id() == patron.mailing_address().id() )
647 $n(row, 'ue_addr_mailing_yes').checked = true;
649 $n(row, 'ue_addr_billing_yes').setAttribute('address', address.id());
650 $n(row, 'ue_addr_mailing_yes').setAttribute('address', address.id());
652 /* currently, non-owners cannot edit an address */
653 var disabled = (address.usr() != patron.id())
659 key : 'address_type',
662 name : 'ue_addr_label',
671 errkey : 'ue_bad_addr_street',
674 name : 'ue_addr_street1',
683 errkey : 'ue_bad_addr_street',
686 name : 'ue_addr_street2',
695 errkey : 'ue_bad_addr_city',
698 name : 'ue_addr_city',
709 name : 'ue_addr_county',
718 errkey : 'ue_bad_addr_state',
721 name : 'ue_addr_state',
730 errkey : 'ue_bad_addr_country',
733 name : 'ue_addr_country',
742 errkey : 'ue_bad_addr_zip',
745 name : 'ue_addr_zip',
749 onblur : function(f) {
750 var v = uEditNodeVal(f);
751 var req = new Request(ZIP_SEARCH, v);
754 var info = r.getResultObject();
756 var state = $n(f.widget.base, 'ue_addr_state');
757 var county = $n(f.widget.base, 'ue_addr_county');
758 var city = $n(f.widget.base, 'ue_addr_city');
760 state.value = info.state;
764 county.value = info.county;
768 city.value = info.city;
780 key : 'within_city_limits',
783 name : 'ue_addr_inc_yes',
794 name : 'ue_addr_valid_yes',
801 for( var f in fields ) {
802 dataFields.push(fields[f]);
803 uEditActivateField(fields[f]);
807 function uEditBuildPatronSCM(patron) {
808 /* get the list of pre-defined maps */
809 var fields = uEditFindFieldsByKey('stat_cat_entry');
813 /* for each user stat cat, pop it off the list,
814 updated the existing stat map field to match
815 the popped map and shove the existing stat
816 map field onto the user's list of stat maps */
817 while( (map = patron.stat_cat_entries().pop()) ) {
819 var field = grep(fields,
821 return (item.object.stat_cat() == map.stat_cat());
826 var val = map.stat_cat_entry();
828 $n(field.widget.base, field.widget.name).value = val;
829 setSelector($n(field.widget.base, 'ue_stat_cat_selector'), val );
830 field.object.stat_cat_entry(val);
831 field.object.id(map.id());
832 newmaps.push(field.object);
836 for( var m in newmaps )
837 patron.stat_cat_entries().push(newmaps[m]);
841 function uEditBuildSCMField(statcat, row) {
843 var map = new actscecm();
844 map.stat_cat(statcat.id());
845 map.target_usr(patron.id());
850 key : 'stat_cat_entry',
853 name : 'ue_stat_cat_newval',
856 onpostchange : function( field, newval ) {
858 /* see if the current map already resides in
859 the patron entry list */
860 var exists = grep( patron.stat_cat_entries(),
862 return (item.stat_cat() == statcat.id());
868 setSelector($n(row, 'ue_stat_cat_selector'), newval);
874 /* if the map is new but currently contains no value
875 remove it from the set of new maps */
877 patron.stat_cat_entries(
878 grep( patron.stat_cat_entries(),
880 return (item.stat_cat() != map.stat_cat());
893 /* map does not exist in the map array but now has data */
896 if(!patron.stat_cat_entries())
897 patron.stat_cat_entries([]);
898 patron.stat_cat_entries().push(map);
905 dataFields.push(field);
910 /** Run this after a new ident type is selected */
911 function _uEditIdentPostchange(type, field, newval) {
915 /* When the ident type is changed, we change the
916 regex on the ident_value to match the selected type */
917 var vfname = 'ident_value';
918 if(type == 'secondary') vfname = 'ident_value2';
919 var vfield = uEditFindFieldByKey(vfname);
920 var name = identTypesCache[uEditNodeVal(field)].name();
922 hideMe($(type+'_ident_ssn_help'));
923 hideMe($(type+'_ident_dl_help'));
925 if(name.match(/ssn/i)) {
926 vfield.widget.regex = ssnRegex;
927 vfield.errkey = 'ue_bad_ident_ssn';
928 unHideMe($(type+'_ident_ssn_help'));
932 if(name.match(/driver/i)) {
933 vfield.widget.regex = dlRegex;
934 vfield.errkey = 'ue_bad_ident_dl';
935 unHideMe($(type+'_ident_dl_help'));
936 if(!uEditNodeVal(vfield))
937 vfield.widget.node.value = defaultState + '-';
940 vfield.widget.regex = null;
941 vfield.errkey = null;
945 /* focus then valdate the value field */
946 vfield.widget.node.onchange();
947 vfield.widget.node.focus();
951 /* checks to see if the given address is shared by others.
952 * if so, the address row is styled and ...
955 function uEditCheckSharedAddr(patron, address, tbody, row) {
957 if( address.isnew() || (patron.isnew() && !clone) ) return;
959 var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
963 var members = r.getResultObject();
966 for( var m in members ) {
969 if( id != patron.id() ) {
971 addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
972 unHideMe($n(row, 'shared_row'));
973 $n(row, 'ue_addr_delete').disabled = true;
975 if( address.usr() != patron.id() ) {
976 var button = $n(row, 'ue_addr_detach');
979 function() { uEditDeleteAddr( tbody, row, address, true ); }
989 /* if this is a shared address, set the owner field and
990 give the staff a chance to edit the owner if it's not this user */
992 var nnode = $n(row, 'addr_owner_name');
993 var link = $n(row, 'addr_owner');
994 var id = address.usr();
996 if( id == patron.id() ) {
998 nnode.appendChild(text(
999 patron.first_given_name() + ' ' + patron.family_name()));
1000 hideMe($n(row, 'owner_link_div'));
1005 function() { window.xulG.spawn_editor({ses:cgi.param('ses'),usr:id}) };
1007 if( userCache[id] ) {
1008 nnode.appendChild(text(
1009 usr.first_given_name() + ' ' + usr.family_name()));
1013 fetchFleshedUser( id,
1015 userCache[usr.id()] = usr;
1016 nnode.appendChild(text(
1017 usr.first_given_name() + ' ' + usr.family_name()));
1033 function uEditCheckDOB(field) {
1035 var dob = uEditNodeVal(field);
1037 /* don't bother if the data isn't valid */
1038 if(!dob || !dob.match(field.widget.regex))
1041 if( dob == __lastdob ) return;
1045 var parts = dob.split(/-/);
1046 parts[2] = parts[2].replace(/[T ].*/,'');
1047 dob = buildDate( parts[0], parts[1], parts[2] );
1049 var today = new Date();
1051 if(!dob || dob > today) {
1052 addCSSClass(field.widget.node, CSS_INVALID_DATA);
1053 alertId('ue_bad_date');
1057 var base = new Date();
1058 base.setYear( today.getYear() + 1900 - ADULT_AGE );
1060 /* patron is at least 18 */
1062 var f = uEditFindFieldByKey('ident_value2');
1064 if( dob < base ) { /* patron is of age */
1066 hideMe(f.widget.node.parentNode.parentNode.parentNode);
1070 unHideMe(f.widget.node.parentNode.parentNode.parentNode);