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 var year = expdate.getYear() + 1900;
402 var month = (expdate.getMonth() + 1) + '';
403 var day = (expdate.getDate()) + '';
405 if(!month.match(/\d{2}/)) month = '0' + month;
406 if(!day.match(/\d{2}/)) day = '0' + day;
408 var node = $('ue_expire');
409 node.value = year+'-'+month+'-'+day;
416 key : 'net_access_level',
425 key : 'master_account',
427 id : 'ue_group_lead',
434 key : 'claims_returned_count',
436 id : 'ue_claims_returned',
445 key : 'alert_message',
447 id : 'ue_alert_message',
453 for( var f in fields )
454 dataFields.push(fields[f]);
456 uEditBuildAddrs(patron);
457 uEditBuildPatronSCM(patron);
460 var uEditOldFirstName;
461 var uEditOldMiddleName; /* future */
462 var uEditOldLastName;
463 function uEditCheckNamesDup(type, field) {
464 var newval = uEditNodeVal(field);
467 var dosearch = false;
470 if( newval != uEditOldFirstName )
472 uEditOldFirstName = newval;
476 if( newval != uEditOldLastName )
478 uEditOldLastName = newval;
481 if( dosearch && uEditOldFirstName && uEditOldLastName ) {
482 var search_hash = {};
483 search_hash['first_given_name'] = { value : uEditOldFirstName, group : 0 };
484 search_hash['family_name'] = { value : uEditOldLastName, group : 0 };
485 uEditRunDupeSearch('names', search_hash);
489 var uEditOldIdentValue;
490 function uEditCheckIdentDup(field) {
491 var newval = uEditNodeVal(field);
492 if( newval && newval != uEditOldIdentValue ) {
493 /* searches all ident_value fields */
494 var search_hash = { ident : { value : newval, group : 2 } };
495 uEditRunDupeSearch('ident', search_hash);
496 uEditOldIdentValue = newval;
501 /* Adds all of the addresses attached to the patron object
502 to the fields array */
503 var uEditAddrTemplate;
504 function uEditBuildAddrs(patron) {
505 var tbody = $('ue_address_tbody');
506 if(!uEditAddrTemplate)
507 uEditAddrTemplate = tbody.removeChild($('ue_address_template'));
508 for( var a in patron.addresses() )
509 uEditBuildAddrFields( patron, patron.addresses()[a]);
513 function uEditDeleteAddr( tbody, row, address, detach ) {
514 if(!confirm($('ue_delete_addr_warn').innerHTML)) return;
515 if(address.isnew()) {
517 grep( patron.addresses(),
519 return (i.id() != address.id());
525 for( var f in dataFields ) {
526 if( dataFields[f].object == address ) {
527 dataFields[f] = null;
531 dataFields = compactArray(dataFields);
535 if( detach ) { /* remove the offending address from the list */
540 return (i.id() != address.id());
546 address.isdeleted(1);
550 tbody.removeChild(row);
552 var bid = patron.billing_address();
553 bid = (typeof bid == 'object') ? bid.id() : bid;
555 var mid = patron.mailing_address();
556 mid = (typeof mid == 'object') ? mid.id() : mid;
559 /* -----------------------------------------------------------------------
560 if we're deleting a billing or mailing address
561 make sure some other address is automatically
562 assigned as the billing or mailng address
563 ----------------------------------------------------------------------- */
565 if( bid == address.id() ) {
566 for( var a in patron.addresses() ) {
567 var addr = patron.addresses()[a];
568 if(!addr.isdeleted() && addr.id() != address.id()) {
569 var node = uEditFindAddrInput('billing', addr.id());
571 uEditAddrTypeClick(node, 'billing');
577 if( mid == address.id() ) {
578 for( var a in patron.addresses() ) {
579 var addr = patron.addresses()[a];
580 if(!addr.isdeleted() && addr.id() != address.id()) {
581 var node = uEditFindAddrInput('mailing', addr.id());
583 uEditAddrTypeClick(node, 'mailing');
592 function uEditFindAddrInput(type, id) {
593 var tbody = $('ue_address_tbody');
594 var rows = tbody.getElementsByTagName('tr');
595 for( var r in rows ) {
597 if(row.parentNode != tbody) continue;
598 var node = $n(row, 'ue_addr_'+type+'_yes');
599 if( node.getAttribute('address') == id )
605 function uEditAddrTypeClick(input, type) {
606 var tbody = $('ue_address_tbody');
607 var rows = tbody.getElementsByTagName('tr');
608 for( var r in rows ) {
610 if(row.parentNode != tbody) continue;
611 var node = $n(row, 'ue_addr_'+type+'_yes');
612 removeCSSClass(node.parentNode,'addr_info_checked');
615 addCSSClass(input.parentNode,'addr_info_checked');
616 patron[type+'_address'](input.getAttribute('address'));
623 /* Creates the field entries for an address object. */
624 function uEditBuildAddrFields(patron, address) {
626 var tbody = $('ue_address_tbody');
628 var row = tbody.appendChild(
629 uEditAddrTemplate.cloneNode(true));
631 uEditCheckSharedAddr(patron, address, tbody, row);
633 $n(row, 'ue_addr_delete').onclick =
634 function() { uEditDeleteAddr(tbody, row, address); }
636 if( patron.billing_address() &&
637 address.id() == patron.billing_address().id() )
638 $n(row, 'ue_addr_billing_yes').checked = true;
640 if( patron.mailing_address() &&
641 address.id() == patron.mailing_address().id() )
642 $n(row, 'ue_addr_mailing_yes').checked = true;
644 $n(row, 'ue_addr_billing_yes').setAttribute('address', address.id());
645 $n(row, 'ue_addr_mailing_yes').setAttribute('address', address.id());
647 /* currently, non-owners cannot edit an address */
648 var disabled = (address.usr() != patron.id())
654 key : 'address_type',
657 name : 'ue_addr_label',
666 errkey : 'ue_bad_addr_street',
669 name : 'ue_addr_street1',
678 errkey : 'ue_bad_addr_street',
681 name : 'ue_addr_street2',
690 errkey : 'ue_bad_addr_city',
693 name : 'ue_addr_city',
704 name : 'ue_addr_county',
713 errkey : 'ue_bad_addr_state',
716 name : 'ue_addr_state',
725 errkey : 'ue_bad_addr_country',
728 name : 'ue_addr_country',
737 errkey : 'ue_bad_addr_zip',
740 name : 'ue_addr_zip',
744 onblur : function(f) {
745 var v = uEditNodeVal(f);
746 var req = new Request(ZIP_SEARCH, v);
749 var info = r.getResultObject();
751 var state = $n(f.widget.base, 'ue_addr_state');
752 var county = $n(f.widget.base, 'ue_addr_county');
753 var city = $n(f.widget.base, 'ue_addr_city');
755 state.value = info.state;
759 county.value = info.county;
763 city.value = info.city;
775 key : 'within_city_limits',
778 name : 'ue_addr_inc_yes',
789 name : 'ue_addr_valid_yes',
796 for( var f in fields ) {
797 dataFields.push(fields[f]);
798 uEditActivateField(fields[f]);
802 function uEditBuildPatronSCM(patron) {
803 /* get the list of pre-defined maps */
804 var fields = uEditFindFieldsByKey('stat_cat_entry');
808 /* for each user stat cat, pop it off the list,
809 updated the existing stat map field to match
810 the popped map and shove the existing stat
811 map field onto the user's list of stat maps */
812 while( (map = patron.stat_cat_entries().pop()) ) {
814 var field = grep(fields,
816 return (item.object.stat_cat() == map.stat_cat());
821 var val = map.stat_cat_entry();
823 $n(field.widget.base, field.widget.name).value = val;
824 setSelector($n(field.widget.base, 'ue_stat_cat_selector'), val );
825 field.object.stat_cat_entry(val);
826 field.object.id(map.id());
827 newmaps.push(field.object);
831 for( var m in newmaps )
832 patron.stat_cat_entries().push(newmaps[m]);
836 function uEditBuildSCMField(statcat, row) {
838 var map = new actscecm();
839 map.stat_cat(statcat.id());
840 map.target_usr(patron.id());
845 key : 'stat_cat_entry',
848 name : 'ue_stat_cat_newval',
851 onpostchange : function( field, newval ) {
853 /* see if the current map already resides in
854 the patron entry list */
855 var exists = grep( patron.stat_cat_entries(),
857 return (item.stat_cat() == statcat.id());
863 setSelector($n(row, 'ue_stat_cat_selector'), newval);
869 /* if the map is new but currently contains no value
870 remove it from the set of new maps */
872 patron.stat_cat_entries(
873 grep( patron.stat_cat_entries(),
875 return (item.stat_cat() != map.stat_cat());
888 /* map does not exist in the map array but now has data */
891 if(!patron.stat_cat_entries())
892 patron.stat_cat_entries([]);
893 patron.stat_cat_entries().push(map);
900 dataFields.push(field);
905 /** Run this after a new ident type is selected */
906 function _uEditIdentPostchange(type, field, newval) {
910 /* When the ident type is changed, we change the
911 regex on the ident_value to match the selected type */
912 var vfname = 'ident_value';
913 if(type == 'secondary') vfname = 'ident_value2';
914 var vfield = uEditFindFieldByKey(vfname);
915 var name = identTypesCache[uEditNodeVal(field)].name();
917 hideMe($(type+'_ident_ssn_help'));
918 hideMe($(type+'_ident_dl_help'));
920 if(name.match(/ssn/i)) {
921 vfield.widget.regex = ssnRegex;
922 vfield.errkey = 'ue_bad_ident_ssn';
923 unHideMe($(type+'_ident_ssn_help'));
927 if(name.match(/driver/i)) {
928 vfield.widget.regex = dlRegex;
929 vfield.errkey = 'ue_bad_ident_dl';
930 unHideMe($(type+'_ident_dl_help'));
931 if(!uEditNodeVal(vfield))
932 vfield.widget.node.value = defaultState + '-';
935 vfield.widget.regex = null;
936 vfield.errkey = null;
940 /* focus then valdate the value field */
941 vfield.widget.node.onchange();
942 vfield.widget.node.focus();
946 /* checks to see if the given address is shared by others.
947 * if so, the address row is styled and ...
950 function uEditCheckSharedAddr(patron, address, tbody, row) {
952 if( address.isnew() || (patron.isnew() && !clone) ) return;
954 var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
958 var members = r.getResultObject();
961 for( var m in members ) {
964 if( id != patron.id() ) {
966 addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
967 unHideMe($n(row, 'shared_row'));
968 $n(row, 'ue_addr_delete').disabled = true;
970 if( address.usr() != patron.id() ) {
971 var button = $n(row, 'ue_addr_detach');
974 function() { uEditDeleteAddr( tbody, row, address, true ); }
984 /* if this is a shared address, set the owner field and
985 give the staff a chance to edit the owner if it's not this user */
987 var nnode = $n(row, 'addr_owner_name');
988 var link = $n(row, 'addr_owner');
989 var id = address.usr();
991 if( id == patron.id() ) {
993 nnode.appendChild(text(
994 patron.first_given_name() + ' ' + patron.family_name()));
995 hideMe($n(row, 'owner_link_div'));
1000 function() { window.xulG.spawn_editor({ses:cgi.param('ses'),usr:id}) };
1002 if( userCache[id] ) {
1003 nnode.appendChild(text(
1004 usr.first_given_name() + ' ' + usr.family_name()));
1008 fetchFleshedUser( id,
1010 userCache[usr.id()] = usr;
1011 nnode.appendChild(text(
1012 usr.first_given_name() + ' ' + usr.family_name()));
1028 function uEditCheckDOB(field) {
1030 var dob = uEditNodeVal(field);
1032 /* don't bother if the data isn't valid */
1033 if(!dob || !dob.match(field.widget.regex))
1036 if( dob == __lastdob ) return;
1040 var parts = dob.split(/-/);
1041 parts[2] = parts[2].replace(/[T ].*/,'');
1042 dob = buildDate( parts[0], parts[1], parts[2] );
1044 var today = new Date();
1046 if(!dob || dob > today) {
1047 addCSSClass(field.widget.node, CSS_INVALID_DATA);
1048 alertId('ue_bad_date');
1052 var base = new Date();
1053 base.setYear( today.getYear() + 1900 - ADULT_AGE );
1055 /* patron is at least 18 */
1057 var f = uEditFindFieldByKey('ident_value2');
1059 if( dob < base ) { /* patron is of age */
1061 hideMe(f.widget.node.parentNode.parentNode.parentNode);
1065 unHideMe(f.widget.node.parentNode.parentNode.parentNode);