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+'$');
162 if( pw1 ) field.required = true;
165 field.required = false;
173 key : 'first_given_name',
174 errkey : 'ue_bad_firstname',
179 onblur : function(field) {
180 uEditCheckNamesDup('first', field );
188 key : 'second_given_name',
189 errkey : 'ue_bad_middlename',
191 id : 'ue_middlename',
200 errkey : 'ue_bad_lastname',
205 onblur : function(field) {
206 uEditCheckNamesDup('last', field );
217 onload : function(val) {
218 setSelector($('ue_suffix_selector'), val);
219 $('ue_suffix_selector').onchange = function() {
220 uEditFindFieldByKey('suffix').widget.node.onchange();
229 errkey : 'ue_bad_dob',
234 onpostchange : function(field) { uEditCheckDOB(field); },
235 onblur : function(field) { uEditCheckDOB(field); }
242 errkey : 'ue_no_ident',
244 id : 'ue_primary_ident_type',
247 onpostchange : function(field, newval)
248 { _uEditIdentPostchange('primary', field, newval); }
256 id : 'ue_primary_ident',
258 onblur : function(field) {
259 uEditCheckIdentDup(field);
266 key : 'ident_value2',
268 id : 'ue_secondary_ident',
276 errkey : 'ue_bad_email',
280 regex : /.+\@.+\..+/, /* make me better */
281 onblur : function(field) {
282 var val = uEditNodeVal(field);
283 if( val && val != field.oldemail ) {
284 uEditRunDupeSearch('email',
285 { email : { value : val, group : 0 } });
286 field.oldemail = val;
295 errkey : 'ue_bad_phone',
305 key : 'evening_phone',
306 errkey : 'ue_bad_phone',
308 id : 'ue_night_phone',
317 errkey : 'ue_bad_phone',
319 id : 'ue_other_phone',
329 id : 'ue_org_selector',
338 errkey : 'ue_bad_expire',
361 onpostchange : function(field, val) {
362 var afield = uEditFindFieldByKey('alert_message');
364 if( !barredAlerted ) {
365 barredAlerted = true;
366 alertId('ue_made_barred');
368 afield.required = true;
370 afield.required = false;
379 errkey : 'ue_no_profile',
384 onpostchange : function(field, value) {
385 var type = groupsCache[value];
387 var interval = type.perm_interval();
389 /* interval_to_seconds expects 'M' for months, 'm' for minutes */
390 interval = interval.replace(/mon/, 'Mon');
391 var intsecs = parseInt(interval_to_seconds(interval));
393 var expdate = new Date();
394 var exptime = expdate.getTime();
395 exptime += intsecs * 1000;
396 expdate.setTime(exptime);
398 var year = expdate.getYear() + 1900;
399 var month = (expdate.getMonth() + 1) + '';
400 var day = (expdate.getDate()) + '';
402 if(!month.match(/\d{2}/)) month = '0' + month;
403 if(!day.match(/\d{2}/)) day = '0' + day;
405 var node = $('ue_expire');
406 node.value = year+'-'+month+'-'+day;
413 key : 'net_access_level',
422 key : 'master_account',
424 id : 'ue_group_lead',
431 key : 'claims_returned_count',
433 id : 'ue_claims_returned',
442 key : 'alert_message',
444 id : 'ue_alert_message',
450 for( var f in fields )
451 dataFields.push(fields[f]);
453 uEditBuildAddrs(patron);
454 uEditBuildPatronSCM(patron);
457 var uEditOldFirstName;
458 var uEditOldMiddleName; /* future */
459 var uEditOldLastName;
460 function uEditCheckNamesDup(type, field) {
461 var newval = uEditNodeVal(field);
464 var dosearch = false;
467 if( newval != uEditOldFirstName )
469 uEditOldFirstName = newval;
473 if( newval != uEditOldLastName )
475 uEditOldLastName = newval;
478 if( dosearch && uEditOldFirstName && uEditOldLastName ) {
479 var search_hash = {};
480 search_hash['first_given_name'] = { value : uEditOldFirstName, group : 0 };
481 search_hash['family_name'] = { value : uEditOldLastName, group : 0 };
482 uEditRunDupeSearch('names', search_hash);
486 var uEditOldIdentValue;
487 function uEditCheckIdentDup(field) {
488 var newval = uEditNodeVal(field);
489 if( newval && newval != uEditOldIdentValue ) {
490 /* searches all ident_value fields */
491 var search_hash = { ident : { value : newval, group : 2 } };
492 uEditRunDupeSearch('ident', search_hash);
493 uEditOldIdentValue = newval;
498 /* Adds all of the addresses attached to the patron object
499 to the fields array */
500 var uEditAddrTemplate;
501 function uEditBuildAddrs(patron) {
502 var tbody = $('ue_address_tbody');
503 if(!uEditAddrTemplate)
504 uEditAddrTemplate = tbody.removeChild($('ue_address_template'));
505 for( var a in patron.addresses() )
506 uEditBuildAddrFields( patron, patron.addresses()[a]);
510 function uEditDeleteAddr( tbody, row, address, detach ) {
511 if(!confirm($('ue_delete_addr_warn').innerHTML)) return;
512 if(address.isnew()) {
514 grep( patron.addresses(),
516 return (i.id() != address.id());
522 for( var f in dataFields ) {
523 if( dataFields[f].object == address ) {
524 dataFields[f] = null;
528 dataFields = compactArray(dataFields);
532 if( detach ) { /* remove the offending address from the list */
537 return (i.id() != address.id());
543 address.isdeleted(1);
547 tbody.removeChild(row);
549 var bid = patron.billing_address();
550 bid = (typeof bid == 'object') ? bid.id() : bid;
552 var mid = patron.mailing_address();
553 mid = (typeof mid == 'object') ? mid.id() : mid;
556 /* -----------------------------------------------------------------------
557 if we're deleting a billing or mailing address
558 make sure some other address is automatically
559 assigned as the billing or mailng address
560 ----------------------------------------------------------------------- */
562 if( bid == address.id() ) {
563 for( var a in patron.addresses() ) {
564 var addr = patron.addresses()[a];
565 if(!addr.isdeleted() && addr.id() != address.id()) {
566 var node = uEditFindAddrInput('billing', addr.id());
568 uEditAddrTypeClick(node, 'billing');
574 if( mid == address.id() ) {
575 for( var a in patron.addresses() ) {
576 var addr = patron.addresses()[a];
577 if(!addr.isdeleted() && addr.id() != address.id()) {
578 var node = uEditFindAddrInput('mailing', addr.id());
580 uEditAddrTypeClick(node, 'mailing');
589 function uEditFindAddrInput(type, id) {
590 var tbody = $('ue_address_tbody');
591 var rows = tbody.getElementsByTagName('tr');
592 for( var r in rows ) {
594 if(row.parentNode != tbody) continue;
595 var node = $n(row, 'ue_addr_'+type+'_yes');
596 if( node.getAttribute('address') == id )
602 function uEditAddrTypeClick(input, type) {
603 var tbody = $('ue_address_tbody');
604 var rows = tbody.getElementsByTagName('tr');
605 for( var r in rows ) {
607 if(row.parentNode != tbody) continue;
608 var node = $n(row, 'ue_addr_'+type+'_yes');
609 removeCSSClass(node.parentNode,'addr_info_checked');
612 addCSSClass(input.parentNode,'addr_info_checked');
613 patron[type+'_address'](input.getAttribute('address'));
620 /* Creates the field entries for an address object. */
621 function uEditBuildAddrFields(patron, address) {
623 var tbody = $('ue_address_tbody');
625 var row = tbody.appendChild(
626 uEditAddrTemplate.cloneNode(true));
628 uEditCheckSharedAddr(patron, address, tbody, row);
630 $n(row, 'ue_addr_delete').onclick =
631 function() { uEditDeleteAddr(tbody, row, address); }
633 if( patron.billing_address() &&
634 address.id() == patron.billing_address().id() )
635 $n(row, 'ue_addr_billing_yes').checked = true;
637 if( patron.mailing_address() &&
638 address.id() == patron.mailing_address().id() )
639 $n(row, 'ue_addr_mailing_yes').checked = true;
641 $n(row, 'ue_addr_billing_yes').setAttribute('address', address.id());
642 $n(row, 'ue_addr_mailing_yes').setAttribute('address', address.id());
644 /* currently, non-owners cannot edit an address */
645 var disabled = (address.usr() != patron.id())
651 key : 'address_type',
654 name : 'ue_addr_label',
663 errkey : 'ue_bad_addr_street',
666 name : 'ue_addr_street1',
675 errkey : 'ue_bad_addr_street',
678 name : 'ue_addr_street2',
687 errkey : 'ue_bad_addr_city',
690 name : 'ue_addr_city',
701 name : 'ue_addr_county',
710 errkey : 'ue_bad_addr_state',
713 name : 'ue_addr_state',
722 errkey : 'ue_bad_addr_country',
725 name : 'ue_addr_country',
734 errkey : 'ue_bad_addr_zip',
737 name : 'ue_addr_zip',
741 onblur : function(f) {
742 var v = uEditNodeVal(f);
743 var req = new Request(ZIP_SEARCH, v);
746 var info = r.getResultObject();
748 var state = $n(f.widget.base, 'ue_addr_state');
749 var county = $n(f.widget.base, 'ue_addr_county');
750 var city = $n(f.widget.base, 'ue_addr_city');
752 state.value = info.state;
756 county.value = info.county;
760 city.value = info.city;
772 key : 'within_city_limits',
775 name : 'ue_addr_inc_yes',
786 name : 'ue_addr_valid_yes',
793 for( var f in fields ) {
794 dataFields.push(fields[f]);
795 uEditActivateField(fields[f]);
799 function uEditBuildPatronSCM(patron) {
800 /* get the list of pre-defined maps */
801 var fields = uEditFindFieldsByKey('stat_cat_entry');
805 /* for each user stat cat, pop it off the list,
806 updated the existing stat map field to match
807 the popped map and shove the existing stat
808 map field onto the user's list of stat maps */
809 while( (map = patron.stat_cat_entries().pop()) ) {
811 var field = grep(fields,
813 return (item.object.stat_cat() == map.stat_cat());
818 var val = map.stat_cat_entry();
820 $n(field.widget.base, field.widget.name).value = val;
821 setSelector($n(field.widget.base, 'ue_stat_cat_selector'), val );
822 field.object.stat_cat_entry(val);
823 field.object.id(map.id());
824 newmaps.push(field.object);
828 for( var m in newmaps )
829 patron.stat_cat_entries().push(newmaps[m]);
833 function uEditBuildSCMField(statcat, row) {
835 var map = new actscecm();
836 map.stat_cat(statcat.id());
837 map.target_usr(patron.id());
842 key : 'stat_cat_entry',
845 name : 'ue_stat_cat_newval',
848 onpostchange : function( field, newval ) {
850 /* see if the current map already resides in
851 the patron entry list */
852 var exists = grep( patron.stat_cat_entries(),
854 return (item.stat_cat() == statcat.id());
860 setSelector($n(row, 'ue_stat_cat_selector'), newval);
866 /* if the map is new but currently contains no value
867 remove it from the set of new maps */
869 patron.stat_cat_entries(
870 grep( patron.stat_cat_entries(),
872 return (item.stat_cat() != map.stat_cat());
885 /* map does not exist in the map array but now has data */
888 patron.stat_cat_entries().push(map);
895 dataFields.push(field);
900 /** Run this after a new ident type is selected */
901 function _uEditIdentPostchange(type, field, newval) {
905 /* When the ident type is changed, we change the
906 regex on the ident_value to match the selected type */
907 var vfname = 'ident_value';
908 if(type == 'secondary') vfname = 'ident_value2';
909 var vfield = uEditFindFieldByKey(vfname);
910 var name = identTypesCache[uEditNodeVal(field)].name();
912 hideMe($(type+'_ident_ssn_help'));
913 hideMe($(type+'_ident_dl_help'));
915 if(name.match(/ssn/i)) {
916 vfield.widget.regex = ssnRegex;
917 vfield.errkey = 'ue_bad_ident_ssn';
918 unHideMe($(type+'_ident_ssn_help'));
922 if(name.match(/driver/i)) {
923 vfield.widget.regex = dlRegex;
924 vfield.errkey = 'ue_bad_ident_dl';
925 unHideMe($(type+'_ident_dl_help'));
926 if(!uEditNodeVal(vfield))
927 vfield.widget.node.value = defaultState + '-';
930 vfield.widget.regex = null;
931 vfield.errkey = null;
935 /* focus then valdate the value field */
936 vfield.widget.node.onchange();
937 vfield.widget.node.focus();
941 /* checks to see if the given address is shared by others.
942 * if so, the address row is styled and ...
945 function uEditCheckSharedAddr(patron, address, tbody, row) {
947 if( address.isnew() || (patron.isnew() && !clone) ) return;
949 var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
953 var members = r.getResultObject();
956 for( var m in members ) {
959 if( id != patron.id() ) {
961 addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
962 unHideMe($n(row, 'shared_row'));
963 $n(row, 'ue_addr_delete').disabled = true;
965 if( address.usr() != patron.id() ) {
966 var button = $n(row, 'ue_addr_detach');
969 function() { uEditDeleteAddr( tbody, row, address, true ); }
979 /* if this is a shared address, set the owner field and
980 give the staff a chance to edit the owner if it's not this user */
982 var nnode = $n(row, 'addr_owner_name');
983 var link = $n(row, 'addr_owner');
984 var id = address.usr();
986 if( id == patron.id() ) {
988 nnode.appendChild(text(
989 patron.first_given_name() + ' ' + patron.family_name()));
990 hideMe($n(row, 'owner_link_div'));
995 function() { window.xulG.spawn_editor({ses:cgi.param('ses'),usr:id}) };
997 if( userCache[id] ) {
998 nnode.appendChild(text(
999 usr.first_given_name() + ' ' + usr.family_name()));
1003 fetchFleshedUser( id,
1005 userCache[usr.id()] = usr;
1006 nnode.appendChild(text(
1007 usr.first_given_name() + ' ' + usr.family_name()));
1023 function uEditCheckDOB(field) {
1025 var dob = uEditNodeVal(field);
1027 /* don't bother if the data isn't valid */
1028 if(!dob || !dob.match(field.widget.regex))
1031 if( dob == __lastdob ) return;
1035 var parts = dob.split(/-/);
1036 parts[2] = parts[2].replace(/[T ].*/,'');
1037 dob = buildDate( parts[0], parts[1], parts[2] );
1039 var today = new Date();
1041 if(!dob || dob > today) {
1042 addCSSClass(field.widget.node, CSS_INVALID_DATA);
1043 alertId('ue_bad_date');
1047 var base = new Date();
1048 base.setYear( today.getYear() + 1900 - ADULT_AGE );
1050 /* patron is at least 18 */
1052 var f = uEditFindFieldByKey('ident_value2');
1054 if( dob < base ) { /* patron is of age */
1056 hideMe(f.widget.node.parentNode.parentNode.parentNode);
1060 unHideMe(f.widget.node.parentNode.parentNode.parentNode);