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';
25 const numRegex = /^\d+$/;
26 const wordRegex = /^[\w-]+$/;
27 const unameRegex = /^\w[\.\w\@-]*$/;
28 const ssnRegex = /^\d{3}-\d{2}-\d{4}$/;
29 const dlRegex = /^[a-zA-Z]{2}-\w+/; /* driver's license */
30 const phoneRegex = /^\d{3}-\d{3}-\d{4}(| \S+.*)$/i;
31 const nonumRegex = /^[a-zA-Z]\D*$/; /* no numbers, no beginning whitespace */
32 const dateRegex = /^\d{4}-\d{2}-\d{2}/;
33 const zipRegex = /^\d{5}(-\d{4}|-?$)/; /* 12345 or 12345-6789 */
35 var barredAlerted = false;
38 function uEditUsrnameBlur(field) {
39 var usrname = uEditNodeVal(field);
40 if (!usrname) { return; }
41 var req = new Request(CHECK_USERNAME, SESSION, usrname);
44 var res = r.getResultObject();
45 if( res !== null && res != patron.id() ) {
46 field.widget.onblur = null; /* prevent alert storm */
47 alertId('ue_dup_username');
48 field.widget.onblur = uEditUsrnameBlur;
51 field.widget.node.focus();
52 field.widget.node.select();
62 function uEditBarcodeBlur(field) {
63 var barcode = uEditNodeVal(field);
65 _debug("blurring card with new value " + barcode);
66 var req = new Request(CHECK_BARCODE, SESSION, barcode);
69 var res = r.getResultObject();
70 if( res !== null && res != patron.id() ) {
71 field.widget.onblur = null; /* prevent alert storm */
72 alertId('ue_dup_barcode');
73 field.widget.onblur = uEditBarcodeBlur;
76 field.widget.node.focus();
77 field.widget.node.select();
81 var node = uEditFindFieldByWId("ue_username");
82 if(!node.widget.node.value) {
83 node.widget.node.value = barcode;
84 node.widget.node.onchange();
93 function uEditDefineData(patron) {
98 object : patron.card(),
100 errkey : 'ue_bad_barcode',
105 onblur : uEditBarcodeBlur
112 errkey : 'ue_bad_username',
117 onblur : uEditUsrnameBlur
121 required : (patron.isnew()) ? true : false,
124 errkey : 'ue_bad_password',
128 onpostchange : function(field, newval) {
129 var pw2 = uEditFindFieldByWId('ue_password2');
130 /* tell the second passsword input to re-validate */
131 pw2.widget.node.onchange();
137 required : (patron.isnew()) ? true : false,
140 errkey : 'ue_bad_password',
144 onpostchange : function(field, newval) {
145 var pw1f = uEditFindFieldByWId('ue_password1');
146 var pw1 = uEditNodeVal(pw1f);
147 field.widget.regex = new RegExp('^'+pw1+'$');
148 if( pw1 ) field.required = true;
151 field.required = false;
159 key : 'first_given_name',
160 errkey : 'ue_bad_firstname',
165 onblur : function(field) {
166 uEditCheckNamesDup('first', field );
174 key : 'second_given_name',
175 errkey : 'ue_bad_middlename',
177 id : 'ue_middlename',
186 errkey : 'ue_bad_lastname',
191 onblur : function(field) {
192 uEditCheckNamesDup('last', field );
203 onload : function(val) {
204 setSelector($('ue_suffix_selector'), val);
205 $('ue_suffix_selector').onchange = function() {
206 uEditFindFieldByKey('suffix').widget.node.onchange();
215 errkey : 'ue_bad_dob',
220 onpostchange : function(field) { uEditCheckDOB(field); },
221 onblur : function(field) { uEditCheckDOB(field); }
228 errkey : 'ue_no_ident',
230 id : 'ue_primary_ident_type',
233 onpostchange : function(field, newval)
234 { _uEditIdentPostchange('primary', field, newval); }
242 id : 'ue_primary_ident',
244 onblur : function(field) {
245 uEditCheckIdentDup(field);
252 key : 'ident_value2',
254 id : 'ue_secondary_ident',
262 errkey : 'ue_bad_email',
266 regex : /.+\@.+\..+/, /* make me better */
267 onblur : function(field) {
268 var val = uEditNodeVal(field);
269 if( val && val != field.oldemail ) {
270 uEditRunDupeSearch('email',
271 { email : { value : val, group : 0 } });
272 field.oldemail = val;
281 errkey : 'ue_bad_phone',
291 key : 'evening_phone',
292 errkey : 'ue_bad_phone',
294 id : 'ue_night_phone',
303 errkey : 'ue_bad_phone',
305 id : 'ue_other_phone',
315 id : 'ue_org_selector',
324 errkey : 'ue_bad_expire',
347 onpostchange : function(field, val) {
348 var afield = uEditFindFieldByKey('alert_message');
350 if( !barredAlerted ) {
351 barredAlerted = true;
352 alertId('ue_made_barred');
354 afield.required = true;
356 afield.required = false;
365 errkey : 'ue_no_profile',
370 onpostchange : function(field, value) {
371 var type = groupsCache[value];
373 var interval = type.perm_interval();
375 /* interval_to_seconds expects 'M' for months, 'm' for minutes */
376 interval = interval.replace(/mon/, 'Mon');
377 var intsecs = parseInt(interval_to_seconds(interval));
379 var expdate = new Date();
380 var exptime = expdate.getTime();
381 exptime += intsecs * 1000;
382 expdate.setTime(exptime);
384 _debug("profile change (interval= '"+interval+"', seconds="+intsecs+")\n\tgenerated a date of " + expdate);
386 var year = expdate.getYear() + 1900;
387 var month = (expdate.getMonth() + 1) + '';
388 var day = (expdate.getDate()) + '';
390 if(!month.match(/\d{2}/)) month = '0' + month;
391 if(!day.match(/\d{2}/)) day = '0' + day;
394 var node = $('ue_expire');
395 node.value = year+'-'+month+'-'+day;
397 _debug("profile change formatted date to "+ node.value);
405 key : 'net_access_level',
414 key : 'master_account',
416 id : 'ue_group_lead',
423 key : 'claims_returned_count',
425 id : 'ue_claims_returned',
434 key : 'alert_message',
436 id : 'ue_alert_message',
442 for( var f in fields )
443 dataFields.push(fields[f]);
445 uEditBuildAddrs(patron);
446 uEditBuildPatronSCM(patron);
449 var uEditOldFirstName;
450 var uEditOldMiddleName; /* future */
451 var uEditOldLastName;
452 function uEditCheckNamesDup(type, field) {
453 var newval = uEditNodeVal(field);
456 var dosearch = false;
459 if( newval != uEditOldFirstName )
461 uEditOldFirstName = newval;
465 if( newval != uEditOldLastName )
467 uEditOldLastName = newval;
470 if( dosearch && uEditOldFirstName && uEditOldLastName ) {
471 var search_hash = {};
472 search_hash['first_given_name'] = { value : uEditOldFirstName, group : 0 };
473 search_hash['family_name'] = { value : uEditOldLastName, group : 0 };
474 uEditRunDupeSearch('names', search_hash);
478 var uEditOldIdentValue;
479 function uEditCheckIdentDup(field) {
480 var newval = uEditNodeVal(field);
481 if( newval && newval != uEditOldIdentValue ) {
482 /* searches all ident_value fields */
483 var search_hash = { ident : { value : newval, group : 2 } };
484 uEditRunDupeSearch('ident', search_hash);
485 uEditOldIdentValue = newval;
490 /* Adds all of the addresses attached to the patron object
491 to the fields array */
492 var uEditAddrTemplate;
493 function uEditBuildAddrs(patron) {
494 var tbody = $('ue_address_tbody');
495 if(!uEditAddrTemplate)
496 uEditAddrTemplate = tbody.removeChild($('ue_address_template'));
497 for( var a in patron.addresses() )
498 uEditBuildAddrFields( patron, patron.addresses()[a]);
502 function uEditDeleteAddr( tbody, row, address, detach ) {
503 if(!confirm($('ue_delete_addr_warn').innerHTML)) return;
504 if(address.isnew()) {
506 grep( patron.addresses(),
508 return (i.id() != address.id());
514 for( var f in dataFields ) {
515 if( dataFields[f].object == address ) {
516 dataFields[f] = null;
520 dataFields = compactArray(dataFields);
524 if( detach ) { /* remove the offending address from the list */
529 return (i.id() != address.id());
535 address.isdeleted(1);
539 tbody.removeChild(row);
541 var bid = patron.billing_address();
542 bid = (typeof bid == 'object') ? bid.id() : bid;
544 var mid = patron.mailing_address();
545 mid = (typeof mid == 'object') ? mid.id() : mid;
548 /* -----------------------------------------------------------------------
549 if we're deleting a billing or mailing address
550 make sure some other address is automatically
551 assigned as the billing or mailng address
552 ----------------------------------------------------------------------- */
554 if( bid == address.id() ) {
555 for( var a in patron.addresses() ) {
556 var addr = patron.addresses()[a];
557 if(!addr.isdeleted() && addr.id() != address.id()) {
558 var node = uEditFindAddrInput('billing', addr.id());
560 uEditAddrTypeClick(node, 'billing');
566 if( mid == address.id() ) {
567 for( var a in patron.addresses() ) {
568 var addr = patron.addresses()[a];
569 if(!addr.isdeleted() && addr.id() != address.id()) {
570 var node = uEditFindAddrInput('mailing', addr.id());
572 uEditAddrTypeClick(node, 'mailing');
581 function uEditFindAddrInput(type, id) {
582 var tbody = $('ue_address_tbody');
583 var rows = tbody.getElementsByTagName('tr');
584 for( var r in rows ) {
586 if(row.parentNode != tbody) continue;
587 var node = $n(row, 'ue_addr_'+type+'_yes');
588 if( node.getAttribute('address') == id )
594 function uEditAddrTypeClick(input, type) {
595 var tbody = $('ue_address_tbody');
596 var rows = tbody.getElementsByTagName('tr');
597 for( var r in rows ) {
599 if(row.parentNode != tbody) continue;
600 var node = $n(row, 'ue_addr_'+type+'_yes');
601 removeCSSClass(node.parentNode,'addr_info_checked');
604 addCSSClass(input.parentNode,'addr_info_checked');
605 patron[type+'_address'](input.getAttribute('address'));
612 /* Creates the field entries for an address object. */
613 function uEditBuildAddrFields(patron, address) {
615 var tbody = $('ue_address_tbody');
617 var row = tbody.appendChild(
618 uEditAddrTemplate.cloneNode(true));
620 uEditCheckSharedAddr(patron, address, tbody, row);
622 $n(row, 'ue_addr_delete').onclick =
624 uEditDeleteAddr(tbody, row, address);
628 if( patron.billing_address() &&
629 address.id() == patron.billing_address().id() )
630 $n(row, 'ue_addr_billing_yes').checked = true;
632 if( patron.mailing_address() &&
633 address.id() == patron.mailing_address().id() )
634 $n(row, 'ue_addr_mailing_yes').checked = true;
636 $n(row, 'ue_addr_billing_yes').setAttribute('address', address.id());
637 $n(row, 'ue_addr_mailing_yes').setAttribute('address', address.id());
639 /* currently, non-owners cannot edit an address */
640 var disabled = (address.usr() != patron.id())
646 key : 'address_type',
649 name : 'ue_addr_label',
658 errkey : 'ue_bad_addr_street',
661 name : 'ue_addr_street1',
670 errkey : 'ue_bad_addr_street',
673 name : 'ue_addr_street2',
682 errkey : 'ue_bad_addr_city',
685 name : 'ue_addr_city',
696 name : 'ue_addr_county',
705 errkey : 'ue_bad_addr_state',
708 name : 'ue_addr_state',
717 errkey : 'ue_bad_addr_country',
720 name : 'ue_addr_country',
729 errkey : 'ue_bad_addr_zip',
732 name : 'ue_addr_zip',
736 onblur : function(f) {
737 var v = uEditNodeVal(f);
738 var req = new Request(ZIP_SEARCH, v);
741 var info = r.getResultObject();
743 var state = $n(f.widget.base, 'ue_addr_state');
744 var county = $n(f.widget.base, 'ue_addr_county');
745 var city = $n(f.widget.base, 'ue_addr_city');
746 state.value = info.state;
748 county.value = info.county;
750 city.value = info.city;
761 key : 'within_city_limits',
764 name : 'ue_addr_inc_yes',
775 name : 'ue_addr_valid_yes',
782 for( var f in fields ) {
783 dataFields.push(fields[f]);
784 uEditActivateField(fields[f]);
788 function uEditBuildPatronSCM(patron) {
789 /* get the list of pre-defined maps */
790 var fields = uEditFindFieldsByKey('stat_cat_entry');
794 /* for each user stat cat, pop it off the list,
795 updated the existing stat map field to match
796 the popped map and shove the existing stat
797 map field onto the user's list of stat maps */
798 while( (map = patron.stat_cat_entries().pop()) ) {
800 var field = grep(fields,
802 return (item.object.stat_cat() == map.stat_cat());
807 var val = map.stat_cat_entry();
809 $n(field.widget.base, field.widget.name).value = val;
810 setSelector($n(field.widget.base, 'ue_stat_cat_selector'), val );
811 field.object.stat_cat_entry(val);
812 field.object.id(map.id());
813 newmaps.push(field.object);
817 for( var m in newmaps )
818 patron.stat_cat_entries().push(newmaps[m]);
822 function uEditBuildSCMField(statcat, row) {
824 var map = new actscecm();
825 map.stat_cat(statcat.id());
826 map.target_usr(patron.id());
831 key : 'stat_cat_entry',
834 name : 'ue_stat_cat_newval',
837 onpostchange : function( field, newval ) {
839 /* see if the current map already resides in
840 the patron entry list */
841 var exists = grep( patron.stat_cat_entries(),
843 return (item.stat_cat() == statcat.id());
849 setSelector($n(row, 'ue_stat_cat_selector'), newval);
855 /* if the map is new but currently contains no value
856 remove it from the set of new maps */
858 patron.stat_cat_entries(
859 grep( patron.stat_cat_entries(),
861 return (item.stat_cat() != map.stat_cat());
874 /* map does not exist in the map array but now has data */
877 if(!patron.stat_cat_entries())
878 patron.stat_cat_entries([]);
879 patron.stat_cat_entries().push(map);
886 dataFields.push(field);
891 /** Run this after a new ident type is selected */
892 function _uEditIdentPostchange(type, field, newval) {
896 /* When the ident type is changed, we change the
897 regex on the ident_value to match the selected type */
898 var vfname = 'ident_value';
899 if(type == 'secondary') vfname = 'ident_value2';
900 var vfield = uEditFindFieldByKey(vfname);
901 var name = identTypesCache[uEditNodeVal(field)].name();
903 hideMe($(type+'_ident_ssn_help'));
904 hideMe($(type+'_ident_dl_help'));
906 if(name.match(/ssn/i)) {
907 vfield.widget.regex = ssnRegex;
908 vfield.errkey = 'ue_bad_ident_ssn';
909 unHideMe($(type+'_ident_ssn_help'));
913 if(name.match(/driver/i)) {
914 vfield.widget.regex = dlRegex;
915 vfield.errkey = 'ue_bad_ident_dl';
916 unHideMe($(type+'_ident_dl_help'));
917 if(!uEditNodeVal(vfield))
918 vfield.widget.node.value = defaultState + '-';
921 vfield.widget.regex = null;
922 vfield.errkey = null;
926 /* focus then valdate the value field */
927 vfield.widget.node.onchange();
928 vfield.widget.node.focus();
932 /* checks to see if the given address is shared by others.
933 * if so, the address row is styled and ...
936 function uEditCheckSharedAddr(patron, address, tbody, row) {
938 if( address.isnew() || (patron.isnew() && !clone) ) return;
940 var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
944 var members = r.getResultObject();
947 for( var m in members ) {
950 if( id != patron.id() ) {
952 addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
953 unHideMe($n(row, 'shared_row'));
954 $n(row, 'ue_addr_delete').disabled = true;
956 if( address.usr() != patron.id() ) {
957 var button = $n(row, 'ue_addr_detach');
960 function() { uEditDeleteAddr( tbody, row, address, true ); }
970 /* if this is a shared address, set the owner field and
971 give the staff a chance to edit the owner if it's not this user */
973 var nnode = $n(row, 'addr_owner_name');
974 var link = $n(row, 'addr_owner');
975 var id = address.usr();
977 if( id == patron.id() ) {
979 nnode.appendChild(text(
980 patron.first_given_name() + ' ' + patron.family_name()));
981 hideMe($n(row, 'owner_link_div'));
985 var ses = cgi.param('ses');
986 if (xulG) if (xulG.ses) ses = xulG.ses;
987 if (xulG) if (xulG.params) if (xulG.params.ses) ses = xulG.params.ses;
989 function() { window.xulG.spawn_editor({ses:ses,usr:id}) };
991 if( userCache[id] ) {
992 var usr = userCache[id];
993 nnode.appendChild(text(
994 usr.first_given_name() + ' ' + usr.family_name()));
998 fetchFleshedUser( id,
1000 userCache[usr.id()] = usr;
1001 nnode.appendChild(text(
1002 usr.first_given_name() + ' ' + usr.family_name()));
1018 function uEditCheckDOB(field) {
1020 var dob = uEditNodeVal(field);
1022 /* don't bother if the data isn't valid */
1023 if(!dob || !dob.match(field.widget.regex))
1026 if( dob == __lastdob ) return;
1030 var parts = dob.split(/-/);
1031 parts[2] = parts[2].replace(/[T ].*/,'');
1032 dob = buildDate( parts[0], parts[1], parts[2] );
1034 var today = new Date();
1036 if(!dob || dob > today) {
1037 addCSSClass(field.widget.node, CSS_INVALID_DATA);
1038 alertId('ue_bad_date');
1042 var base = new Date();
1043 base.setYear( today.getYear() + 1900 - ADULT_AGE );
1045 /* patron is at least 18 */
1047 var f = uEditFindFieldByKey('ident_value2');
1049 if( dob < base ) { /* patron is of age */
1051 hideMe(f.widget.node.parentNode.parentNode.parentNode);
1055 unHideMe(f.widget.node.parentNode.parentNode.parentNode);