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',
437 key : 'alert_message',
439 id : 'ue_alert_message',
445 for( var f in fields )
446 dataFields.push(fields[f]);
448 uEditBuildAddrs(patron);
449 uEditBuildPatronSCM(patron);
452 var uEditOldFirstName;
453 var uEditOldMiddleName; /* future */
454 var uEditOldLastName;
455 function uEditCheckNamesDup(type, field) {
456 var newval = uEditNodeVal(field);
459 var dosearch = false;
462 if( newval != uEditOldFirstName )
464 uEditOldFirstName = newval;
468 if( newval != uEditOldLastName )
470 uEditOldLastName = newval;
473 if( dosearch && uEditOldFirstName && uEditOldLastName ) {
474 var search_hash = {};
475 search_hash['first_given_name'] = { value : uEditOldFirstName, group : 0 };
476 search_hash['family_name'] = { value : uEditOldLastName, group : 0 };
477 uEditRunDupeSearch('names', search_hash);
481 var uEditOldIdentValue;
482 function uEditCheckIdentDup(field) {
483 var newval = uEditNodeVal(field);
484 if( newval && newval != uEditOldIdentValue ) {
485 /* searches all ident_value fields */
486 var search_hash = { ident : { value : newval, group : 2 } };
487 uEditRunDupeSearch('ident', search_hash);
488 uEditOldIdentValue = newval;
493 /* Adds all of the addresses attached to the patron object
494 to the fields array */
495 var uEditAddrTemplate;
496 function uEditBuildAddrs(patron) {
497 var tbody = $('ue_address_tbody');
498 if(!uEditAddrTemplate)
499 uEditAddrTemplate = tbody.removeChild($('ue_address_template'));
500 for( var a in patron.addresses() )
501 uEditBuildAddrFields( patron, patron.addresses()[a]);
505 function uEditDeleteAddr( tbody, row, address, detach ) {
506 if(!confirm($('ue_delete_addr_warn').innerHTML)) return;
507 if(address.isnew()) {
509 grep( patron.addresses(),
511 return (i.id() != address.id());
517 for( var f in dataFields ) {
518 if( dataFields[f].object == address ) {
519 dataFields[f] = null;
523 dataFields = compactArray(dataFields);
527 if( detach ) { /* remove the offending address from the list */
532 return (i.id() != address.id());
538 address.isdeleted(1);
542 tbody.removeChild(row);
544 var bid = patron.billing_address();
545 bid = (typeof bid == 'object') ? bid.id() : bid;
547 var mid = patron.mailing_address();
548 mid = (typeof mid == 'object') ? mid.id() : mid;
551 /* -----------------------------------------------------------------------
552 if we're deleting a billing or mailing address
553 make sure some other address is automatically
554 assigned as the billing or mailng address
555 ----------------------------------------------------------------------- */
557 if( bid == address.id() ) {
558 for( var a in patron.addresses() ) {
559 var addr = patron.addresses()[a];
560 if(!addr.isdeleted() && addr.id() != address.id()) {
561 var node = uEditFindAddrInput('billing', addr.id());
563 uEditAddrTypeClick(node, 'billing');
569 if( mid == address.id() ) {
570 for( var a in patron.addresses() ) {
571 var addr = patron.addresses()[a];
572 if(!addr.isdeleted() && addr.id() != address.id()) {
573 var node = uEditFindAddrInput('mailing', addr.id());
575 uEditAddrTypeClick(node, 'mailing');
584 function uEditFindAddrInput(type, id) {
585 var tbody = $('ue_address_tbody');
586 var rows = tbody.getElementsByTagName('tr');
587 for( var r in rows ) {
589 if(row.parentNode != tbody) continue;
590 var node = $n(row, 'ue_addr_'+type+'_yes');
591 if( node.getAttribute('address') == id )
597 function uEditAddrTypeClick(input, type) {
598 var tbody = $('ue_address_tbody');
599 var rows = tbody.getElementsByTagName('tr');
600 for( var r in rows ) {
602 if(row.parentNode != tbody) continue;
603 var node = $n(row, 'ue_addr_'+type+'_yes');
604 removeCSSClass(node.parentNode,'addr_info_checked');
607 addCSSClass(input.parentNode,'addr_info_checked');
608 patron[type+'_address'](input.getAttribute('address'));
615 /* Creates the field entries for an address object. */
616 function uEditBuildAddrFields(patron, address) {
618 var tbody = $('ue_address_tbody');
620 var row = tbody.appendChild(
621 uEditAddrTemplate.cloneNode(true));
623 uEditCheckSharedAddr(patron, address, tbody, row);
625 $n(row, 'ue_addr_delete').onclick =
626 function() { uEditDeleteAddr(tbody, row, address); }
628 if( patron.billing_address() &&
629 address.id() == patron.billing_address().id() )
630 $n(row, 'ue_addr_billing_yes').checked = true;
632 if( patron.mailing_address() &&
633 address.id() == patron.mailing_address().id() )
634 $n(row, 'ue_addr_mailing_yes').checked = true;
636 $n(row, 'ue_addr_billing_yes').setAttribute('address', address.id());
637 $n(row, 'ue_addr_mailing_yes').setAttribute('address', address.id());
639 /* currently, non-owners cannot edit an address */
640 var disabled = (address.usr() != patron.id())
646 key : 'address_type',
649 name : 'ue_addr_label',
658 errkey : 'ue_bad_addr_street',
661 name : 'ue_addr_street1',
670 errkey : 'ue_bad_addr_street',
673 name : 'ue_addr_street2',
682 errkey : 'ue_bad_addr_city',
685 name : 'ue_addr_city',
696 name : 'ue_addr_county',
705 errkey : 'ue_bad_addr_state',
708 name : 'ue_addr_state',
717 errkey : 'ue_bad_addr_country',
720 name : 'ue_addr_country',
729 errkey : 'ue_bad_addr_zip',
732 name : 'ue_addr_zip',
736 onblur : function(f) {
737 var v = uEditNodeVal(f);
738 var req = new Request(ZIP_SEARCH, v);
741 var info = r.getResultObject();
743 var state = $n(f.widget.base, 'ue_addr_state');
744 var county = $n(f.widget.base, 'ue_addr_county');
745 var city = $n(f.widget.base, 'ue_addr_city');
747 state.value = info.state;
751 county.value = info.county;
755 city.value = info.city;
767 key : 'within_city_limits',
770 name : 'ue_addr_inc_yes',
781 name : 'ue_addr_valid_yes',
788 for( var f in fields ) {
789 dataFields.push(fields[f]);
790 uEditActivateField(fields[f]);
794 function uEditBuildPatronSCM(patron) {
795 /* get the list of pre-defined maps */
796 var fields = uEditFindFieldsByKey('stat_cat_entry');
800 /* for each user stat cat, pop it off the list,
801 updated the existing stat map field to match
802 the popped map and shove the existing stat
803 map field onto the user's list of stat maps */
804 while( (map = patron.stat_cat_entries().pop()) ) {
806 var field = grep(fields,
808 return (item.object.stat_cat() == map.stat_cat());
813 var val = map.stat_cat_entry();
815 $n(field.widget.base, field.widget.name).value = val;
816 setSelector($n(field.widget.base, 'ue_stat_cat_selector'), val );
817 field.object.stat_cat_entry(val);
818 field.object.id(map.id());
819 newmaps.push(field.object);
823 for( var m in newmaps )
824 patron.stat_cat_entries().push(newmaps[m]);
828 function uEditBuildSCMField(statcat, row) {
830 var map = new actscecm();
831 map.stat_cat(statcat.id());
832 map.target_usr(patron.id());
837 key : 'stat_cat_entry',
840 name : 'ue_stat_cat_newval',
843 onpostchange : function( field, newval ) {
845 /* see if the current map already resides in
846 the patron entry list */
847 var exists = grep( patron.stat_cat_entries(),
849 return (item.stat_cat() == statcat.id());
855 setSelector($n(row, 'ue_stat_cat_selector'), newval);
861 /* if the map is new but currently contains no value
862 remove it from the set of new maps */
864 patron.stat_cat_entries(
865 grep( patron.stat_cat_entries(),
867 return (item.stat_cat() != map.stat_cat());
880 /* map does not exist in the map array but now has data */
883 patron.stat_cat_entries().push(map);
890 dataFields.push(field);
895 /** Run this after a new ident type is selected */
896 function _uEditIdentPostchange(type, field, newval) {
900 /* When the ident type is changed, we change the
901 regex on the ident_value to match the selected type */
902 var vfname = 'ident_value';
903 if(type == 'secondary') vfname = 'ident_value2';
904 var vfield = uEditFindFieldByKey(vfname);
905 var name = identTypesCache[uEditNodeVal(field)].name();
907 hideMe($(type+'_ident_ssn_help'));
908 hideMe($(type+'_ident_dl_help'));
910 if(name.match(/ssn/i)) {
911 vfield.widget.regex = ssnRegex;
912 vfield.errkey = 'ue_bad_ident_ssn';
913 unHideMe($(type+'_ident_ssn_help'));
917 if(name.match(/driver/i)) {
918 vfield.widget.regex = dlRegex;
919 vfield.errkey = 'ue_bad_ident_dl';
920 unHideMe($(type+'_ident_dl_help'));
921 if(!uEditNodeVal(vfield))
922 vfield.widget.node.value = defaultState + '-';
925 vfield.widget.regex = null;
926 vfield.errkey = null;
930 /* focus then valdate the value field */
931 vfield.widget.node.onchange();
932 vfield.widget.node.focus();
936 /* checks to see if the given address is shared by others.
937 * if so, the address row is styled and ...
940 function uEditCheckSharedAddr(patron, address, tbody, row) {
942 if( address.isnew() || (patron.isnew() && !clone) ) return;
944 var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
948 var members = r.getResultObject();
951 for( var m in members ) {
954 if( id != patron.id() ) {
956 addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
957 unHideMe($n(row, 'shared_row'));
958 $n(row, 'ue_addr_delete').disabled = true;
960 if( address.usr() != patron.id() ) {
961 var button = $n(row, 'ue_addr_detach');
964 function() { uEditDeleteAddr( tbody, row, address, true ); }
974 /* if this is a shared address, set the owner field and
975 give the staff a chance to edit the owner if it's not this user */
977 var nnode = $n(row, 'addr_owner_name');
978 var link = $n(row, 'addr_owner');
979 var id = address.usr();
981 if( id == patron.id() ) {
983 nnode.appendChild(text(
984 patron.first_given_name() + ' ' + patron.family_name()));
985 hideMe($n(row, 'owner_link_div'));
990 function() { window.xulG.spawn_editor({ses:cgi.param('ses'),usr:id}) };
992 if( userCache[id] ) {
993 nnode.appendChild(text(
994 usr.first_given_name() + ' ' + usr.family_name()));
998 fetchFleshedUser( id,
1000 userCache[usr.id()] = usr;
1001 nnode.appendChild(text(
1002 usr.first_given_name() + ' ' + usr.family_name()));
1018 function uEditCheckDOB(field) {
1020 var dob = uEditNodeVal(field);
1022 /* don't bother if the data isn't valid */
1023 if(!dob || !dob.match(field.widget.regex))
1026 if( dob == __lastdob ) return;
1030 var parts = dob.split(/-/);
1031 parts[2] = parts[2].replace(/[T ].*/,'');
1032 dob = buildDate( parts[0], parts[1], parts[2] );
1034 var today = new Date();
1036 if(!dob || dob > today) {
1037 addCSSClass(field.widget.node, CSS_INVALID_DATA);
1038 alertId('ue_bad_date');
1042 var base = new Date();
1043 base.setYear( today.getYear() + 1900 - ADULT_AGE );
1045 /* patron is at least 18 */
1047 var f = uEditFindFieldByKey('ident_value2');
1049 if( dob < base ) { /* patron is of age */
1051 hideMe(f.widget.node.parentNode.parentNode.parentNode);
1055 unHideMe(f.widget.node.parentNode.parentNode.parentNode);