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 var req = new Request(CHECK_BARCODE, SESSION, barcode);
84 var res = r.getResultObject();
85 if( res && res != patron.id() ) {
86 field.widget.onblur = null; /* prevent alert storm */
87 alertId('ue_dup_barcode');
88 field.widget.onblur = uEditBarcodeBlur;
91 field.widget.node.focus();
92 field.widget.node.select();
96 var node = uEditFindFieldByWId("ue_username");
97 if(!node.widget.node.value) {
98 node.widget.node.value = barcode;
99 node.widget.node.onchange();
108 function uEditDefineData(patron) {
113 object : patron.card(),
115 errkey : 'ue_bad_barcode',
120 onblur : uEditBarcodeBlur
127 errkey : 'ue_bad_username',
132 onblur : uEditUsrnameBlur
136 required : (patron.isnew()) ? true : false,
139 errkey : 'ue_bad_password',
143 onpostchange : function(field, newval) {
144 var pw2 = uEditFindFieldByWId('ue_password2');
145 /* tell the second passsword input to re-validate */
146 pw2.widget.node.onchange();
152 required : (patron.isnew()) ? true : false,
155 errkey : 'ue_bad_password',
159 onpostchange : function(field, newval) {
160 var pw1f = uEditFindFieldByWId('ue_password1');
161 var pw1 = uEditNodeVal(pw1f);
162 field.widget.regex = new RegExp('^'+pw1+'$');
163 if( pw1 ) field.required = true;
166 field.required = false;
174 key : 'first_given_name',
175 errkey : 'ue_bad_firstname',
180 onblur : function(field) {
181 uEditCheckNamesDup('first', field );
189 key : 'second_given_name',
190 errkey : 'ue_bad_middlename',
192 id : 'ue_middlename',
201 errkey : 'ue_bad_lastname',
206 onblur : function(field) {
207 uEditCheckNamesDup('last', field );
218 onload : function(val) {
219 setSelector($('ue_suffix_selector'), val);
220 $('ue_suffix_selector').onchange = function() {
221 uEditFindFieldByKey('suffix').widget.node.onchange();
230 errkey : 'ue_bad_dob',
235 onpostchange : function(field) { uEditCheckDOB(field); },
236 onblur : function(field) { uEditCheckDOB(field); }
243 errkey : 'ue_no_ident',
245 id : 'ue_primary_ident_type',
248 onpostchange : function(field, newval)
249 { _uEditIdentPostchange('primary', field, newval); }
257 id : 'ue_primary_ident',
259 onblur : function(field) {
260 uEditCheckIdentDup(field);
267 key : 'ident_value2',
269 id : 'ue_secondary_ident',
277 errkey : 'ue_bad_email',
281 regex : /.+\@.+\..+/, /* make me better */
282 onblur : function(field) {
283 var val = uEditNodeVal(field);
284 if( val && val != field.oldemail ) {
285 uEditRunDupeSearch('email',
286 { email : { value : val, group : 0 } });
287 field.oldemail = val;
296 errkey : 'ue_bad_phone',
306 key : 'evening_phone',
307 errkey : 'ue_bad_phone',
309 id : 'ue_night_phone',
318 errkey : 'ue_bad_phone',
320 id : 'ue_other_phone',
330 id : 'ue_org_selector',
339 errkey : 'ue_bad_expire',
362 onpostchange : function(field, val) {
363 var afield = uEditFindFieldByKey('alert_message');
365 if( !barredAlerted ) {
366 barredAlerted = true;
367 alertId('ue_made_barred');
369 afield.required = true;
371 afield.required = false;
380 errkey : 'ue_no_profile',
385 onpostchange : function(field, value) {
386 var type = groupsCache[value];
388 var interval = type.perm_interval();
390 /* interval_to_seconds expects 'M' for months, 'm' for minutes */
391 interval = interval.replace(/mon/, 'Mon');
392 var intsecs = parseInt(interval_to_seconds(interval));
394 var expdate = new Date();
395 var exptime = expdate.getTime();
396 exptime += intsecs * 1000;
397 expdate.setTime(exptime);
399 var year = expdate.getYear() + 1900;
400 var month = (expdate.getMonth() + 1) + '';
401 var day = (expdate.getDate()) + '';
403 if(!month.match(/\d{2}/)) month = '0' + month;
404 if(!day.match(/\d{2}/)) day = '0' + day;
406 var node = $('ue_expire');
407 node.value = year+'-'+month+'-'+day;
414 key : 'net_access_level',
423 key : 'master_account',
425 id : 'ue_group_lead',
432 key : 'claims_returned_count',
434 id : 'ue_claims_returned',
443 key : 'alert_message',
445 id : 'ue_alert_message',
451 for( var f in fields )
452 dataFields.push(fields[f]);
454 uEditBuildAddrs(patron);
455 uEditBuildPatronSCM(patron);
458 var uEditOldFirstName;
459 var uEditOldMiddleName; /* future */
460 var uEditOldLastName;
461 function uEditCheckNamesDup(type, field) {
462 var newval = uEditNodeVal(field);
465 var dosearch = false;
468 if( newval != uEditOldFirstName )
470 uEditOldFirstName = newval;
474 if( newval != uEditOldLastName )
476 uEditOldLastName = newval;
479 if( dosearch && uEditOldFirstName && uEditOldLastName ) {
480 var search_hash = {};
481 search_hash['first_given_name'] = { value : uEditOldFirstName, group : 0 };
482 search_hash['family_name'] = { value : uEditOldLastName, group : 0 };
483 uEditRunDupeSearch('names', search_hash);
487 var uEditOldIdentValue;
488 function uEditCheckIdentDup(field) {
489 var newval = uEditNodeVal(field);
490 if( newval && newval != uEditOldIdentValue ) {
491 /* searches all ident_value fields */
492 var search_hash = { ident : { value : newval, group : 2 } };
493 uEditRunDupeSearch('ident', search_hash);
494 uEditOldIdentValue = newval;
499 /* Adds all of the addresses attached to the patron object
500 to the fields array */
501 var uEditAddrTemplate;
502 function uEditBuildAddrs(patron) {
503 var tbody = $('ue_address_tbody');
504 if(!uEditAddrTemplate)
505 uEditAddrTemplate = tbody.removeChild($('ue_address_template'));
506 for( var a in patron.addresses() )
507 uEditBuildAddrFields( patron, patron.addresses()[a]);
511 function uEditDeleteAddr( tbody, row, address, detach ) {
512 if(!confirm($('ue_delete_addr_warn').innerHTML)) return;
513 if(address.isnew()) {
515 grep( patron.addresses(),
517 return (i.id() != address.id());
523 for( var f in dataFields ) {
524 if( dataFields[f].object == address ) {
525 dataFields[f] = null;
529 dataFields = compactArray(dataFields);
533 if( detach ) { /* remove the offending address from the list */
538 return (i.id() != address.id());
544 address.isdeleted(1);
548 tbody.removeChild(row);
550 var bid = patron.billing_address();
551 bid = (typeof bid == 'object') ? bid.id() : bid;
553 var mid = patron.mailing_address();
554 mid = (typeof mid == 'object') ? mid.id() : mid;
557 /* -----------------------------------------------------------------------
558 if we're deleting a billing or mailing address
559 make sure some other address is automatically
560 assigned as the billing or mailng address
561 ----------------------------------------------------------------------- */
563 if( bid == address.id() ) {
564 for( var a in patron.addresses() ) {
565 var addr = patron.addresses()[a];
566 if(!addr.isdeleted() && addr.id() != address.id()) {
567 var node = uEditFindAddrInput('billing', addr.id());
569 uEditAddrTypeClick(node, 'billing');
575 if( mid == address.id() ) {
576 for( var a in patron.addresses() ) {
577 var addr = patron.addresses()[a];
578 if(!addr.isdeleted() && addr.id() != address.id()) {
579 var node = uEditFindAddrInput('mailing', addr.id());
581 uEditAddrTypeClick(node, 'mailing');
590 function uEditFindAddrInput(type, id) {
591 var tbody = $('ue_address_tbody');
592 var rows = tbody.getElementsByTagName('tr');
593 for( var r in rows ) {
595 if(row.parentNode != tbody) continue;
596 var node = $n(row, 'ue_addr_'+type+'_yes');
597 if( node.getAttribute('address') == id )
603 function uEditAddrTypeClick(input, type) {
604 var tbody = $('ue_address_tbody');
605 var rows = tbody.getElementsByTagName('tr');
606 for( var r in rows ) {
608 if(row.parentNode != tbody) continue;
609 var node = $n(row, 'ue_addr_'+type+'_yes');
610 removeCSSClass(node.parentNode,'addr_info_checked');
613 addCSSClass(input.parentNode,'addr_info_checked');
614 patron[type+'_address'](input.getAttribute('address'));
621 /* Creates the field entries for an address object. */
622 function uEditBuildAddrFields(patron, address) {
624 var tbody = $('ue_address_tbody');
626 var row = tbody.appendChild(
627 uEditAddrTemplate.cloneNode(true));
629 uEditCheckSharedAddr(patron, address, tbody, row);
631 $n(row, 'ue_addr_delete').onclick =
632 function() { uEditDeleteAddr(tbody, row, address); }
634 if( patron.billing_address() &&
635 address.id() == patron.billing_address().id() )
636 $n(row, 'ue_addr_billing_yes').checked = true;
638 if( patron.mailing_address() &&
639 address.id() == patron.mailing_address().id() )
640 $n(row, 'ue_addr_mailing_yes').checked = true;
642 $n(row, 'ue_addr_billing_yes').setAttribute('address', address.id());
643 $n(row, 'ue_addr_mailing_yes').setAttribute('address', address.id());
645 /* currently, non-owners cannot edit an address */
646 var disabled = (address.usr() != patron.id())
652 key : 'address_type',
655 name : 'ue_addr_label',
664 errkey : 'ue_bad_addr_street',
667 name : 'ue_addr_street1',
676 errkey : 'ue_bad_addr_street',
679 name : 'ue_addr_street2',
688 errkey : 'ue_bad_addr_city',
691 name : 'ue_addr_city',
702 name : 'ue_addr_county',
711 errkey : 'ue_bad_addr_state',
714 name : 'ue_addr_state',
723 errkey : 'ue_bad_addr_country',
726 name : 'ue_addr_country',
735 errkey : 'ue_bad_addr_zip',
738 name : 'ue_addr_zip',
742 onblur : function(f) {
743 var v = uEditNodeVal(f);
744 var req = new Request(ZIP_SEARCH, v);
747 var info = r.getResultObject();
749 var state = $n(f.widget.base, 'ue_addr_state');
750 var county = $n(f.widget.base, 'ue_addr_county');
751 var city = $n(f.widget.base, 'ue_addr_city');
753 state.value = info.state;
757 county.value = info.county;
761 city.value = info.city;
773 key : 'within_city_limits',
776 name : 'ue_addr_inc_yes',
787 name : 'ue_addr_valid_yes',
794 for( var f in fields ) {
795 dataFields.push(fields[f]);
796 uEditActivateField(fields[f]);
800 function uEditBuildPatronSCM(patron) {
801 /* get the list of pre-defined maps */
802 var fields = uEditFindFieldsByKey('stat_cat_entry');
806 /* for each user stat cat, pop it off the list,
807 updated the existing stat map field to match
808 the popped map and shove the existing stat
809 map field onto the user's list of stat maps */
810 while( (map = patron.stat_cat_entries().pop()) ) {
812 var field = grep(fields,
814 return (item.object.stat_cat() == map.stat_cat());
819 var val = map.stat_cat_entry();
821 $n(field.widget.base, field.widget.name).value = val;
822 setSelector($n(field.widget.base, 'ue_stat_cat_selector'), val );
823 field.object.stat_cat_entry(val);
824 field.object.id(map.id());
825 newmaps.push(field.object);
829 for( var m in newmaps )
830 patron.stat_cat_entries().push(newmaps[m]);
834 function uEditBuildSCMField(statcat, row) {
836 var map = new actscecm();
837 map.stat_cat(statcat.id());
838 map.target_usr(patron.id());
843 key : 'stat_cat_entry',
846 name : 'ue_stat_cat_newval',
849 onpostchange : function( field, newval ) {
851 /* see if the current map already resides in
852 the patron entry list */
853 var exists = grep( patron.stat_cat_entries(),
855 return (item.stat_cat() == statcat.id());
861 setSelector($n(row, 'ue_stat_cat_selector'), newval);
867 /* if the map is new but currently contains no value
868 remove it from the set of new maps */
870 patron.stat_cat_entries(
871 grep( patron.stat_cat_entries(),
873 return (item.stat_cat() != map.stat_cat());
886 /* map does not exist in the map array but now has data */
889 patron.stat_cat_entries().push(map);
896 dataFields.push(field);
901 /** Run this after a new ident type is selected */
902 function _uEditIdentPostchange(type, field, newval) {
906 /* When the ident type is changed, we change the
907 regex on the ident_value to match the selected type */
908 var vfname = 'ident_value';
909 if(type == 'secondary') vfname = 'ident_value2';
910 var vfield = uEditFindFieldByKey(vfname);
911 var name = identTypesCache[uEditNodeVal(field)].name();
913 hideMe($(type+'_ident_ssn_help'));
914 hideMe($(type+'_ident_dl_help'));
916 if(name.match(/ssn/i)) {
917 vfield.widget.regex = ssnRegex;
918 vfield.errkey = 'ue_bad_ident_ssn';
919 unHideMe($(type+'_ident_ssn_help'));
923 if(name.match(/driver/i)) {
924 vfield.widget.regex = dlRegex;
925 vfield.errkey = 'ue_bad_ident_dl';
926 unHideMe($(type+'_ident_dl_help'));
927 if(!uEditNodeVal(vfield))
928 vfield.widget.node.value = defaultState + '-';
931 vfield.widget.regex = null;
932 vfield.errkey = null;
936 /* focus then valdate the value field */
937 vfield.widget.node.onchange();
938 vfield.widget.node.focus();
942 /* checks to see if the given address is shared by others.
943 * if so, the address row is styled and ...
946 function uEditCheckSharedAddr(patron, address, tbody, row) {
948 if( address.isnew() || (patron.isnew() && !clone) ) return;
950 var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
954 var members = r.getResultObject();
957 for( var m in members ) {
960 if( id != patron.id() ) {
962 addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
963 unHideMe($n(row, 'shared_row'));
964 $n(row, 'ue_addr_delete').disabled = true;
966 if( address.usr() != patron.id() ) {
967 var button = $n(row, 'ue_addr_detach');
970 function() { uEditDeleteAddr( tbody, row, address, true ); }
980 /* if this is a shared address, set the owner field and
981 give the staff a chance to edit the owner if it's not this user */
983 var nnode = $n(row, 'addr_owner_name');
984 var link = $n(row, 'addr_owner');
985 var id = address.usr();
987 if( id == patron.id() ) {
989 nnode.appendChild(text(
990 patron.first_given_name() + ' ' + patron.family_name()));
991 hideMe($n(row, 'owner_link_div'));
996 function() { window.xulG.spawn_editor({ses:cgi.param('ses'),usr:id}) };
998 if( userCache[id] ) {
999 nnode.appendChild(text(
1000 usr.first_given_name() + ' ' + usr.family_name()));
1004 fetchFleshedUser( id,
1006 userCache[usr.id()] = usr;
1007 nnode.appendChild(text(
1008 usr.first_given_name() + ' ' + usr.family_name()));
1024 function uEditCheckDOB(field) {
1026 var dob = uEditNodeVal(field);
1028 /* don't bother if the data isn't valid */
1029 if(!dob || !dob.match(field.widget.regex))
1032 if( dob == __lastdob ) return;
1036 var parts = dob.split(/-/);
1037 parts[2] = parts[2].replace(/[T ].*/,'');
1038 dob = buildDate( parts[0], parts[1], parts[2] );
1040 var today = new Date();
1042 if(!dob || dob > today) {
1043 addCSSClass(field.widget.node, CSS_INVALID_DATA);
1044 alertId('ue_bad_date');
1048 var base = new Date();
1049 base.setYear( today.getYear() + 1900 - ADULT_AGE );
1051 /* patron is at least 18 */
1053 var f = uEditFindFieldByKey('ident_value2');
1055 if( dob < base ) { /* patron is of age */
1057 hideMe(f.widget.node.parentNode.parentNode.parentNode);
1061 unHideMe(f.widget.node.parentNode.parentNode.parentNode);