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 unameRegex = /^\w[\.\w\@-]*$/;
44 const ssnRegex = /^\d{3}-\d{2}-\d{4}$/;
45 const dlRegex = /^[a-zA-Z]{2}-\w+/; /* driver's license */
46 const phoneRegex = /^\d{3}-\d{3}-\d{4}(| ex\d+)$/i;
47 const nonumRegex = /^[a-zA-Z]\D*$/; /* no numbers, no beginning whitespace */
48 const dateRegex = /^\d{4}-\d{2}-\d{2}/;
49 const zipRegex = /^\d{5}(-\d{4}|$)/; /* 12345 or 12345-6789 */
51 var barredAlerted = false;
54 function uEditUsrnameBlur(field) {
55 var usrname = uEditNodeVal(field);
57 var req = new Request(CHECK_USERNAME, SESSION, usrname);
60 var res = r.getResultObject();
61 if( res && res != patron.id() ) {
62 field.widget.onblur = null; /* prevent alert storm */
63 alertId('ue_dup_username');
64 field.widget.onblur = uEditUsrnameBlur;
67 field.widget.node.focus();
68 field.widget.node.select();
78 function uEditBarcodeBlur(field) {
79 var barcode = uEditNodeVal(field);
81 _debug("blurring card with new value " + barcode);
82 var req = new Request(CHECK_BARCODE, SESSION, barcode);
85 var res = r.getResultObject();
86 if( res && res != patron.id() ) {
87 field.widget.onblur = null; /* prevent alert storm */
88 alertId('ue_dup_barcode');
89 field.widget.onblur = uEditBarcodeBlur;
92 field.widget.node.focus();
93 field.widget.node.select();
97 var node = uEditFindFieldByWId("ue_username");
98 if(!node.widget.node.value) {
99 node.widget.node.value = barcode;
100 node.widget.node.onchange();
109 function uEditDefineData(patron) {
114 object : patron.card(),
116 errkey : 'ue_bad_barcode',
121 onblur : uEditBarcodeBlur
128 errkey : 'ue_bad_username',
133 onblur : uEditUsrnameBlur
137 required : (patron.isnew()) ? true : false,
140 errkey : 'ue_bad_password',
144 onpostchange : function(field, newval) {
145 var pw2 = uEditFindFieldByWId('ue_password2');
146 /* tell the second passsword input to re-validate */
147 pw2.widget.node.onchange();
153 required : (patron.isnew()) ? true : false,
156 errkey : 'ue_bad_password',
160 onpostchange : function(field, newval) {
161 var pw1f = uEditFindFieldByWId('ue_password1');
162 var pw1 = uEditNodeVal(pw1f);
163 field.widget.regex = new RegExp('^'+pw1+'$');
164 if( pw1 ) field.required = true;
167 field.required = false;
175 key : 'first_given_name',
176 errkey : 'ue_bad_firstname',
181 onblur : function(field) {
182 uEditCheckNamesDup('first', field );
190 key : 'second_given_name',
191 errkey : 'ue_bad_middlename',
193 id : 'ue_middlename',
202 errkey : 'ue_bad_lastname',
207 onblur : function(field) {
208 uEditCheckNamesDup('last', field );
219 onload : function(val) {
220 setSelector($('ue_suffix_selector'), val);
221 $('ue_suffix_selector').onchange = function() {
222 uEditFindFieldByKey('suffix').widget.node.onchange();
231 errkey : 'ue_bad_dob',
236 onpostchange : function(field) { uEditCheckDOB(field); },
237 onblur : function(field) { uEditCheckDOB(field); }
244 errkey : 'ue_no_ident',
246 id : 'ue_primary_ident_type',
249 onpostchange : function(field, newval)
250 { _uEditIdentPostchange('primary', field, newval); }
258 id : 'ue_primary_ident',
260 onblur : function(field) {
261 uEditCheckIdentDup(field);
268 key : 'ident_value2',
270 id : 'ue_secondary_ident',
278 errkey : 'ue_bad_email',
282 regex : /.+\@.+\..+/, /* make me better */
283 onblur : function(field) {
284 var val = uEditNodeVal(field);
285 if( val && val != field.oldemail ) {
286 uEditRunDupeSearch('email',
287 { email : { value : val, group : 0 } });
288 field.oldemail = val;
297 errkey : 'ue_bad_phone',
307 key : 'evening_phone',
308 errkey : 'ue_bad_phone',
310 id : 'ue_night_phone',
319 errkey : 'ue_bad_phone',
321 id : 'ue_other_phone',
331 id : 'ue_org_selector',
340 errkey : 'ue_bad_expire',
363 onpostchange : function(field, val) {
364 var afield = uEditFindFieldByKey('alert_message');
366 if( !barredAlerted ) {
367 barredAlerted = true;
368 alertId('ue_made_barred');
370 afield.required = true;
372 afield.required = false;
381 errkey : 'ue_no_profile',
386 onpostchange : function(field, value) {
387 var type = groupsCache[value];
389 var interval = type.perm_interval();
391 /* interval_to_seconds expects 'M' for months, 'm' for minutes */
392 interval = interval.replace(/mon/, 'Mon');
393 var intsecs = parseInt(interval_to_seconds(interval));
395 var expdate = new Date();
396 var exptime = expdate.getTime();
397 exptime += intsecs * 1000;
398 expdate.setTime(exptime);
400 var year = expdate.getYear() + 1900;
401 var month = (expdate.getMonth() + 1) + '';
402 var day = (expdate.getDate()) + '';
404 if(!month.match(/\d{2}/)) month = '0' + month;
405 if(!day.match(/\d{2}/)) day = '0' + day;
407 var node = $('ue_expire');
408 node.value = year+'-'+month+'-'+day;
415 key : 'net_access_level',
424 key : 'master_account',
426 id : 'ue_group_lead',
433 key : 'claims_returned_count',
435 id : 'ue_claims_returned',
444 key : 'alert_message',
446 id : 'ue_alert_message',
452 for( var f in fields )
453 dataFields.push(fields[f]);
455 uEditBuildAddrs(patron);
456 uEditBuildPatronSCM(patron);
459 var uEditOldFirstName;
460 var uEditOldMiddleName; /* future */
461 var uEditOldLastName;
462 function uEditCheckNamesDup(type, field) {
463 var newval = uEditNodeVal(field);
466 var dosearch = false;
469 if( newval != uEditOldFirstName )
471 uEditOldFirstName = newval;
475 if( newval != uEditOldLastName )
477 uEditOldLastName = newval;
480 if( dosearch && uEditOldFirstName && uEditOldLastName ) {
481 var search_hash = {};
482 search_hash['first_given_name'] = { value : uEditOldFirstName, group : 0 };
483 search_hash['family_name'] = { value : uEditOldLastName, group : 0 };
484 uEditRunDupeSearch('names', search_hash);
488 var uEditOldIdentValue;
489 function uEditCheckIdentDup(field) {
490 var newval = uEditNodeVal(field);
491 if( newval && newval != uEditOldIdentValue ) {
492 /* searches all ident_value fields */
493 var search_hash = { ident : { value : newval, group : 2 } };
494 uEditRunDupeSearch('ident', search_hash);
495 uEditOldIdentValue = newval;
500 /* Adds all of the addresses attached to the patron object
501 to the fields array */
502 var uEditAddrTemplate;
503 function uEditBuildAddrs(patron) {
504 var tbody = $('ue_address_tbody');
505 if(!uEditAddrTemplate)
506 uEditAddrTemplate = tbody.removeChild($('ue_address_template'));
507 for( var a in patron.addresses() )
508 uEditBuildAddrFields( patron, patron.addresses()[a]);
512 function uEditDeleteAddr( tbody, row, address, detach ) {
513 if(!confirm($('ue_delete_addr_warn').innerHTML)) return;
514 if(address.isnew()) {
516 grep( patron.addresses(),
518 return (i.id() != address.id());
524 for( var f in dataFields ) {
525 if( dataFields[f].object == address ) {
526 dataFields[f] = null;
530 dataFields = compactArray(dataFields);
534 if( detach ) { /* remove the offending address from the list */
539 return (i.id() != address.id());
545 address.isdeleted(1);
549 tbody.removeChild(row);
551 var bid = patron.billing_address();
552 bid = (typeof bid == 'object') ? bid.id() : bid;
554 var mid = patron.mailing_address();
555 mid = (typeof mid == 'object') ? mid.id() : mid;
558 /* -----------------------------------------------------------------------
559 if we're deleting a billing or mailing address
560 make sure some other address is automatically
561 assigned as the billing or mailng address
562 ----------------------------------------------------------------------- */
564 if( bid == address.id() ) {
565 for( var a in patron.addresses() ) {
566 var addr = patron.addresses()[a];
567 if(!addr.isdeleted() && addr.id() != address.id()) {
568 var node = uEditFindAddrInput('billing', addr.id());
570 uEditAddrTypeClick(node, 'billing');
576 if( mid == address.id() ) {
577 for( var a in patron.addresses() ) {
578 var addr = patron.addresses()[a];
579 if(!addr.isdeleted() && addr.id() != address.id()) {
580 var node = uEditFindAddrInput('mailing', addr.id());
582 uEditAddrTypeClick(node, 'mailing');
591 function uEditFindAddrInput(type, id) {
592 var tbody = $('ue_address_tbody');
593 var rows = tbody.getElementsByTagName('tr');
594 for( var r in rows ) {
596 if(row.parentNode != tbody) continue;
597 var node = $n(row, 'ue_addr_'+type+'_yes');
598 if( node.getAttribute('address') == id )
604 function uEditAddrTypeClick(input, type) {
605 var tbody = $('ue_address_tbody');
606 var rows = tbody.getElementsByTagName('tr');
607 for( var r in rows ) {
609 if(row.parentNode != tbody) continue;
610 var node = $n(row, 'ue_addr_'+type+'_yes');
611 removeCSSClass(node.parentNode,'addr_info_checked');
614 addCSSClass(input.parentNode,'addr_info_checked');
615 patron[type+'_address'](input.getAttribute('address'));
622 /* Creates the field entries for an address object. */
623 function uEditBuildAddrFields(patron, address) {
625 var tbody = $('ue_address_tbody');
627 var row = tbody.appendChild(
628 uEditAddrTemplate.cloneNode(true));
630 uEditCheckSharedAddr(patron, address, tbody, row);
632 $n(row, 'ue_addr_delete').onclick =
633 function() { uEditDeleteAddr(tbody, row, address); }
635 if( patron.billing_address() &&
636 address.id() == patron.billing_address().id() )
637 $n(row, 'ue_addr_billing_yes').checked = true;
639 if( patron.mailing_address() &&
640 address.id() == patron.mailing_address().id() )
641 $n(row, 'ue_addr_mailing_yes').checked = true;
643 $n(row, 'ue_addr_billing_yes').setAttribute('address', address.id());
644 $n(row, 'ue_addr_mailing_yes').setAttribute('address', address.id());
646 /* currently, non-owners cannot edit an address */
647 var disabled = (address.usr() != patron.id())
653 key : 'address_type',
656 name : 'ue_addr_label',
665 errkey : 'ue_bad_addr_street',
668 name : 'ue_addr_street1',
677 errkey : 'ue_bad_addr_street',
680 name : 'ue_addr_street2',
689 errkey : 'ue_bad_addr_city',
692 name : 'ue_addr_city',
703 name : 'ue_addr_county',
712 errkey : 'ue_bad_addr_state',
715 name : 'ue_addr_state',
724 errkey : 'ue_bad_addr_country',
727 name : 'ue_addr_country',
736 errkey : 'ue_bad_addr_zip',
739 name : 'ue_addr_zip',
743 onblur : function(f) {
744 var v = uEditNodeVal(f);
745 var req = new Request(ZIP_SEARCH, v);
748 var info = r.getResultObject();
750 var state = $n(f.widget.base, 'ue_addr_state');
751 var county = $n(f.widget.base, 'ue_addr_county');
752 var city = $n(f.widget.base, 'ue_addr_city');
754 state.value = info.state;
758 county.value = info.county;
762 city.value = info.city;
774 key : 'within_city_limits',
777 name : 'ue_addr_inc_yes',
788 name : 'ue_addr_valid_yes',
795 for( var f in fields ) {
796 dataFields.push(fields[f]);
797 uEditActivateField(fields[f]);
801 function uEditBuildPatronSCM(patron) {
802 /* get the list of pre-defined maps */
803 var fields = uEditFindFieldsByKey('stat_cat_entry');
807 /* for each user stat cat, pop it off the list,
808 updated the existing stat map field to match
809 the popped map and shove the existing stat
810 map field onto the user's list of stat maps */
811 while( (map = patron.stat_cat_entries().pop()) ) {
813 var field = grep(fields,
815 return (item.object.stat_cat() == map.stat_cat());
820 var val = map.stat_cat_entry();
822 $n(field.widget.base, field.widget.name).value = val;
823 setSelector($n(field.widget.base, 'ue_stat_cat_selector'), val );
824 field.object.stat_cat_entry(val);
825 field.object.id(map.id());
826 newmaps.push(field.object);
830 for( var m in newmaps )
831 patron.stat_cat_entries().push(newmaps[m]);
835 function uEditBuildSCMField(statcat, row) {
837 var map = new actscecm();
838 map.stat_cat(statcat.id());
839 map.target_usr(patron.id());
844 key : 'stat_cat_entry',
847 name : 'ue_stat_cat_newval',
850 onpostchange : function( field, newval ) {
852 /* see if the current map already resides in
853 the patron entry list */
854 var exists = grep( patron.stat_cat_entries(),
856 return (item.stat_cat() == statcat.id());
862 setSelector($n(row, 'ue_stat_cat_selector'), newval);
868 /* if the map is new but currently contains no value
869 remove it from the set of new maps */
871 patron.stat_cat_entries(
872 grep( patron.stat_cat_entries(),
874 return (item.stat_cat() != map.stat_cat());
887 /* map does not exist in the map array but now has data */
890 patron.stat_cat_entries().push(map);
897 dataFields.push(field);
902 /** Run this after a new ident type is selected */
903 function _uEditIdentPostchange(type, field, newval) {
907 /* When the ident type is changed, we change the
908 regex on the ident_value to match the selected type */
909 var vfname = 'ident_value';
910 if(type == 'secondary') vfname = 'ident_value2';
911 var vfield = uEditFindFieldByKey(vfname);
912 var name = identTypesCache[uEditNodeVal(field)].name();
914 hideMe($(type+'_ident_ssn_help'));
915 hideMe($(type+'_ident_dl_help'));
917 if(name.match(/ssn/i)) {
918 vfield.widget.regex = ssnRegex;
919 vfield.errkey = 'ue_bad_ident_ssn';
920 unHideMe($(type+'_ident_ssn_help'));
924 if(name.match(/driver/i)) {
925 vfield.widget.regex = dlRegex;
926 vfield.errkey = 'ue_bad_ident_dl';
927 unHideMe($(type+'_ident_dl_help'));
928 if(!uEditNodeVal(vfield))
929 vfield.widget.node.value = defaultState + '-';
932 vfield.widget.regex = null;
933 vfield.errkey = null;
937 /* focus then valdate the value field */
938 vfield.widget.node.onchange();
939 vfield.widget.node.focus();
943 /* checks to see if the given address is shared by others.
944 * if so, the address row is styled and ...
947 function uEditCheckSharedAddr(patron, address, tbody, row) {
949 if( address.isnew() || (patron.isnew() && !clone) ) return;
951 var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
955 var members = r.getResultObject();
958 for( var m in members ) {
961 if( id != patron.id() ) {
963 addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
964 unHideMe($n(row, 'shared_row'));
965 $n(row, 'ue_addr_delete').disabled = true;
967 if( address.usr() != patron.id() ) {
968 var button = $n(row, 'ue_addr_detach');
971 function() { uEditDeleteAddr( tbody, row, address, true ); }
981 /* if this is a shared address, set the owner field and
982 give the staff a chance to edit the owner if it's not this user */
984 var nnode = $n(row, 'addr_owner_name');
985 var link = $n(row, 'addr_owner');
986 var id = address.usr();
988 if( id == patron.id() ) {
990 nnode.appendChild(text(
991 patron.first_given_name() + ' ' + patron.family_name()));
992 hideMe($n(row, 'owner_link_div'));
997 function() { window.xulG.spawn_editor({ses:cgi.param('ses'),usr:id}) };
999 if( userCache[id] ) {
1000 nnode.appendChild(text(
1001 usr.first_given_name() + ' ' + usr.family_name()));
1005 fetchFleshedUser( id,
1007 userCache[usr.id()] = usr;
1008 nnode.appendChild(text(
1009 usr.first_given_name() + ' ' + usr.family_name()));
1025 function uEditCheckDOB(field) {
1027 var dob = uEditNodeVal(field);
1029 /* don't bother if the data isn't valid */
1030 if(!dob || !dob.match(field.widget.regex))
1033 if( dob == __lastdob ) return;
1037 var parts = dob.split(/-/);
1038 parts[2] = parts[2].replace(/[T ].*/,'');
1039 dob = buildDate( parts[0], parts[1], parts[2] );
1041 var today = new Date();
1043 if(!dob || dob > today) {
1044 addCSSClass(field.widget.node, CSS_INVALID_DATA);
1045 alertId('ue_bad_date');
1049 var base = new Date();
1050 base.setYear( today.getYear() + 1900 - ADULT_AGE );
1052 /* patron is at least 18 */
1054 var f = uEditFindFieldByKey('ident_value2');
1056 if( dob < base ) { /* patron is of age */
1058 hideMe(f.widget.node.parentNode.parentNode.parentNode);
1062 unHideMe(f.widget.node.parentNode.parentNode.parentNode);