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 CSS_INVALID_DATA = 'invalid_value';
21 const GUARDIAN_NOTE = 'SYSTEM: Parent/Guardian';
23 /* if they don't have these perms, they shouldn't be here */
26 'group_application.user',
27 'group_application.user.patron',
28 'group_application.user.staff',
29 'group_application.user.staff.circ',
30 'group_application.user.staff.cat',
31 'group_application.user.staff.admin.global_admin',
32 'group_application.user.staff.admin.local_admin',
33 'group_application.user.staff.admin.lib_manager',
34 'group_application.user.staff.cat.cat1',
35 'group_application.user.staff.supercat',
36 'group_application.user.sip_client',
37 'group_application.user.vendor'
41 const numRegex = /^\d+$/;
42 const wordRegex = /^\w+$/;
43 const ssnRegex = /^\d{3}-\d{2}-\d{4}$/;
44 const dlRegex = /^[a-zA-Z]{2}-\w+/; /* driver's license */
45 const phoneRegex = /^\d{3}-\d{3}-\d{4}(| ex\d+)$/i;
46 const nonumRegex = /^[a-zA-Z]\D*$/; /* no numbers, no beginning whitespace */
47 const dateRegex = /^\d{4}-\d{2}-\d{2}/;
48 const zipRegex = /^\d{5}(-\d{4}|$)/; /* 12345 or 12345-6789 */
50 var barredAlerted = false;
53 function uEditUsrnameBlur(field) {
54 var usrname = uEditNodeVal(field);
56 var req = new Request(CHECK_USERNAME, SESSION, usrname);
59 var res = r.getResultObject();
60 if( res && res != patron.id() ) {
61 field.widget.onblur = null; /* prevent alert storm */
62 alertId('ue_dup_username');
63 field.widget.onblur = uEditUsrnameBlur;
66 field.widget.node.focus();
67 field.widget.node.select();
77 function uEditBarcodeBlur(field) {
78 var barcode = uEditNodeVal(field);
80 var req = new Request(CHECK_BARCODE, SESSION, barcode);
83 var res = r.getResultObject();
84 if( res && res != patron.id() ) {
85 field.widget.onblur = null; /* prevent alert storm */
86 alertId('ue_dup_barcode');
87 field.widget.onblur = uEditBarcodeBlur;
90 field.widget.node.focus();
91 field.widget.node.select();
95 var node = uEditFindFieldByWId("ue_username");
96 if(!node.widget.node.value) {
97 node.widget.node.value = barcode;
98 node.widget.node.onchange();
107 function uEditDefineData(patron) {
112 object : patron.card(),
114 errkey : 'ue_bad_barcode',
119 onblur : uEditBarcodeBlur
126 errkey : 'ue_bad_username',
131 onblur : uEditUsrnameBlur
135 required : (patron.isnew()) ? true : false,
138 errkey : 'ue_bad_password',
142 onpostchange : function(field, newval) {
143 var pw2 = uEditFindFieldByWId('ue_password2');
144 /* tell the second passsword input to re-validate */
145 pw2.widget.node.onchange();
151 required : (patron.isnew()) ? true : false,
154 errkey : 'ue_bad_password',
158 onpostchange : function(field, newval) {
159 var pw1f = uEditFindFieldByWId('ue_password1');
160 var pw1 = uEditNodeVal(pw1f);
161 field.widget.regex = new RegExp('^'+pw1+'$');
168 key : 'first_given_name',
169 errkey : 'ue_bad_firstname',
174 onblur : function(field) {
175 uEditCheckNamesDup('first', field );
183 key : 'second_given_name',
184 errkey : 'ue_bad_middlename',
186 id : 'ue_middlename',
195 errkey : 'ue_bad_lastname',
200 onblur : function(field) {
201 uEditCheckNamesDup('last', field );
212 onload : function(val) {
213 setSelector($('ue_suffix_selector'), val);
221 errkey : 'ue_bad_dob',
226 onpostchange : function(field) { uEditCheckDOB(field); },
227 onblur : function(field) { uEditCheckDOB(field); }
234 errkey : 'ue_no_ident',
236 id : 'ue_primary_ident_type',
239 onpostchange : function(field, newval)
240 { _uEditIdentPostchange('primary', field, newval); }
248 id : 'ue_primary_ident',
250 onblur : function(field) {
251 uEditCheckIdentDup(field);
260 id : 'ue_secondary_ident_type',
263 onpostchange : function(field, newval)
264 { _uEditIdentPostchange('secondary', field, newval); }
270 key : 'ident_value2',
272 id : 'ue_secondary_ident',
274 onblur : function(field) {
275 uEditCheckIdentDup(field);
283 errkey : 'ue_bad_email',
287 regex : /.+\@.+\..+/, /* make me better */
288 onblur : function(field) {
289 var val = uEditNodeVal(field);
290 if( val && val != field.oldemail ) {
291 uEditRunDupeSearch('email',
292 { email : { value : val, group : 0 } });
293 field.oldemail = val;
302 errkey : 'ue_bad_phone',
312 key : 'evening_phone',
313 errkey : 'ue_bad_phone',
315 id : 'ue_night_phone',
324 errkey : 'ue_bad_phone',
326 id : 'ue_other_phone',
336 id : 'ue_org_selector',
345 errkey : 'ue_bad_expire',
368 onpostchange : function(field, val) {
369 var afield = uEditFindFieldByKey('alert_message');
371 if( !barredAlerted ) {
372 barredAlerted = true;
373 alertId('ue_made_barred');
375 afield.required = true;
377 afield.required = false;
386 errkey : 'ue_no_profile',
391 onpostchange : function(field, value) {
392 var type = groupsCache[value];
394 var interval = type.perm_interval();
396 /* interval_to_seconds expects 'M' for months, 'm' for minutes */
397 interval = interval.replace(/mon/, 'Mon');
398 var intsecs = parseInt(interval_to_seconds(interval));
400 var expdate = new Date();
401 var exptime = expdate.getTime();
402 exptime += intsecs * 1000;
403 expdate.setTime(exptime);
405 var year = expdate.getYear() + 1900;
406 var month = (expdate.getMonth() + 1) + '';
407 var day = (expdate.getDate()) + '';
409 if(!month.match(/\d{2}/)) month = '0' + month;
410 if(!day.match(/\d{2}/)) day = '0' + day;
412 var node = $('ue_expire');
413 node.value = year+'-'+month+'-'+day;
420 key : 'net_access_level',
429 key : 'master_account',
431 id : 'ue_group_lead',
438 key : 'claims_returned_count',
440 id : 'ue_claims_returned',
448 key : 'alert_message',
450 id : 'ue_alert_message',
456 for( var f in fields )
457 dataFields.push(fields[f]);
459 uEditBuildAddrs(patron);
460 uEditBuildPatronSCM(patron);
463 var uEditOldFirstName;
464 var uEditOldMiddleName; /* future */
465 var uEditOldLastName;
466 function uEditCheckNamesDup(type, field) {
467 var newval = uEditNodeVal(field);
470 var dosearch = false;
473 if( newval != uEditOldFirstName )
475 uEditOldFirstName = newval;
479 if( newval != uEditOldLastName )
481 uEditOldLastName = newval;
484 if( dosearch && uEditOldFirstName && uEditOldLastName ) {
485 var search_hash = {};
486 search_hash['first_given_name'] = { value : uEditOldFirstName, group : 0 };
487 search_hash['family_name'] = { value : uEditOldLastName, group : 0 };
488 uEditRunDupeSearch('names', search_hash);
492 var uEditOldIdentValue;
493 function uEditCheckIdentDup(field) {
494 var newval = uEditNodeVal(field);
495 if( newval && newval != uEditOldIdentValue ) {
496 /* searches all ident_value fields */
497 var search_hash = { ident : { value : newval, group : 2 } };
498 uEditRunDupeSearch('ident', search_hash);
499 uEditOldIdentValue = newval;
504 /* Adds all of the addresses attached to the patron object
505 to the fields array */
506 var uEditAddrTemplate;
507 function uEditBuildAddrs(patron) {
508 var tbody = $('ue_address_tbody');
509 if(!uEditAddrTemplate)
510 uEditAddrTemplate = tbody.removeChild($('ue_address_template'));
511 for( var a in patron.addresses() )
512 uEditBuildAddrFields( patron, patron.addresses()[a]);
516 function uEditDeleteAddr( tbody, row, address, detach ) {
517 if(!confirm($('ue_delete_addr_warn').innerHTML)) return;
518 if(address.isnew()) {
520 grep( patron.addresses(),
522 return (i.id() != address.id());
528 for( var f in dataFields ) {
529 if( dataFields[f].object == address ) {
530 dataFields[f] = null;
534 dataFields = compactArray(dataFields);
538 if( detach ) { /* remove the offending address from the list */
543 return (i.id() != address.id());
549 address.isdeleted(1);
553 tbody.removeChild(row);
555 var bid = patron.billing_address();
556 bid = (typeof bid == 'object') ? bid.id() : bid;
558 var mid = patron.mailing_address();
559 mid = (typeof mid == 'object') ? mid.id() : mid;
562 /* -----------------------------------------------------------------------
563 if we're deleting a billing or mailing address
564 make sure some other address is automatically
565 assigned as the billing or mailng address
566 ----------------------------------------------------------------------- */
568 if( bid == address.id() ) {
569 for( var a in patron.addresses() ) {
570 var addr = patron.addresses()[a];
571 if(!addr.isdeleted() && addr.id() != address.id()) {
572 var node = uEditFindAddrInput('billing', addr.id());
574 uEditAddrTypeClick(node, 'billing');
580 if( mid == address.id() ) {
581 for( var a in patron.addresses() ) {
582 var addr = patron.addresses()[a];
583 if(!addr.isdeleted() && addr.id() != address.id()) {
584 var node = uEditFindAddrInput('mailing', addr.id());
586 uEditAddrTypeClick(node, 'mailing');
595 function uEditFindAddrInput(type, id) {
596 var tbody = $('ue_address_tbody');
597 var rows = tbody.getElementsByTagName('tr');
598 for( var r in rows ) {
600 if(row.parentNode != tbody) continue;
601 var node = $n(row, 'ue_addr_'+type+'_yes');
602 if( node.getAttribute('address') == id )
608 function uEditAddrTypeClick(input, type) {
609 var tbody = $('ue_address_tbody');
610 var rows = tbody.getElementsByTagName('tr');
611 for( var r in rows ) {
613 if(row.parentNode != tbody) continue;
614 var node = $n(row, 'ue_addr_'+type+'_yes');
615 removeCSSClass(node.parentNode,'addr_info_checked');
618 addCSSClass(input.parentNode,'addr_info_checked');
619 patron[type+'_address'](input.getAttribute('address'));
626 /* Creates the field entries for an address object. */
627 function uEditBuildAddrFields(patron, address) {
629 var tbody = $('ue_address_tbody');
631 var row = tbody.appendChild(
632 uEditAddrTemplate.cloneNode(true));
634 uEditCheckSharedAddr(patron, address, tbody, row);
636 $n(row, 'ue_addr_delete').onclick =
637 function() { uEditDeleteAddr(tbody, row, address); }
639 if( patron.billing_address() &&
640 address.id() == patron.billing_address().id() )
641 $n(row, 'ue_addr_billing_yes').checked = true;
643 if( patron.mailing_address() &&
644 address.id() == patron.mailing_address().id() )
645 $n(row, 'ue_addr_mailing_yes').checked = true;
647 $n(row, 'ue_addr_billing_yes').setAttribute('address', address.id());
648 $n(row, 'ue_addr_mailing_yes').setAttribute('address', address.id());
650 /* currently, non-owners cannot edit an address */
651 var disabled = (address.usr() != patron.id())
657 key : 'address_type',
660 name : 'ue_addr_label',
669 errkey : 'ue_bad_addr_street',
672 name : 'ue_addr_street1',
681 errkey : 'ue_bad_addr_street',
684 name : 'ue_addr_street2',
693 errkey : 'ue_bad_addr_city',
696 name : 'ue_addr_city',
707 name : 'ue_addr_county',
716 errkey : 'ue_bad_addr_state',
719 name : 'ue_addr_state',
728 errkey : 'ue_bad_addr_country',
731 name : 'ue_addr_country',
740 errkey : 'ue_bad_addr_zip',
743 name : 'ue_addr_zip',
747 onblur : function(f) {
748 var v = uEditNodeVal(f);
749 var req = new Request(ZIP_SEARCH, v);
752 var info = r.getResultObject();
754 var state = $n(f.widget.base, 'ue_addr_state');
755 var county = $n(f.widget.base, 'ue_addr_county');
756 var city = $n(f.widget.base, 'ue_addr_city');
758 state.value = info.state;
762 county.value = info.county;
766 city.value = info.city;
778 key : 'within_city_limits',
781 name : 'ue_addr_inc_yes',
792 name : 'ue_addr_valid_yes',
799 for( var f in fields ) {
800 dataFields.push(fields[f]);
801 uEditActivateField(fields[f]);
805 function uEditBuildPatronSCM(patron) {
806 /* get the list of pre-defined maps */
807 var fields = uEditFindFieldsByKey('stat_cat_entry');
811 /* for each user stat cat, pop it off the list,
812 updated the existing stat map field to match
813 the popped map and shove the existing stat
814 map field onto the user's list of stat maps */
815 while( (map = patron.stat_cat_entries().pop()) ) {
817 var field = grep(fields,
819 return (item.object.stat_cat() == map.stat_cat());
824 var val = map.stat_cat_entry();
826 $n(field.widget.base, field.widget.name).value = val;
827 setSelector($n(field.widget.base, 'ue_stat_cat_selector'), val );
828 field.object.stat_cat_entry(val);
829 field.object.id(map.id());
830 newmaps.push(field.object);
834 for( var m in newmaps )
835 patron.stat_cat_entries().push(newmaps[m]);
839 function uEditBuildSCMField(statcat, row) {
841 var map = new actscecm();
842 map.stat_cat(statcat.id());
843 map.target_usr(patron.id());
848 key : 'stat_cat_entry',
851 name : 'ue_stat_cat_newval',
854 onpostchange : function( field, newval ) {
856 /* see if the current map already resides in
857 the patron entry list */
858 var exists = grep( patron.stat_cat_entries(),
860 return (item.stat_cat() == statcat.id());
866 setSelector($n(row, 'ue_stat_cat_selector'), newval);
872 /* if the map is new but currently contains no value
873 remove it from the set of new maps */
875 patron.stat_cat_entries(
876 grep( patron.stat_cat_entries(),
878 return (item.stat_cat() != map.stat_cat());
891 /* map does not exist in the map array but now has data */
894 patron.stat_cat_entries().push(map);
901 dataFields.push(field);
906 /** Run this after a new ident type is selected */
907 function _uEditIdentPostchange(type, field, newval) {
911 /* When the ident type is changed, we change the
912 regex on the ident_value to match the selected type */
913 var vfname = 'ident_value';
914 if(type == 'secondary') vfname = 'ident_value2';
915 var vfield = uEditFindFieldByKey(vfname);
916 var name = identTypesCache[uEditNodeVal(field)].name();
918 hideMe($(type+'_ident_ssn_help'));
919 hideMe($(type+'_ident_dl_help'));
921 if(name.match(/ssn/i)) {
922 vfield.widget.regex = ssnRegex;
923 vfield.errkey = 'ue_bad_ident_ssn';
924 unHideMe($(type+'_ident_ssn_help'));
928 if(name.match(/driver/i)) {
929 vfield.widget.regex = dlRegex;
930 vfield.errkey = 'ue_bad_ident_dl';
931 unHideMe($(type+'_ident_dl_help'));
932 if(!uEditNodeVal(vfield))
933 vfield.widget.node.value = defaultState + '-';
936 vfield.widget.regex = null;
937 vfield.errkey = null;
941 /* focus then valdate the value field */
942 vfield.widget.node.onchange();
943 vfield.widget.node.focus();
947 /* checks to see if the given address is shared by others.
948 * if so, the address row is styled and ...
951 function uEditCheckSharedAddr(patron, address, tbody, row) {
953 if( address.isnew() || (patron.isnew() && !clone) ) return;
955 var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
959 var members = r.getResultObject();
962 for( var m in members ) {
965 if( id != patron.id() ) {
967 addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
968 unHideMe($n(row, 'shared_row'));
969 $n(row, 'ue_addr_delete').disabled = true;
971 if( address.usr() != patron.id() ) {
972 var button = $n(row, 'ue_addr_detach');
975 function() { uEditDeleteAddr( tbody, row, address, true ); }
985 /* if this is a shared address, set the owner field and
986 give the staff a chance to edit the owner if it's not this user */
988 var nnode = $n(row, 'addr_owner_name');
989 var link = $n(row, 'addr_owner');
990 var id = address.usr();
992 if( id == patron.id() ) {
994 nnode.appendChild(text(
995 patron.first_given_name() + ' ' + patron.family_name()));
996 hideMe($n(row, 'owner_link_div'));
1000 var ses = cgi.param('ses');
1001 if (xulG.ses) ses = xulG.ses;
1002 if (xulG.params) if (xulG.params.ses) ses = xulG.params.ses;
1004 function() { window.xulG.spawn_editor({ses:ses,usr:id}) };
1006 if( userCache[id] ) {
1007 nnode.appendChild(text(
1008 usr.first_given_name() + ' ' + usr.family_name()));
1012 fetchFleshedUser( id,
1014 userCache[usr.id()] = usr;
1015 nnode.appendChild(text(
1016 usr.first_given_name() + ' ' + usr.family_name()));
1032 function uEditCheckDOB(field) {
1034 var dob = uEditNodeVal(field);
1036 /* don't bother if the data isn't valid */
1037 if(!dob || !dob.match(field.widget.regex))
1040 if( dob == __lastdob ) return;
1044 var parts = dob.split(/-/);
1045 var d = new Date( parts[0], parts[1] - 1, parts[2] );
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 already exists or is at least 18 */
1061 if( !patron.isnew() || dob < base ) return;
1063 if( guardianNote ) return;
1065 /* create a new note to represent the patron's guardian */
1066 var note = new aun();
1067 note.title(GUARDIAN_NOTE);
1069 note.creator(USER.id());
1072 var txt; /* get the guardian info from the staff */
1073 while(!txt || txt == "")
1074 txt = prompt($('ue_juv_guardian').innerHTML);
1077 guardianNote = note;
1079 unHideMe($('ue_guardian_row'));
1080 $('ue_guardian_field').appendChild(text(guardianNote.value()));