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');
763 state.value = info.state;
765 county.value = info.county;
767 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 if(!patron.stat_cat_entries())
895 patron.stat_cat_entries([]);
896 patron.stat_cat_entries().push(map);
903 dataFields.push(field);
908 /** Run this after a new ident type is selected */
909 function _uEditIdentPostchange(type, field, newval) {
913 /* When the ident type is changed, we change the
914 regex on the ident_value to match the selected type */
915 var vfname = 'ident_value';
916 if(type == 'secondary') vfname = 'ident_value2';
917 var vfield = uEditFindFieldByKey(vfname);
918 var name = identTypesCache[uEditNodeVal(field)].name();
920 hideMe($(type+'_ident_ssn_help'));
921 hideMe($(type+'_ident_dl_help'));
923 if(name.match(/ssn/i)) {
924 vfield.widget.regex = ssnRegex;
925 vfield.errkey = 'ue_bad_ident_ssn';
926 unHideMe($(type+'_ident_ssn_help'));
930 if(name.match(/driver/i)) {
931 vfield.widget.regex = dlRegex;
932 vfield.errkey = 'ue_bad_ident_dl';
933 unHideMe($(type+'_ident_dl_help'));
934 if(!uEditNodeVal(vfield))
935 vfield.widget.node.value = defaultState + '-';
938 vfield.widget.regex = null;
939 vfield.errkey = null;
943 /* focus then valdate the value field */
944 vfield.widget.node.onchange();
945 vfield.widget.node.focus();
949 /* checks to see if the given address is shared by others.
950 * if so, the address row is styled and ...
953 function uEditCheckSharedAddr(patron, address, tbody, row) {
955 if( address.isnew() || (patron.isnew() && !clone) ) return;
957 var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
961 var members = r.getResultObject();
964 for( var m in members ) {
967 if( id != patron.id() ) {
969 addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
970 unHideMe($n(row, 'shared_row'));
971 $n(row, 'ue_addr_delete').disabled = true;
973 if( address.usr() != patron.id() ) {
974 var button = $n(row, 'ue_addr_detach');
977 function() { uEditDeleteAddr( tbody, row, address, true ); }
987 /* if this is a shared address, set the owner field and
988 give the staff a chance to edit the owner if it's not this user */
990 var nnode = $n(row, 'addr_owner_name');
991 var link = $n(row, 'addr_owner');
992 var id = address.usr();
994 if( id == patron.id() ) {
996 nnode.appendChild(text(
997 patron.first_given_name() + ' ' + patron.family_name()));
998 hideMe($n(row, 'owner_link_div'));
1003 function() { window.xulG.spawn_editor({ses:cgi.param('ses'),usr:id}) };
1005 if( userCache[id] ) {
1006 var usr = 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 parts[2] = parts[2].replace(/[T ].*/,'');
1046 dob = buildDate( parts[0], parts[1], parts[2] );
1048 var today = new Date();
1050 if(!dob || dob > today) {
1051 addCSSClass(field.widget.node, CSS_INVALID_DATA);
1052 alertId('ue_bad_date');
1056 var base = new Date();
1057 base.setYear( today.getYear() + 1900 - ADULT_AGE );
1059 /* patron is at least 18 */
1061 var f = uEditFindFieldByKey('ident_value2');
1063 if( dob < base ) { /* patron is of age */
1065 hideMe(f.widget.node.parentNode.parentNode.parentNode);
1069 unHideMe(f.widget.node.parentNode.parentNode.parentNode);