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 =
641 uEditDeleteAddr(tbody, row, address);
645 if( patron.billing_address() &&
646 address.id() == patron.billing_address().id() )
647 $n(row, 'ue_addr_billing_yes').checked = true;
649 if( patron.mailing_address() &&
650 address.id() == patron.mailing_address().id() )
651 $n(row, 'ue_addr_mailing_yes').checked = true;
653 $n(row, 'ue_addr_billing_yes').setAttribute('address', address.id());
654 $n(row, 'ue_addr_mailing_yes').setAttribute('address', address.id());
656 /* currently, non-owners cannot edit an address */
657 var disabled = (address.usr() != patron.id())
663 key : 'address_type',
666 name : 'ue_addr_label',
675 errkey : 'ue_bad_addr_street',
678 name : 'ue_addr_street1',
687 errkey : 'ue_bad_addr_street',
690 name : 'ue_addr_street2',
699 errkey : 'ue_bad_addr_city',
702 name : 'ue_addr_city',
713 name : 'ue_addr_county',
722 errkey : 'ue_bad_addr_state',
725 name : 'ue_addr_state',
734 errkey : 'ue_bad_addr_country',
737 name : 'ue_addr_country',
746 errkey : 'ue_bad_addr_zip',
749 name : 'ue_addr_zip',
753 onblur : function(f) {
754 var v = uEditNodeVal(f);
755 var req = new Request(ZIP_SEARCH, v);
758 var info = r.getResultObject();
760 var state = $n(f.widget.base, 'ue_addr_state');
761 var county = $n(f.widget.base, 'ue_addr_county');
762 var city = $n(f.widget.base, 'ue_addr_city');
764 state.value = info.state;
768 county.value = info.county;
772 city.value = info.city;
784 key : 'within_city_limits',
787 name : 'ue_addr_inc_yes',
798 name : 'ue_addr_valid_yes',
805 for( var f in fields ) {
806 dataFields.push(fields[f]);
807 uEditActivateField(fields[f]);
811 function uEditBuildPatronSCM(patron) {
812 /* get the list of pre-defined maps */
813 var fields = uEditFindFieldsByKey('stat_cat_entry');
817 /* for each user stat cat, pop it off the list,
818 updated the existing stat map field to match
819 the popped map and shove the existing stat
820 map field onto the user's list of stat maps */
821 while( (map = patron.stat_cat_entries().pop()) ) {
823 var field = grep(fields,
825 return (item.object.stat_cat() == map.stat_cat());
830 var val = map.stat_cat_entry();
832 $n(field.widget.base, field.widget.name).value = val;
833 setSelector($n(field.widget.base, 'ue_stat_cat_selector'), val );
834 field.object.stat_cat_entry(val);
835 field.object.id(map.id());
836 newmaps.push(field.object);
840 for( var m in newmaps )
841 patron.stat_cat_entries().push(newmaps[m]);
845 function uEditBuildSCMField(statcat, row) {
847 var map = new actscecm();
848 map.stat_cat(statcat.id());
849 map.target_usr(patron.id());
854 key : 'stat_cat_entry',
857 name : 'ue_stat_cat_newval',
860 onpostchange : function( field, newval ) {
862 /* see if the current map already resides in
863 the patron entry list */
864 var exists = grep( patron.stat_cat_entries(),
866 return (item.stat_cat() == statcat.id());
872 setSelector($n(row, 'ue_stat_cat_selector'), newval);
878 /* if the map is new but currently contains no value
879 remove it from the set of new maps */
881 patron.stat_cat_entries(
882 grep( patron.stat_cat_entries(),
884 return (item.stat_cat() != map.stat_cat());
897 /* map does not exist in the map array but now has data */
900 if(!patron.stat_cat_entries())
901 patron.stat_cat_entries([]);
902 patron.stat_cat_entries().push(map);
909 dataFields.push(field);
914 /** Run this after a new ident type is selected */
915 function _uEditIdentPostchange(type, field, newval) {
919 /* When the ident type is changed, we change the
920 regex on the ident_value to match the selected type */
921 var vfname = 'ident_value';
922 if(type == 'secondary') vfname = 'ident_value2';
923 var vfield = uEditFindFieldByKey(vfname);
924 var name = identTypesCache[uEditNodeVal(field)].name();
926 hideMe($(type+'_ident_ssn_help'));
927 hideMe($(type+'_ident_dl_help'));
929 if(name.match(/ssn/i)) {
930 vfield.widget.regex = ssnRegex;
931 vfield.errkey = 'ue_bad_ident_ssn';
932 unHideMe($(type+'_ident_ssn_help'));
936 if(name.match(/driver/i)) {
937 vfield.widget.regex = dlRegex;
938 vfield.errkey = 'ue_bad_ident_dl';
939 unHideMe($(type+'_ident_dl_help'));
940 if(!uEditNodeVal(vfield))
941 vfield.widget.node.value = defaultState + '-';
944 vfield.widget.regex = null;
945 vfield.errkey = null;
949 /* focus then valdate the value field */
950 vfield.widget.node.onchange();
951 vfield.widget.node.focus();
955 /* checks to see if the given address is shared by others.
956 * if so, the address row is styled and ...
959 function uEditCheckSharedAddr(patron, address, tbody, row) {
961 if( address.isnew() || (patron.isnew() && !clone) ) return;
963 var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
967 var members = r.getResultObject();
970 for( var m in members ) {
973 if( id != patron.id() ) {
975 addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
976 unHideMe($n(row, 'shared_row'));
977 $n(row, 'ue_addr_delete').disabled = true;
979 if( address.usr() != patron.id() ) {
980 var button = $n(row, 'ue_addr_detach');
983 function() { uEditDeleteAddr( tbody, row, address, true ); }
993 /* if this is a shared address, set the owner field and
994 give the staff a chance to edit the owner if it's not this user */
996 var nnode = $n(row, 'addr_owner_name');
997 var link = $n(row, 'addr_owner');
998 var id = address.usr();
1000 if( id == patron.id() ) {
1002 nnode.appendChild(text(
1003 patron.first_given_name() + ' ' + patron.family_name()));
1004 hideMe($n(row, 'owner_link_div'));
1009 function() { window.xulG.spawn_editor({ses:cgi.param('ses'),usr:id}) };
1011 if( userCache[id] ) {
1012 var usr = userCache[id];
1013 nnode.appendChild(text(
1014 usr.first_given_name() + ' ' + usr.family_name()));
1018 fetchFleshedUser( id,
1020 userCache[usr.id()] = usr;
1021 nnode.appendChild(text(
1022 usr.first_given_name() + ' ' + usr.family_name()));
1038 function uEditCheckDOB(field) {
1040 var dob = uEditNodeVal(field);
1042 /* don't bother if the data isn't valid */
1043 if(!dob || !dob.match(field.widget.regex))
1046 if( dob == __lastdob ) return;
1050 var parts = dob.split(/-/);
1051 parts[2] = parts[2].replace(/[T ].*/,'');
1052 dob = buildDate( parts[0], parts[1], parts[2] );
1054 var today = new Date();
1056 if(!dob || dob > today) {
1057 addCSSClass(field.widget.node, CSS_INVALID_DATA);
1058 alertId('ue_bad_date');
1062 var base = new Date();
1063 base.setYear( today.getYear() + 1900 - ADULT_AGE );
1065 /* patron is at least 18 */
1067 var f = uEditFindFieldByKey('ident_value2');
1069 if( dob < base ) { /* patron is of age */
1071 hideMe(f.widget.node.parentNode.parentNode.parentNode);
1075 unHideMe(f.widget.node.parentNode.parentNode.parentNode);