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);
422 key : 'net_access_level',
431 key : 'master_account',
433 id : 'ue_group_lead',
440 key : 'claims_returned_count',
442 id : 'ue_claims_returned',
451 key : 'alert_message',
453 id : 'ue_alert_message',
459 for( var f in fields )
460 dataFields.push(fields[f]);
462 uEditBuildAddrs(patron);
463 uEditBuildPatronSCM(patron);
466 var uEditOldFirstName;
467 var uEditOldMiddleName; /* future */
468 var uEditOldLastName;
469 function uEditCheckNamesDup(type, field) {
470 var newval = uEditNodeVal(field);
473 var dosearch = false;
476 if( newval != uEditOldFirstName )
478 uEditOldFirstName = newval;
482 if( newval != uEditOldLastName )
484 uEditOldLastName = newval;
487 if( dosearch && uEditOldFirstName && uEditOldLastName ) {
488 var search_hash = {};
489 search_hash['first_given_name'] = { value : uEditOldFirstName, group : 0 };
490 search_hash['family_name'] = { value : uEditOldLastName, group : 0 };
491 uEditRunDupeSearch('names', search_hash);
495 var uEditOldIdentValue;
496 function uEditCheckIdentDup(field) {
497 var newval = uEditNodeVal(field);
498 if( newval && newval != uEditOldIdentValue ) {
499 /* searches all ident_value fields */
500 var search_hash = { ident : { value : newval, group : 2 } };
501 uEditRunDupeSearch('ident', search_hash);
502 uEditOldIdentValue = newval;
507 /* Adds all of the addresses attached to the patron object
508 to the fields array */
509 var uEditAddrTemplate;
510 function uEditBuildAddrs(patron) {
511 var tbody = $('ue_address_tbody');
512 if(!uEditAddrTemplate)
513 uEditAddrTemplate = tbody.removeChild($('ue_address_template'));
514 for( var a in patron.addresses() )
515 uEditBuildAddrFields( patron, patron.addresses()[a]);
519 function uEditDeleteAddr( tbody, row, address, detach ) {
520 if(!confirm($('ue_delete_addr_warn').innerHTML)) return;
521 if(address.isnew()) {
523 grep( patron.addresses(),
525 return (i.id() != address.id());
531 for( var f in dataFields ) {
532 if( dataFields[f].object == address ) {
533 dataFields[f] = null;
537 dataFields = compactArray(dataFields);
541 if( detach ) { /* remove the offending address from the list */
546 return (i.id() != address.id());
552 address.isdeleted(1);
556 tbody.removeChild(row);
558 var bid = patron.billing_address();
559 bid = (typeof bid == 'object') ? bid.id() : bid;
561 var mid = patron.mailing_address();
562 mid = (typeof mid == 'object') ? mid.id() : mid;
565 /* -----------------------------------------------------------------------
566 if we're deleting a billing or mailing address
567 make sure some other address is automatically
568 assigned as the billing or mailng address
569 ----------------------------------------------------------------------- */
571 if( bid == address.id() ) {
572 for( var a in patron.addresses() ) {
573 var addr = patron.addresses()[a];
574 if(!addr.isdeleted() && addr.id() != address.id()) {
575 var node = uEditFindAddrInput('billing', addr.id());
577 uEditAddrTypeClick(node, 'billing');
583 if( mid == address.id() ) {
584 for( var a in patron.addresses() ) {
585 var addr = patron.addresses()[a];
586 if(!addr.isdeleted() && addr.id() != address.id()) {
587 var node = uEditFindAddrInput('mailing', addr.id());
589 uEditAddrTypeClick(node, 'mailing');
598 function uEditFindAddrInput(type, id) {
599 var tbody = $('ue_address_tbody');
600 var rows = tbody.getElementsByTagName('tr');
601 for( var r in rows ) {
603 if(row.parentNode != tbody) continue;
604 var node = $n(row, 'ue_addr_'+type+'_yes');
605 if( node.getAttribute('address') == id )
611 function uEditAddrTypeClick(input, type) {
612 var tbody = $('ue_address_tbody');
613 var rows = tbody.getElementsByTagName('tr');
614 for( var r in rows ) {
616 if(row.parentNode != tbody) continue;
617 var node = $n(row, 'ue_addr_'+type+'_yes');
618 removeCSSClass(node.parentNode,'addr_info_checked');
621 addCSSClass(input.parentNode,'addr_info_checked');
622 patron[type+'_address'](input.getAttribute('address'));
629 /* Creates the field entries for an address object. */
630 function uEditBuildAddrFields(patron, address) {
632 var tbody = $('ue_address_tbody');
634 var row = tbody.appendChild(
635 uEditAddrTemplate.cloneNode(true));
637 uEditCheckSharedAddr(patron, address, tbody, row);
639 $n(row, 'ue_addr_delete').onclick =
640 function() { uEditDeleteAddr(tbody, row, address); }
642 if( patron.billing_address() &&
643 address.id() == patron.billing_address().id() )
644 $n(row, 'ue_addr_billing_yes').checked = true;
646 if( patron.mailing_address() &&
647 address.id() == patron.mailing_address().id() )
648 $n(row, 'ue_addr_mailing_yes').checked = true;
650 $n(row, 'ue_addr_billing_yes').setAttribute('address', address.id());
651 $n(row, 'ue_addr_mailing_yes').setAttribute('address', address.id());
653 /* currently, non-owners cannot edit an address */
654 var disabled = (address.usr() != patron.id())
660 key : 'address_type',
663 name : 'ue_addr_label',
672 errkey : 'ue_bad_addr_street',
675 name : 'ue_addr_street1',
684 errkey : 'ue_bad_addr_street',
687 name : 'ue_addr_street2',
696 errkey : 'ue_bad_addr_city',
699 name : 'ue_addr_city',
710 name : 'ue_addr_county',
719 errkey : 'ue_bad_addr_state',
722 name : 'ue_addr_state',
731 errkey : 'ue_bad_addr_country',
734 name : 'ue_addr_country',
743 errkey : 'ue_bad_addr_zip',
746 name : 'ue_addr_zip',
750 onblur : function(f) {
751 var v = uEditNodeVal(f);
752 var req = new Request(ZIP_SEARCH, v);
755 var info = r.getResultObject();
757 var state = $n(f.widget.base, 'ue_addr_state');
758 var county = $n(f.widget.base, 'ue_addr_county');
759 var city = $n(f.widget.base, 'ue_addr_city');
761 state.value = info.state;
765 county.value = info.county;
769 city.value = info.city;
781 key : 'within_city_limits',
784 name : 'ue_addr_inc_yes',
795 name : 'ue_addr_valid_yes',
802 for( var f in fields ) {
803 dataFields.push(fields[f]);
804 uEditActivateField(fields[f]);
808 function uEditBuildPatronSCM(patron) {
809 /* get the list of pre-defined maps */
810 var fields = uEditFindFieldsByKey('stat_cat_entry');
814 /* for each user stat cat, pop it off the list,
815 updated the existing stat map field to match
816 the popped map and shove the existing stat
817 map field onto the user's list of stat maps */
818 while( (map = patron.stat_cat_entries().pop()) ) {
820 var field = grep(fields,
822 return (item.object.stat_cat() == map.stat_cat());
827 var val = map.stat_cat_entry();
829 $n(field.widget.base, field.widget.name).value = val;
830 setSelector($n(field.widget.base, 'ue_stat_cat_selector'), val );
831 field.object.stat_cat_entry(val);
832 field.object.id(map.id());
833 newmaps.push(field.object);
837 for( var m in newmaps )
838 patron.stat_cat_entries().push(newmaps[m]);
842 function uEditBuildSCMField(statcat, row) {
844 var map = new actscecm();
845 map.stat_cat(statcat.id());
846 map.target_usr(patron.id());
851 key : 'stat_cat_entry',
854 name : 'ue_stat_cat_newval',
857 onpostchange : function( field, newval ) {
859 /* see if the current map already resides in
860 the patron entry list */
861 var exists = grep( patron.stat_cat_entries(),
863 return (item.stat_cat() == statcat.id());
869 setSelector($n(row, 'ue_stat_cat_selector'), newval);
875 /* if the map is new but currently contains no value
876 remove it from the set of new maps */
878 patron.stat_cat_entries(
879 grep( patron.stat_cat_entries(),
881 return (item.stat_cat() != map.stat_cat());
894 /* map does not exist in the map array but now has data */
897 if(!patron.stat_cat_entries())
898 patron.stat_cat_entries([]);
899 patron.stat_cat_entries().push(map);
906 dataFields.push(field);
911 /** Run this after a new ident type is selected */
912 function _uEditIdentPostchange(type, field, newval) {
916 /* When the ident type is changed, we change the
917 regex on the ident_value to match the selected type */
918 var vfname = 'ident_value';
919 if(type == 'secondary') vfname = 'ident_value2';
920 var vfield = uEditFindFieldByKey(vfname);
921 var name = identTypesCache[uEditNodeVal(field)].name();
923 hideMe($(type+'_ident_ssn_help'));
924 hideMe($(type+'_ident_dl_help'));
926 if(name.match(/ssn/i)) {
927 vfield.widget.regex = ssnRegex;
928 vfield.errkey = 'ue_bad_ident_ssn';
929 unHideMe($(type+'_ident_ssn_help'));
933 if(name.match(/driver/i)) {
934 vfield.widget.regex = dlRegex;
935 vfield.errkey = 'ue_bad_ident_dl';
936 unHideMe($(type+'_ident_dl_help'));
937 if(!uEditNodeVal(vfield))
938 vfield.widget.node.value = defaultState + '-';
941 vfield.widget.regex = null;
942 vfield.errkey = null;
946 /* focus then valdate the value field */
947 vfield.widget.node.onchange();
948 vfield.widget.node.focus();
952 /* checks to see if the given address is shared by others.
953 * if so, the address row is styled and ...
956 function uEditCheckSharedAddr(patron, address, tbody, row) {
958 if( address.isnew() || (patron.isnew() && !clone) ) return;
960 var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
964 var members = r.getResultObject();
967 for( var m in members ) {
970 if( id != patron.id() ) {
972 addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
973 unHideMe($n(row, 'shared_row'));
974 $n(row, 'ue_addr_delete').disabled = true;
976 if( address.usr() != patron.id() ) {
977 var button = $n(row, 'ue_addr_detach');
980 function() { uEditDeleteAddr( tbody, row, address, true ); }
990 /* if this is a shared address, set the owner field and
991 give the staff a chance to edit the owner if it's not this user */
993 var nnode = $n(row, 'addr_owner_name');
994 var link = $n(row, 'addr_owner');
995 var id = address.usr();
997 if( id == patron.id() ) {
999 nnode.appendChild(text(
1000 patron.first_given_name() + ' ' + patron.family_name()));
1001 hideMe($n(row, 'owner_link_div'));
1006 function() { window.xulG.spawn_editor({ses:cgi.param('ses'),usr:id}) };
1008 if( userCache[id] ) {
1009 nnode.appendChild(text(
1010 usr.first_given_name() + ' ' + usr.family_name()));
1014 fetchFleshedUser( id,
1016 userCache[usr.id()] = usr;
1017 nnode.appendChild(text(
1018 usr.first_given_name() + ' ' + usr.family_name()));
1034 function uEditCheckDOB(field) {
1036 var dob = uEditNodeVal(field);
1038 /* don't bother if the data isn't valid */
1039 if(!dob || !dob.match(field.widget.regex))
1042 if( dob == __lastdob ) return;
1046 var parts = dob.split(/-/);
1047 parts[2] = parts[2].replace(/[T ].*/,'');
1048 dob = buildDate( parts[0], parts[1], parts[2] );
1050 var today = new Date();
1052 if(!dob || dob > today) {
1053 addCSSClass(field.widget.node, CSS_INVALID_DATA);
1054 alertId('ue_bad_date');
1058 var base = new Date();
1059 base.setYear( today.getYear() + 1900 - ADULT_AGE );
1061 /* patron is at least 18 */
1063 var f = uEditFindFieldByKey('ident_value2');
1065 if( dob < base ) { /* patron is of age */
1067 hideMe(f.widget.node.parentNode.parentNode.parentNode);
1071 unHideMe(f.widget.node.parentNode.parentNode.parentNode);