Lazy Circ (AKA partial barcode lookup)
[working/Evergreen.git] / Open-ILS / web / opac / skin / default / js / holds.js
1 var holdsOrgSelectorBuilt = false;
2 var holdArgs;
3
4 /* 
5 note: metarecord holds have a holdable_formats field that contains
6 item_type(s)-item_forms(s)-language
7 item_form and language are optional - if language exist and no 
8 item_form is specified, use item_type(s)--language
9 */
10
11 var noEmailMessage;
12 var noEmailMessageXUL;
13
14 var holdTargetTypeMap = {
15     M : 'metarecord',
16     T : 'record',
17     V : 'volume',
18     I : 'issuance',
19     C : 'copy',
20     P : 'part'
21 };
22
23
24
25 function holdsHandleStaff() {
26
27     // if we know the recipient's barcode, use it
28     if(xulG.patron_barcode) return _holdsHandleStaff();
29
30         swapCanvas($('xulholds_box'));
31         $('xul_recipient_barcode').focus();
32         $('xul_recipient_barcode').onkeypress = function(evt) 
33                 {if(userPressedEnter(evt)) { _holdsHandleStaff(); } };
34         $('xul_recipient_barcode_submit').onclick = _holdsHandleStaff;
35         $('xul_recipient_me').onclick = _holdsHandleStaffMe;
36
37         $('xul_recipient_barcode').onkeyup = function(evt) {
38         if($('xul_recipient_barcode').value == '') 
39             $('xul_recipient_me').disabled = false;
40         else
41             $('xul_recipient_me').disabled = true;
42     };
43 }
44
45 $('holds_frozen_thaw_input').onchange = 
46         function(){holdsVerifyThawDateUI('holds_frozen_thaw_input');}
47 $('holds_frozen_thaw_input').onkeyup = 
48         function(){holdsVerifyThawDateUI('holds_frozen_thaw_input');}
49
50 function _holdsHandleStaffMe() {
51         holdArgs.recipient = G.user;
52         holdsDrawEditor();
53 }
54
55 function _holdsHandleStaff() {
56         var barcode = xulG.patron_barcode;
57     if(!barcode) {
58         barcode = $('xul_recipient_barcode').value;
59         if(xulG.get_barcode) {
60             // We have a "complete the barcode" function, call it (actor = users only)
61             var new_barcode = xulG.get_barcode(window, 'actor', barcode);
62             // If we got a result (boolean false is "no result") check it
63             if(new_barcode) {
64                 // user_false string means they picked "None of the above"
65                 // Abort before any other events can fire
66                 if(new_barcode == "user_false") return;
67                 // No error means we have a (hopefully valid) completed barcode to use.
68                 // Otherwise, fall through to other methods of checking
69                 if(typeof new_barcode.ilsevent == 'undefined')
70                     barcode = new_barcode.barcode;
71             }
72         }
73     }
74         var user = grabUserByBarcode( G.user.session, barcode );
75
76         var evt;
77         if(evt = checkILSEvent(user)) {
78                 alertILSEvent(user);
79                 return;
80         }
81
82         if(!barcode || !user) {
83                 alertId('holds_invalid_recipient', barcode);
84                 return
85         }
86
87         grabUserPrefs(user);
88
89         holdArgs.recipient = user;
90         holdsDrawEditor();
91 }
92
93
94
95 /** args:
96   * record, volume, copy (ids)
97   * request, recipient, editHold (objects)
98   */
99
100 function holdsDrawEditor(args) {
101
102         holdArgs = (args) ? args : holdArgs;
103
104     if(!noEmailMessage)
105         noEmailMessage = $('holds_email').removeChild($('holds.no_email'));
106
107     if(!noEmailMessageXUL)
108         noEmailMessageXUL = $('holds_email').removeChild($('holds.no_email.xul'));
109
110         if(isXUL() && holdArgs.recipient == null 
111                         && holdArgs.editHold == null) {
112                 holdsHandleStaff();
113                 return;
114         }
115
116         if(!holdArgs.recipient) holdArgs.recipient = G.user;
117         if(!holdArgs.requestor) holdArgs.requestor = G.user;
118
119         if(!(holdArgs.requestor && holdArgs.requestor.session)) {
120                 detachAllEvt('common','locationChanged');
121                 attachEvt('common','loggedIn', holdsDrawEditor)
122                 initLogin();
123                 return;
124         }
125
126         if(holdArgs.editHold) // flesh the args with the existing hold 
127                 holdArgsFromHold(holdArgs.editHold, holdArgs);
128
129         holdsDrawWindow();
130 }
131
132
133 // updates the edit window with the existing hold's data 
134 function _holdsUpdateEditHold() {
135
136         var hold = holdArgs.editHold;
137         var qstats = holdArgs.status;
138
139         var orgsel = $('holds_org_selector');
140     var frozenbox = $('holds_frozen_chkbox');
141
142         setSelector(orgsel, hold.pickup_lib());
143
144         if( hold.capture_time() || qstats.status > 2 ) {
145         frozenbox.disabled = true;
146         $('holds_frozen_thaw_input').disabled = true;
147         if(qstats.status == 3) {
148             // no pickup lib changes while in-transit
149                     orgsel.disabled = true;
150         } else {
151             var orgs = fetchPermOrgs('UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF');
152             if(orgs[0] == -1)
153                         orgsel.disabled = true;
154         }
155     } else {
156                 orgsel.disabled = false;
157         frozenbox.disabled = false;
158     }
159
160
161         $('holds_submit').onclick = holdsEditHold;
162         $('holds_update').onclick = holdsEditHold;
163
164         if(hold.phone_notify()) {
165                 $('holds_enable_phone').checked = true;
166                 $('holds_phone').value = hold.phone_notify();
167
168         } else {
169                 $('holds_phone').disabled = true;
170                 $('holds_enable_phone').checked = false;
171         }
172
173         if(isTrue(hold.email_notify())) {
174                 $('holds_enable_email').checked = true;
175
176         } else {
177                 $('holds_enable_email').checked = false;
178         }
179
180     dijit.byId('holds_expire_time').setValue(dojo.date.stamp.fromISOString(hold.expire_time()));
181
182     /* populate the hold freezing info */
183     if(!frozenbox.disabled && isTrue(hold.frozen())) {
184         frozenbox.checked = true;
185         unHideMe($('hold_frozen_thaw_row'));
186         if(hold.thaw_date()) {
187             dijit.byId('holds_frozen_thaw_input').setValue(dojo.date.stamp.fromISOString(hold.thaw_date()));
188         } else {
189             dijit.byId('holds_frozen_thaw_input').setValue('');
190         }
191     } else {
192         frozenbox.checked = false;
193         dijit.byId('holds_frozen_thaw_input').setValue('');
194         hideMe($('hold_frozen_thaw_row'));
195     }
196 }
197
198 function holdsEditHold() {
199         var hold = holdsBuildHoldFromWindow();
200         if(!hold) return;
201         holdsUpdate(hold);
202         showCanvas();
203         if(holdArgs.onComplete)
204                 holdArgs.onComplete(hold);
205 }
206
207 function holdArgsFromHold(hold, oargs) {
208         var args = (oargs) ? oargs : {};
209         args.type = hold.hold_type();
210         var target = hold.target();
211     args[holdTargetTypeMap[args.type]] = target;
212         return args;
213 }
214
215 function holdFetchObjects(hold, doneCallback) {
216
217         var args = (hold) ? holdArgsFromHold(hold) : holdArgs;
218
219         var type = args.type;
220
221         if( type == 'C' ) {
222
223                 if( args.copyObject ) {
224
225                         args.copy = args.copyObject.id();
226                         args.volume = args.copyObject.call_number();
227                         _h_set_vol(args, doneCallback);
228
229                 } else {
230                         var creq = new Request(FETCH_COPY, args.copy);
231
232                         creq.callback(
233                                 function(r) {
234                                         var cp = r.getResultObject();
235                                         args.copyObject = cp;
236                                         args.volume = args.copyObject.call_number();
237                                         _h_set_vol(args, doneCallback);
238                                 }
239                         );
240                         creq.send();
241                 }
242         } else {
243                 if( type == 'V' ) {
244                         _h_set_vol(args, doneCallback);
245
246         } else if( type == 'I' ) {
247             _h_set_issuance(args, doneCallback);
248
249         } else if( type == 'P' ) {
250             _h_set_parts(args, doneCallback);
251
252                 } else {
253                         if( type == 'T') {
254                                 _h_set_rec(args, doneCallback);
255                         } else {
256                                 _h_set_rec_descriptors(args, doneCallback);
257                         }
258                 }
259         }
260
261         return args;
262 }
263
264 function _h_set_parts(args, doneCallback) {
265
266     var preq = new Request(
267         'open-ils.fielder:open-ils.fielder.bmp.atomic',
268         {"cache":1, "fields":["label", "record"],"query": {"id":args.part}}
269     );
270
271     preq.callback(
272         function(r) {
273             var part = r.getResultObject()[0];
274             args.record = part.record;
275             args.partObject = part;
276             _h_set_rec(args, doneCallback);
277         }
278     );
279
280     preq.send();
281 }
282
283 function _h_set_vol(args, doneCallback) {
284
285         if( args.volumeObject ) {
286                 args.volume = args.volumeObject.id();
287                 args.record = args.volumeObject.record();
288                 _h_set_rec(args, doneCallback);
289
290         } else {
291
292                 var vreq = new Request(FETCH_VOLUME, args.volume);
293                 vreq.callback(
294                         function(r) {
295                                 var vol = r.getResultObject();
296                                 args.volumeObject = vol;
297                                 args.record = vol.record();
298                                 _h_set_rec(args, doneCallback);
299                         }
300                 );
301                 vreq.send();
302         }
303 }
304
305 function _h_set_issuance(args, doneCallback) {
306
307         if( args.issuanceObject ) {
308                 args.issuance = args.issuanceObject.id();
309                 args.record = args.issuanceObject.subscription().record_entry();
310                 _h_set_rec(args, doneCallback);
311
312         } else {
313
314                 var vreq = new Request(FETCH_ISSUANCE, [args.issuance]);
315                 vreq.callback(
316                         function(r) {
317                                 var issuance = r.getResultObject()[0];
318                                 args.issuanceObject = issuance;
319                                 args.record = issuance.subscription().record_entry();
320                                 _h_set_rec(args, doneCallback);
321                         }
322                 );
323                 vreq.send();
324         }
325 }
326
327 function _h_set_rec(args, doneCallback) {
328
329         if(args.recordObject) 
330                 args.record = args.recordObject.doc_id();
331         else 
332                 args.recordObject = findRecord( args.record, 'T' );
333         
334         if( args.type == 'T' || args.type == 'M' )  {
335                 _h_set_rec_descriptors(args, doneCallback);
336         //} else if(args.type == 'P') {
337         //_h_get_parts(args, doneCallback);
338     } else {
339                 if(doneCallback) doneCallback(args);
340     }
341 }
342
343
344 function _h_set_rec_descriptors(args, doneCallback) {
345
346     if( ! args.pickup_lib )
347         args.pickup_lib = getSelectorVal($('holds_org_selector'));
348
349     if(args.pickup_lib === null)
350         args.pickup_lib = args.recipient.home_ou();
351
352         // grab the list of record desciptors attached to this records metarecord 
353         if( ! args.recordDescriptors )  {
354                 var params = { pickup_lib: args.pickup_lib };
355
356         if (args.type == 'M') {
357                 if( !args.metarecord && args.record) {
358                 params.metarecord = args.metarecord = args.record;
359                 delete(args.record);
360                 } else {
361                                 params.metarecord = args.metarecordObject.doc_id();
362                 }
363         } else {
364                 params.record = args.record;
365         }
366
367                 if( ! args.record ) {
368                         if( args.metarecord )
369                                 params.metarecord = args.metarecord;
370                         else 
371                                 params.metarecord = args.metarecordObject.doc_id();
372                 }
373
374                 var req = new Request(FETCH_MR_DESCRIPTORS, params );
375                 req.callback(
376                         function(r) {
377                                 var data = r.getResultObject();
378                                 args.recordDescriptors = args.recordDescriptors = data.descriptors;
379                                 args.metarecord = args.metarecord = data.metarecord;
380                                 if( args.type == 'M' && ! args.metarecordObject) 
381                                         args.metarecordObject = args.metarecordObject = findRecord(args.metarecord, 'M');       
382
383                 _h_get_parts(args, doneCallback);
384                         }
385                 );
386                 req.send();
387
388         } else {
389         _h_get_parts(args, doneCallback);
390         }
391
392         return args;
393 }
394
395 function _h_get_parts(args, doneCallback) {
396
397     if(args.type == 'M' || args.editHold || args.holdParts) {
398         if(doneCallback) 
399             doneCallback(args);
400
401     } else {
402
403                 var req = new Request(
404             'open-ils.search:open-ils.search.biblio.record_hold_parts', 
405                     {pickup_lib: args.pickup_lib, record: args.record}
406         );
407
408                 req.callback(
409                         function(r) {
410                                 args.recordParts = r.getResultObject();
411                 if(doneCallback)
412                     doneCallback(args);
413                         }
414                 );
415                 req.send();
416     }
417 }
418
419
420
421 function holdsDrawWindow() {
422         swapCanvas($('holds_box'));
423         $('holds_cancel').onclick = function(){ runEvt('common', 'holdUpdateCanceled'), showCanvas() };
424         $('holds_submit').onclick = function(){holdsPlaceHold(holdsBuildHoldFromWindow())};
425         $('holds_update').onclick = function(){holdsPlaceHold(holdsBuildHoldFromWindow())};
426     $('holds_org_selector').onchange = function(){holdsDrawWindow()};
427         holdFetchObjects(null, 
428                 function(){
429                         __holdsDrawWindow();
430
431                         if(holdArgs.editHold) {
432                                 hideMe($('holds_submit'));
433                                 unHideMe($('holds_update'));
434                                 var req = new Request(FETCH_HOLD_STATUS, 
435                                         G.user.session, holdArgs.editHold.id());
436                                 req.send(true);
437                                 holdArgs.status = req.result();
438                                 _holdsUpdateEditHold();
439                         }  
440                 }
441         );
442 }
443
444 function __holdsDrawWindow() {
445
446         var rec = holdArgs.recordObject;
447         var vol = holdArgs.volumeObject;
448         var copy = holdArgs.copyObject;
449         var mr = holdArgs.metarecordObject;
450
451         rec = (rec) ? rec : mr;
452
453         if(!holdsOrgSelectorBuilt) {
454                 holdsBuildOrgSelector(null,0);
455                 holdsOrgSelectorBuilt = true;
456                 var selector = $('holds_org_selector');
457
458                 /*
459                 var o_loc = findOrgUnit(getOrigLocation());
460                 var t = findOrgType(o_loc.ou_type());
461                 if( t.can_have_users() ) 
462                         setSelector(selector, o_loc.id());
463                 else 
464                 */
465
466                 setSelector(selector, holdArgs.recipient.home_ou());
467         
468         }
469
470         /*
471         if(isXUL()) {
472                 var dsel = $('holds_depth_selector');
473                 unHideMe($('holds_depth_selector_row'));
474                 if(dsel.getElementsByTagName('option').length == 0) {
475                         var types = globalOrgTypes;
476                         var depth = findOrgDepth(G.user.ws_ou());
477                         iterate(types, 
478                                 function(t) {
479                                         if(t.depth() > depth) return;
480                                         insertSelectorVal(dsel, -1, t.opac_label(), t.depth());
481                                 }
482                         );
483                 }
484         }
485         */
486
487         appendClear($('holds_recipient'), text(
488                 holdArgs.recipient.family_name() + ', ' +  
489                         holdArgs.recipient.first_given_name()));
490         appendClear($('holds_title'), text(rec.title()));
491         appendClear($('holds_author'), text(rec.author()));
492
493     if( holdArgs.type == 'I' ) {
494                 unHideMe($('holds_type_row'));
495         unHideMe($('holds_is_issuance'));
496         unHideMe($('holds_issuance_row'));
497         appendClear($('holds_issuance_label'), text(holdArgs.issuanceObject.label()));
498
499     } else if( holdArgs.type == 'V' || holdArgs.type == 'C' ) {
500
501                 unHideMe($('holds_type_row'));
502                 unHideMe($('holds_cn_row'));
503                 appendClear($('holds_cn'), text(holdArgs.volumeObject.label()));
504
505                 if( holdArgs.type == 'V'  ) {
506                         unHideMe($('holds_is_cn'));
507                         hideMe($('holds_is_copy'));
508
509                 } else {
510                         hideMe($('holds_is_cn'));
511                         unHideMe($('holds_is_copy'));
512                         unHideMe($('holds_copy_row'));
513                         appendClear($('holds_copy'), text(holdArgs.copyObject.barcode()));
514                 }
515
516         } else {
517                 hideMe($('holds_type_row'));
518                 hideMe($('holds_copy_row'));
519                 hideMe($('holds_cn_row'));
520                 hideMe($('holds_issuance_row'));
521         }
522
523     if(holdArgs.recordParts && holdArgs.recordParts.length) {
524         var selector = $('holds_parts_selector');
525         unHideMe($('holds_parts_row'));
526         unHideMe(selector);
527
528         var nodeList = [];
529         dojo.forEach(selector.options, 
530             function(node) { if(node.value != '') nodeList.push(node) } );
531
532         dojo.forEach(nodeList, function(node) { selector.removeChild(node); });
533
534         dojo.forEach(
535             holdArgs.recordParts, 
536             function(part) {
537                 insertSelectorVal(selector, -1, part.label, part.id);
538             }
539         );
540
541     } else if(holdArgs.type == 'P') {
542         unHideMe($('holds_parts_row'));
543         unHideMe($('holds_parts_label'));
544             appendClear( $('holds_parts_label'), text(holdArgs.partObject.label));
545     }
546
547         removeChildren($('holds_format'));
548
549         var mods_formats = rec.types_of_resource();
550         var formats;
551
552         if(holdArgs.recordDescriptors)
553                 formats = holdArgs.recordDescriptors[0].item_type();
554
555         if( holdArgs.type == 'T' ) {
556                 var desc = grep( holdArgs.recordDescriptors,
557                         function(i) {
558                                 return (i.record() == holdArgs.record); 
559                         }
560                 );
561                 formats = desc[0].item_type();
562         }
563
564         if( holdArgs.type == 'M' ) {
565                 var mr_formats;
566                 if(holdArgs.editHold){
567                         mr_formats = holdArgs.editHold.holdable_formats();
568                 }else{
569                         mr_formats = ''; // collect the item_type()s from all holdArgs.recordDescriptors
570                         for(var desc in holdArgs.recordDescriptors){
571                 if (!holdArgs.recordDescriptors[desc].item_type()) continue;
572                                 mr_formats += holdArgs.recordDescriptors[desc].item_type();
573                         }
574
575             var first_form = 1;
576                         for(var desc in holdArgs.recordDescriptors){
577                 if (!holdArgs.recordDescriptors[desc].item_form()) continue;
578                 if (first_form) {
579                     mr_formats += '-';
580                     first_form = 0;
581                 }
582                                 mr_formats += holdArgs.recordDescriptors[desc].item_form();
583                         }
584
585
586                 }
587                 
588                 var data = holdsParseMRFormats(mr_formats);
589                 mods_formats = data.mods_formats;
590                 formats = data.formats;
591         }
592
593
594         for( var i in mods_formats ) {
595                 var res = mods_formats[i];
596                 var img = elem("img");
597                 setResourcePic(img, res);
598                 $('holds_format').appendChild(img);
599                 if(formats)
600                         $('holds_format').appendChild(text(' '+ MARCTypeToFriendly(formats[i]) +' '));
601                 else
602                         $('holds_format').appendChild(text(' '+ mods_formats[i] +' '));
603                 $('holds_format').appendChild(elem('br'));
604         }
605
606
607         $('holds_phone').value = holdArgs.recipient.day_phone();
608         appendClear( $('holds_email'), text(holdArgs.recipient.email()));
609
610         var pref = holdArgs.recipient.prefs[PREF_HOLD_NOTIFY];
611
612         if(pref) {
613                 if( ! pref.match(/email/i) ) {
614                         $('holds_enable_email').checked = false;
615                 } else {
616                         $('holds_enable_email').checked = true;
617                 }
618
619                 if( ! pref.match(/phone/i) ) {
620                         $('holds_phone').disabled = true;
621                         $('holds_enable_phone').checked = false;
622                 } else {
623                         $('holds_phone').disabled = false;
624                         $('holds_enable_phone').checked = true;
625                 }
626         }
627
628     if(!holdArgs.recipient.email()) {
629                 $('holds_enable_email').checked = false;        
630                 $('holds_enable_email').disabled = true;
631         var message;
632         if(isXUL()) {
633             message = noEmailMessageXUL.cloneNode(true);
634                 appendClear($('holds_email'), message);
635         } else {
636             message = noEmailMessage.cloneNode(true);
637                 appendClear($('holds_email'), message);
638             $('holds.no_email.my_account').setAttribute('href', buildOPACLink({page:MYOPAC},null,true));
639         }
640         unHideMe(message);
641     }
642
643         if(!$('holds_phone').value) 
644                 $('holds_enable_phone').checked = false;        
645
646         appendClear($('holds_physical_desc'), text(rec.physical_description()));
647
648         if(holdArgs.type == 'M') hideMe($('hold_physical_desc_row'));
649
650         holdsSetFormatSelector();
651
652     $('holds_frozen_chkbox').checked = false;
653     hideMe($('hold_frozen_thaw_row'));
654
655     var interval = fetchOrgSettingDefault(holdArgs.recipient.home_ou(), 'circ.hold_expire_interval');
656     var secs = 0;
657     if(interval) {
658         secs = interval_to_seconds(interval);
659         var expire = new Date();
660         expire.setTime(expire.getTime() + Number(secs + '000'));
661         dijit.byId('holds_expire_time').setValue(expire);
662     }
663 }
664
665 function holdsParseMRFormats(str) {
666         var data = str.split(/-/);      
667
668         var formats = [];
669         var mods_formats = [];
670
671         for( var i = 0; i < data[0].length; i++ ) {
672                 formats.push( data[0].charAt(i) );
673                 mods_formats.push( MARCFormatToMods( formats[i] ) );
674         }
675         
676         formats = uniquify(formats);
677         mods_formats = uniquify(mods_formats);
678
679         return {
680                 formats                 : formats,
681                 mods_formats    : mods_formats,
682                 lang                            : data[2],
683                 largeprint              : data[1]
684         };
685 }
686
687
688 function holdsSetFormatSelector() {
689         var type = holdArgs.type;
690         if( type == 'C' || type == 'V' || type == "I" || holdArgs.editHold ) return;
691
692         var data                                = holdsGetFormats();
693         var avail_formats       = data.avail_formats;
694         var sel_formats = data.sel_formats;
695         holdArgs.language = data.lang;
696         if( type=='M'){         
697                 hideMe($('holds_alt_formats_row_extras'));
698                 unHideMe($('holds_alt_formats_row'));   
699         }else{
700                 unHideMe($('holds_alt_formats_row_extras'));
701         }
702
703         var selector = $('hold_alt_form_selector');
704
705     for( var i = 0; i < selector.options.length; i++ ) {
706         if (selector.options[i].className.indexOf('hide_me') == -1)
707             hideMe(selector.options[i]);
708     }
709
710         for( var i = 0; i < avail_formats.length; i++ ) {
711                 var form = avail_formats[i];
712                 var opt = findFormatSelectorOptByParts(selector,form);
713         if (!opt) continue;
714                 if(type=='M') opt.selected=true;
715                 unHideMe(opt);
716         }
717
718     // If the user selects a format, P-type holds are no longer an option
719     // disable and reset the P-type form control
720     selector.onchange = function() {
721         var partsSel = $('holds_parts_selector');
722         for(var i = 0; i < selector.options.length; i++) {
723             if(selector.options[i].selected) {
724                 partsSel.selectedIndex = 0; // none selected
725                 partsSel.disabled = true;
726                 return;
727             }
728         }
729         partsSel.disabled = false;
730     }
731 }
732
733 function findFormatSelectorOptByParts( sel, val ) {
734     var parts = val.split('-');
735     for( var i = 0; i < sel.options.length; i++ ) {
736         var opt = sel.options[i];
737         var oval = opt.value;
738         var oparts = oval.split('-');
739         if( oparts[0].indexOf(parts[0]) > -1 && (!parts[1] || oparts[1].indexOf(parts[1]) > -1) ) return opt;
740     }
741     return null;
742 }
743
744 function holdsGetFormats() {
745
746         var lang;
747         var formats = [];
748         var sformats = []; // selected formats 
749
750         var type = holdArgs.type;
751         var desc = holdArgs.recordDescriptors;
752         var rec = holdArgs.record;
753         var mrec = holdArgs.metarecord;
754
755
756         if( type == 'T') {
757
758                 for( var i = 0; i < desc.length; i++ ) {
759                         var d = desc[i];
760                         if( d.record() == holdArgs.record ) {
761                                 lang = d.item_lang();
762                                 holdArgs.myFormat =  _t_f_2_format(d.item_type(), d.item_form());
763                                 sformats.push(holdArgs.myFormat);
764                                 break;
765                         }
766                 }
767
768         for( var i = 0; i < desc.length; i++ ) {
769                 var d = desc[i];
770                     if( type == 'T' && d.item_lang() != lang ) continue;
771                 formats.push( _t_f_2_format(d.item_type(), d.item_form()));
772             }
773
774         } else if( type =='M') {
775
776         // All available formats are selected by default in MR holds
777         for( var i = 0; i < desc.length; i++ ) {
778                 var d = desc[i];
779                     var _tmp_f = _t_f_2_format(d.item_type(), d.item_form());
780                 formats.push( _tmp_f );
781                 sformats.push( _tmp_f );
782         }
783         }
784
785         formats = uniquify(formats);
786         sformats = uniquify(sformats);
787
788         return {
789                 lang : lang,
790                 avail_formats : formats, 
791                 sel_formats : sformats
792         };
793 }
794
795
796
797 function _t_f_2_format(type, form) {
798         if( (type == 'a' || type == 't') && form == 's' ) return 'at-s';
799         if( form == 'd' ) return 'at-d';
800         return (type == 'a' || type == 't') ? 'at' : type;
801 }
802
803 function holdsSetSelectedFormats() {
804
805         var cn = $('holds_alt_formats_row').className;
806         if( cn && cn.match(/hide_me/) ) return;
807
808         var selector = $('hold_alt_form_selector');
809         var vals = getSelectedList(selector);
810
811         if(vals.length == 0) return;
812
813         var fstring = "";
814
815         if( contains(vals, 'at-d') || contains(vals, 'at-s')) {
816                 if( contains(vals, 'at') ) {
817                         fstring = 'at';
818                 } else if (contains(vals, 'at-s') && contains(vals, 'at-d')) {
819                         fstring = 'at-sd';
820                 } else if (!contains(vals, 'at-s')) {
821                         fstring = 'at-d';
822         } else {
823                         fstring = 'at-s';
824                 }
825         }
826
827         for( var i = 0; i < vals.length; i++ ) {
828                 var val = vals[i];
829                 if( !val.match(/at/) ) fstring = val + fstring;
830         }
831
832         if( holdArgs.language ) {
833                 if( fstring.match(/-/) )
834                         fstring = fstring + '-' + holdArgs.language;
835                 else
836                         fstring = fstring + '--' + holdArgs.language;
837         }
838
839
840         return fstring;
841 }
842
843
844 function holdsCheckPossibility(pickuplib, hold, recurse) {
845
846         var args = { 
847                 titleid : holdArgs.record,
848                 mrid : holdArgs.metarecord,
849                 volume_id : holdArgs.volume,
850                 issuanceid : holdArgs.issuance,
851                 copy_id : holdArgs.copy,
852                 hold_type : holdArgs.type,
853                 patronid : holdArgs.recipient.id(),
854                 depth : 0, 
855                 pickup_lib : pickuplib,
856         partid : holdArgs.part
857         };
858
859         if(recurse) {
860                 /* if we're calling create again (recursing), 
861                         we know that the hold possibility check already succeeded */
862                 holdHandleCreateResponse({_recurse:true, _hold:hold}, true );
863
864         } else {
865                 _debug("hold possible args = "+js2JSON(args));
866         
867                 var req = new Request(CHECK_HOLD_POSSIBLE, G.user.session, args );
868         
869                 req.request.alertEvent = false;
870                 req.request._hold = hold;
871                 req.request._recurse = recurse;
872                 req.callback(holdHandleCreateResponse);
873                 req.send();
874         }
875 }
876
877
878 function holdsBuildOrgSelector(node) {
879
880         if(!node) node = globalOrgTree;
881         if(!isTrue(node.opac_visible())) return;
882
883         var render_this_org = true;
884         var orgHiding = checkOrgHiding(); // value here is cached so not too painful with the recursion
885         if (orgHiding) {
886                 if (node.id() == globalOrgTree.id()) {
887                         node = orgHiding.org; // top of tree = org hiding context org
888                 }
889                 if ( ! orgIsMine( orgHiding.org, node, orgHiding.depth ) ) {
890                         render_this_org = false;
891                 }
892         }
893
894         if (render_this_org) {
895                 var selector = $('holds_org_selector');
896                 var index = selector.options.length;
897
898                 var type = findOrgType(node.ou_type());
899                 var indent = type.depth() - 1;
900
901                 var opt = setSelectorVal( selector, index, node.name(), node.id(), null, indent );
902                 if(!type.can_have_users()) {
903                         opt.disabled = true;
904                         addCSSClass(opt, 'disabled_option');
905                 }
906         }
907         
908         for( var i in node.children() ) {
909                 var child = node.children()[i];
910                 if(child) holdsBuildOrgSelector(child);
911         }
912 }
913
914 function holdsBuildHoldFromWindow() {
915
916         var org = getSelectorVal($('holds_org_selector'));
917         var node = findOrgUnit(org);
918         var ntype = findOrgType(node.ou_type());
919         if(!ntype.can_have_users()) {
920                 alertId('holds_pick_good_org');
921                 return;
922         }
923
924     fieldmapper.IDL.load(['ahr']);
925         var hold = new ahr();
926         if(holdArgs.editHold) {
927                 hold = holdArgs.editHold;
928                 holdArgs.editHold = null;
929         }
930
931         if( $('holds_enable_phone').checked ) {
932                 var phone = $('holds_phone').value;
933                 if( !phone || !phone.match(REGEX_PHONE) ) {
934                         alert($('holds_bad_phone').innerHTML);
935                         return null;
936                 }
937                 hold.phone_notify(phone);
938
939         } else {
940                 hold.phone_notify("");
941         }
942
943         if( $('holds_enable_email').checked ) 
944                 hold.email_notify(1);
945         else
946                 hold.email_notify(0);
947
948     var part = getSelectorVal($('holds_parts_selector'));
949     if(part) {
950         holdArgs.type = 'P';
951         holdArgs.part = part;
952     }
953
954         var target = holdArgs[holdTargetTypeMap[holdArgs.type]];
955
956     // a mono part is selected
957
958         hold.pickup_lib(org); 
959         //hold.request_lib(org); 
960         hold.requestor(holdArgs.requestor.id());
961         hold.usr(holdArgs.recipient.id());
962         hold.target(target);
963         hold.hold_type(holdArgs.type);
964
965     var expDate = dijit.byId('holds_expire_time').getValue();
966     if(expDate) {
967         var expireDate = dojo.date.stamp.toISOString(expDate);
968         expireDate = holdsVerifyThawDate(expireDate); 
969         if(expireDate)
970             hold.expire_time(expireDate);
971         else 
972             return;
973     }
974
975     // see if this hold should be frozen and for how long
976     if($('holds_frozen_chkbox').checked) {
977         hold.frozen('t');
978         unHideMe($('hold_frozen_thaw_row'));
979         var thawDate = dijit.byId('holds_frozen_thaw_input').attr('value');
980         if(thawDate) {
981             thawDate = dojo.date.stamp.toISOString(thawDate);
982             thawDate = holdsVerifyThawDate(thawDate); 
983             if(thawDate) 
984                 hold.thaw_date(thawDate);
985             else
986                 return;
987         } else {
988             hold.thaw_date(null);
989         }
990     } else {
991         hold.frozen('f');
992         hold.thaw_date(null);
993     }
994
995         //check for alternate hold formats 
996         var fstring = holdsSetSelectedFormats();
997         if(fstring) { 
998                 hold.hold_type('M'); 
999                 hold.holdable_formats(fstring);
1000                 hold.target(holdArgs.metarecord);
1001         }
1002         return hold;
1003 }
1004         
1005 function holdsPlaceHold(hold, recurse) {
1006         if(!hold) return;
1007         swapCanvas($('check_holds_box'));
1008         holdsCheckPossibility(hold.pickup_lib(), hold, recurse);
1009 }
1010
1011
1012 function holdHandleCreateResponse(r, recurse) {
1013
1014         if(!recurse) {
1015                 var res = r.getResultObject();
1016                 if(checkILSEvent(res) || res.success != 1) {
1017                         if(res.success != 1) {
1018                                 alert($('hold_not_allowed').innerHTML);
1019                         } else {
1020                                 if( res.textcode == 'PATRON_BARRED' ) {
1021                                         alertId('hold_failed_patron_barred');
1022                         } else {
1023                                         alert($('hold_not_allowed').innerHTML);
1024                                 }
1025                         }
1026                         swapCanvas($('holds_box'));
1027                         return;
1028                 }
1029         r._hold.selection_depth(res.depth);
1030         }       
1031
1032         holdCreateHold(r._recurse, r._hold);
1033 }
1034
1035
1036 function holdCreateHold( recurse, hold ) {
1037         var method = CREATE_HOLD;
1038         if(recurse) method = CREATE_HOLD_OVERRIDE;
1039         var req = new Request( method, holdArgs.requestor.session, hold );
1040         req.request.alertEvent = false;
1041         req.send(true);
1042         var res = req.result();
1043         holdProcessResult(hold, res, recurse);
1044         
1045         showCanvas();
1046
1047         runEvt('common', 'holdUpdated');
1048 }
1049
1050
1051 function holdProcessResult( hold, res, recurse ) {
1052
1053         if( res && res > -1 ) {
1054                 alert($('holds_success').innerHTML);
1055                 holdArgs = null;
1056         if(isXUL() && typeof xulG.opac_hold_placed == 'function')
1057             xulG.opac_hold_placed(res);
1058
1059         } else {
1060
1061                 if( recurse ) {
1062                         alert($('holds_failure').innerHTML);
1063                         return;
1064                 }
1065
1066                 if( grep(res, function(e) { return (e.textcode == 'HOLD_EXISTS'); }) ) {
1067                         if( fetchPermOrgs('HOLD_EXISTS.override')[0] != -1 ) {
1068                                 if( confirm($('hold_dup_exists_override').innerHTML) ) {
1069                                         return holdsPlaceHold(hold, true);
1070                                 }
1071                 return;
1072
1073                         } else {
1074                                 return alert($('hold_dup_exists').innerHTML);
1075                         }
1076                 }
1077
1078                 if( grep(res, function(e) { return (e.textcode == 'HOLD_ITEM_CHECKED_OUT'); }) ) {
1079                         if( fetchPermOrgs('HOLD_ITEM_CHECKED_OUT.override')[0] != -1 ) {
1080                                 if( confirm($('hold_checked_out_override').innerHTML) ) {
1081                                         return holdsPlaceHold(hold, true);
1082                                 }
1083                 return;
1084
1085                         } else {
1086                                 return alert($('hold_checked_out').innerHTML);
1087                         }
1088                 }
1089
1090
1091                 alert($('holds_failure').innerHTML);
1092         }
1093 }
1094
1095
1096 function holdsCancel(holdid, user) {
1097         if(!user) user = G.user;
1098         var req = new Request(CANCEL_HOLD, user.session, holdid, /* Patron via OPAC */ 6);
1099         req.send(true);
1100         return req.result();
1101         runEvt('common', 'holdUpdated');
1102 }
1103
1104 function holdsUpdate(hold, user) {
1105         if(!user) user = G.user;
1106         var req = new Request(UPDATE_HOLD, user.session, hold);
1107         req.send(true);
1108         var x = req.result(); // cause an exception if there is one 
1109         runEvt('common', 'holdUpdated');
1110 }
1111
1112 /* verify that the thaw date is valid and after today */
1113 function holdsVerifyThawDate(dateString, isGreater) {
1114     thawDate = dojo.date.stamp.fromISOString(dateString);
1115     if(thawDate) {
1116         if(isGreater) {
1117             if(dojo.date.compare(thawDate) > 0) {
1118                 return dojo.date.stamp.toISOString(thawDate);
1119             }
1120         } else {
1121             return dojo.date.stamp.toISOString(thawDate);
1122         }
1123     }
1124     return null;
1125 }
1126
1127 function holdsVerifyThawDateUI(element) {
1128     value = dojo.date.stamp.toISOString(dijit.byId(element).getValue());
1129
1130     if(!value) {
1131         removeCSSClass($(element), 'invalid_field');
1132         return;
1133     }
1134
1135     if(!holdsVerifyThawDate(value, true)) {
1136         addCSSClass($(element), 'invalid_field');
1137     } else {
1138         removeCSSClass($(element), 'invalid_field');
1139     }
1140 }
1141