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+'$');
168 key : 'first_given_name',
169 errkey : 'ue_bad_firstname',
174 onblur : function(field) {
175 uEditCheckNamesDup('first', field );
183 key : 'second_given_name',
184 errkey : 'ue_bad_middlename',
186 id : 'ue_middlename',
195 errkey : 'ue_bad_lastname',
200 onblur : function(field) {
201 uEditCheckNamesDup('last', field );
212 onload : function(val) {
213 setSelector($('ue_suffix_selector'), val);
214 $('ue_suffix_selector').onchange = function() {
215 uEditFindFieldByKey('suffix').widget.node.onchange();
224 errkey : 'ue_bad_dob',
229 onpostchange : function(field) { uEditCheckDOB(field); },
230 onblur : function(field) { uEditCheckDOB(field); }
237 errkey : 'ue_no_ident',
239 id : 'ue_primary_ident_type',
242 onpostchange : function(field, newval)
243 { _uEditIdentPostchange('primary', field, newval); }
251 id : 'ue_primary_ident',
253 onblur : function(field) {
254 uEditCheckIdentDup(field);
261 key : 'ident_value2',
263 id : 'ue_secondary_ident',
271 errkey : 'ue_bad_email',
275 regex : /.+\@.+\..+/, /* make me better */
276 onblur : function(field) {
277 var val = uEditNodeVal(field);
278 if( val && val != field.oldemail ) {
279 uEditRunDupeSearch('email',
280 { email : { value : val, group : 0 } });
281 field.oldemail = val;
290 errkey : 'ue_bad_phone',
300 key : 'evening_phone',
301 errkey : 'ue_bad_phone',
303 id : 'ue_night_phone',
312 errkey : 'ue_bad_phone',
314 id : 'ue_other_phone',
324 id : 'ue_org_selector',
333 errkey : 'ue_bad_expire',
356 onpostchange : function(field, val) {
357 var afield = uEditFindFieldByKey('alert_message');
359 if( !barredAlerted ) {
360 barredAlerted = true;
361 alertId('ue_made_barred');
363 afield.required = true;
365 afield.required = false;
374 errkey : 'ue_no_profile',
379 onpostchange : function(field, value) {
380 var type = groupsCache[value];
382 var interval = type.perm_interval();
384 /* interval_to_seconds expects 'M' for months, 'm' for minutes */
385 interval = interval.replace(/mon/, 'Mon');
386 var intsecs = parseInt(interval_to_seconds(interval));
388 var expdate = new Date();
389 var exptime = expdate.getTime();
390 exptime += intsecs * 1000;
391 expdate.setTime(exptime);
393 var year = expdate.getYear() + 1900;
394 var month = (expdate.getMonth() + 1) + '';
395 var day = (expdate.getDate()) + '';
397 if(!month.match(/\d{2}/)) month = '0' + month;
398 if(!day.match(/\d{2}/)) day = '0' + day;
400 var node = $('ue_expire');
401 node.value = year+'-'+month+'-'+day;
408 key : 'net_access_level',
417 key : 'master_account',
419 id : 'ue_group_lead',
426 key : 'claims_returned_count',
428 id : 'ue_claims_returned',
436 key : 'alert_message',
438 id : 'ue_alert_message',
444 for( var f in fields )
445 dataFields.push(fields[f]);
447 uEditBuildAddrs(patron);
448 uEditBuildPatronSCM(patron);
451 var uEditOldFirstName;
452 var uEditOldMiddleName; /* future */
453 var uEditOldLastName;
454 function uEditCheckNamesDup(type, field) {
455 var newval = uEditNodeVal(field);
458 var dosearch = false;
461 if( newval != uEditOldFirstName )
463 uEditOldFirstName = newval;
467 if( newval != uEditOldLastName )
469 uEditOldLastName = newval;
472 if( dosearch && uEditOldFirstName && uEditOldLastName ) {
473 var search_hash = {};
474 search_hash['first_given_name'] = { value : uEditOldFirstName, group : 0 };
475 search_hash['family_name'] = { value : uEditOldLastName, group : 0 };
476 uEditRunDupeSearch('names', search_hash);
480 var uEditOldIdentValue;
481 function uEditCheckIdentDup(field) {
482 var newval = uEditNodeVal(field);
483 if( newval && newval != uEditOldIdentValue ) {
484 /* searches all ident_value fields */
485 var search_hash = { ident : { value : newval, group : 2 } };
486 uEditRunDupeSearch('ident', search_hash);
487 uEditOldIdentValue = newval;
492 /* Adds all of the addresses attached to the patron object
493 to the fields array */
494 var uEditAddrTemplate;
495 function uEditBuildAddrs(patron) {
496 var tbody = $('ue_address_tbody');
497 if(!uEditAddrTemplate)
498 uEditAddrTemplate = tbody.removeChild($('ue_address_template'));
499 for( var a in patron.addresses() )
500 uEditBuildAddrFields( patron, patron.addresses()[a]);
504 function uEditDeleteAddr( tbody, row, address, detach ) {
505 if(!confirm($('ue_delete_addr_warn').innerHTML)) return;
506 if(address.isnew()) {
508 grep( patron.addresses(),
510 return (i.id() != address.id());
516 for( var f in dataFields ) {
517 if( dataFields[f].object == address ) {
518 dataFields[f] = null;
522 dataFields = compactArray(dataFields);
526 if( detach ) { /* remove the offending address from the list */
531 return (i.id() != address.id());
537 address.isdeleted(1);
541 tbody.removeChild(row);
543 var bid = patron.billing_address();
544 bid = (typeof bid == 'object') ? bid.id() : bid;
546 var mid = patron.mailing_address();
547 mid = (typeof mid == 'object') ? mid.id() : mid;
550 /* -----------------------------------------------------------------------
551 if we're deleting a billing or mailing address
552 make sure some other address is automatically
553 assigned as the billing or mailng address
554 ----------------------------------------------------------------------- */
556 if( bid == address.id() ) {
557 for( var a in patron.addresses() ) {
558 var addr = patron.addresses()[a];
559 if(!addr.isdeleted() && addr.id() != address.id()) {
560 var node = uEditFindAddrInput('billing', addr.id());
562 uEditAddrTypeClick(node, 'billing');
568 if( mid == address.id() ) {
569 for( var a in patron.addresses() ) {
570 var addr = patron.addresses()[a];
571 if(!addr.isdeleted() && addr.id() != address.id()) {
572 var node = uEditFindAddrInput('mailing', addr.id());
574 uEditAddrTypeClick(node, 'mailing');
583 function uEditFindAddrInput(type, id) {
584 var tbody = $('ue_address_tbody');
585 var rows = tbody.getElementsByTagName('tr');
586 for( var r in rows ) {
588 if(row.parentNode != tbody) continue;
589 var node = $n(row, 'ue_addr_'+type+'_yes');
590 if( node.getAttribute('address') == id )
596 function uEditAddrTypeClick(input, type) {
597 var tbody = $('ue_address_tbody');
598 var rows = tbody.getElementsByTagName('tr');
599 for( var r in rows ) {
601 if(row.parentNode != tbody) continue;
602 var node = $n(row, 'ue_addr_'+type+'_yes');
603 removeCSSClass(node.parentNode,'addr_info_checked');
606 addCSSClass(input.parentNode,'addr_info_checked');
607 patron[type+'_address'](input.getAttribute('address'));
614 /* Creates the field entries for an address object. */
615 function uEditBuildAddrFields(patron, address) {
617 var tbody = $('ue_address_tbody');
619 var row = tbody.appendChild(
620 uEditAddrTemplate.cloneNode(true));
622 uEditCheckSharedAddr(patron, address, tbody, row);
624 $n(row, 'ue_addr_delete').onclick =
625 function() { uEditDeleteAddr(tbody, row, address); }
627 if( patron.billing_address() &&
628 address.id() == patron.billing_address().id() )
629 $n(row, 'ue_addr_billing_yes').checked = true;
631 if( patron.mailing_address() &&
632 address.id() == patron.mailing_address().id() )
633 $n(row, 'ue_addr_mailing_yes').checked = true;
635 $n(row, 'ue_addr_billing_yes').setAttribute('address', address.id());
636 $n(row, 'ue_addr_mailing_yes').setAttribute('address', address.id());
638 /* currently, non-owners cannot edit an address */
639 var disabled = (address.usr() != patron.id())
645 key : 'address_type',
648 name : 'ue_addr_label',
657 errkey : 'ue_bad_addr_street',
660 name : 'ue_addr_street1',
669 errkey : 'ue_bad_addr_street',
672 name : 'ue_addr_street2',
681 errkey : 'ue_bad_addr_city',
684 name : 'ue_addr_city',
695 name : 'ue_addr_county',
704 errkey : 'ue_bad_addr_state',
707 name : 'ue_addr_state',
716 errkey : 'ue_bad_addr_country',
719 name : 'ue_addr_country',
728 errkey : 'ue_bad_addr_zip',
731 name : 'ue_addr_zip',
735 onblur : function(f) {
736 var v = uEditNodeVal(f);
737 var req = new Request(ZIP_SEARCH, v);
740 var info = r.getResultObject();
742 var state = $n(f.widget.base, 'ue_addr_state');
743 var county = $n(f.widget.base, 'ue_addr_county');
744 var city = $n(f.widget.base, 'ue_addr_city');
746 state.value = info.state;
750 county.value = info.county;
754 city.value = info.city;
766 key : 'within_city_limits',
769 name : 'ue_addr_inc_yes',
780 name : 'ue_addr_valid_yes',
787 for( var f in fields ) {
788 dataFields.push(fields[f]);
789 uEditActivateField(fields[f]);
793 function uEditBuildPatronSCM(patron) {
794 /* get the list of pre-defined maps */
795 var fields = uEditFindFieldsByKey('stat_cat_entry');
799 /* for each user stat cat, pop it off the list,
800 updated the existing stat map field to match
801 the popped map and shove the existing stat
802 map field onto the user's list of stat maps */
803 while( (map = patron.stat_cat_entries().pop()) ) {
805 var field = grep(fields,
807 return (item.object.stat_cat() == map.stat_cat());
812 var val = map.stat_cat_entry();
814 $n(field.widget.base, field.widget.name).value = val;
815 setSelector($n(field.widget.base, 'ue_stat_cat_selector'), val );
816 field.object.stat_cat_entry(val);
817 field.object.id(map.id());
818 newmaps.push(field.object);
822 for( var m in newmaps )
823 patron.stat_cat_entries().push(newmaps[m]);
827 function uEditBuildSCMField(statcat, row) {
829 var map = new actscecm();
830 map.stat_cat(statcat.id());
831 map.target_usr(patron.id());
836 key : 'stat_cat_entry',
839 name : 'ue_stat_cat_newval',
842 onpostchange : function( field, newval ) {
844 /* see if the current map already resides in
845 the patron entry list */
846 var exists = grep( patron.stat_cat_entries(),
848 return (item.stat_cat() == statcat.id());
854 setSelector($n(row, 'ue_stat_cat_selector'), newval);
860 /* if the map is new but currently contains no value
861 remove it from the set of new maps */
863 patron.stat_cat_entries(
864 grep( patron.stat_cat_entries(),
866 return (item.stat_cat() != map.stat_cat());
879 /* map does not exist in the map array but now has data */
882 patron.stat_cat_entries().push(map);
889 dataFields.push(field);
894 /** Run this after a new ident type is selected */
895 function _uEditIdentPostchange(type, field, newval) {
899 /* When the ident type is changed, we change the
900 regex on the ident_value to match the selected type */
901 var vfname = 'ident_value';
902 if(type == 'secondary') vfname = 'ident_value2';
903 var vfield = uEditFindFieldByKey(vfname);
904 var name = identTypesCache[uEditNodeVal(field)].name();
906 hideMe($(type+'_ident_ssn_help'));
907 hideMe($(type+'_ident_dl_help'));
909 if(name.match(/ssn/i)) {
910 vfield.widget.regex = ssnRegex;
911 vfield.errkey = 'ue_bad_ident_ssn';
912 unHideMe($(type+'_ident_ssn_help'));
916 if(name.match(/driver/i)) {
917 vfield.widget.regex = dlRegex;
918 vfield.errkey = 'ue_bad_ident_dl';
919 unHideMe($(type+'_ident_dl_help'));
920 if(!uEditNodeVal(vfield))
921 vfield.widget.node.value = defaultState + '-';
924 vfield.widget.regex = null;
925 vfield.errkey = null;
929 /* focus then valdate the value field */
930 vfield.widget.node.onchange();
931 vfield.widget.node.focus();
935 /* checks to see if the given address is shared by others.
936 * if so, the address row is styled and ...
939 function uEditCheckSharedAddr(patron, address, tbody, row) {
941 if( address.isnew() || (patron.isnew() && !clone) ) return;
943 var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
947 var members = r.getResultObject();
950 for( var m in members ) {
953 if( id != patron.id() ) {
955 addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
956 unHideMe($n(row, 'shared_row'));
957 $n(row, 'ue_addr_delete').disabled = true;
959 if( address.usr() != patron.id() ) {
960 var button = $n(row, 'ue_addr_detach');
963 function() { uEditDeleteAddr( tbody, row, address, true ); }
973 /* if this is a shared address, set the owner field and
974 give the staff a chance to edit the owner if it's not this user */
976 var nnode = $n(row, 'addr_owner_name');
977 var link = $n(row, 'addr_owner');
978 var id = address.usr();
980 if( id == patron.id() ) {
982 nnode.appendChild(text(
983 patron.first_given_name() + ' ' + patron.family_name()));
984 hideMe($n(row, 'owner_link_div'));
989 function() { window.xulG.spawn_editor({ses:cgi.param('ses'),usr:id}) };
991 if( userCache[id] ) {
992 nnode.appendChild(text(
993 usr.first_given_name() + ' ' + usr.family_name()));
997 fetchFleshedUser( id,
999 userCache[usr.id()] = usr;
1000 nnode.appendChild(text(
1001 usr.first_given_name() + ' ' + usr.family_name()));
1017 function uEditCheckDOB(field) {
1019 var dob = uEditNodeVal(field);
1021 /* don't bother if the data isn't valid */
1022 if(!dob || !dob.match(field.widget.regex))
1025 if( dob == __lastdob ) return;
1029 var parts = dob.split(/-/);
1030 parts[2] = parts[2].replace(/[T ].*/,'');
1031 dob = buildDate( parts[0], parts[1], parts[2] );
1033 var today = new Date();
1035 if(!dob || dob > today) {
1036 addCSSClass(field.widget.node, CSS_INVALID_DATA);
1037 alertId('ue_bad_date');
1041 var base = new Date();
1042 base.setYear( today.getYear() + 1900 - ADULT_AGE );
1044 /* patron is at least 18 */
1046 var f = uEditFindFieldByKey('ident_value2');
1048 if( dob < base ) { /* patron is of age */
1050 hideMe(f.widget.node.parentNode.parentNode.parentNode);
1054 unHideMe(f.widget.node.parentNode.parentNode.parentNode);