Initial dev repository
[kcls-web.git] / js / ui / kcls / circ / selfcheck / selfcheck.js
1 dojo.require('dojo.date.locale');\r
2 dojo.require('dojo.date.stamp');\r
3 dojo.require('dijit.form.CheckBox');\r
4 dojo.require('dijit.form.NumberSpinner');\r
5 dojo.require('openils.CGI');\r
6 dojo.require('openils.Util');\r
7 dojo.require('openils.User');\r
8 dojo.require('openils.Event');\r
9 dojo.require('openils.widget.ProgressDialog');\r
10 dojo.require('openils.widget.OrgUnitFilteringSelect');\r
11 \r
12 dojo.requireLocalization('openils.circ', 'selfcheck');\r
13 var localeStrings = dojo.i18n.getLocalization('openils.circ', 'selfcheck');\r
14 var selfCheckMgr;\r
15 var itemsOutCirc = [];\r
16 var itemsOutMod = [];\r
17 var itemsOutCopy = [];\r
18 var TIMEOUT = 45; // logout timer\r
19 \r
20 \r
21 const SET_BARCODE_REGEX = 'opac.barcode_regex';\r
22 const SET_PATRON_TIMEOUT = 'circ.selfcheck.patron_login_timeout';\r
23 const SET_AUTO_OVERRIDE_EVENTS = 'circ.selfcheck.auto_override_checkout_events';\r
24 const SET_PATRON_PASSWORD_REQUIRED = 'circ.selfcheck.patron_password_required';\r
25 const SET_AUTO_RENEW_INTERVAL = 'circ.checkout_auto_renew_age';\r
26 const SET_WORKSTATION_REQUIRED = 'circ.selfcheck.workstation_required';\r
27 const SET_ALERT_POPUP = 'circ.selfcheck.alert.popup';\r
28 const SET_ALERT_SOUND = 'circ.selfcheck.alert.sound';\r
29 const SET_CC_PAYMENT_ALLOWED = 'credit.payments.allow';\r
30 // This setting only comes into play if COPY_NOT_AVAILABLE is in the SET_AUTO_OVERRIDE_EVENTS list\r
31 const SET_BLOCK_CHECKOUT_ON_COPY_STATUS = 'circ.selfcheck.block_checkout_on_copy_status';\r
32 \r
33 function SelfCheckManager() {\r
34         selfCheckMgr = this;\r
35         switchTo('step1');\r
36         \r
37         this.timer = null;\r
38     this.cgi = new openils.CGI();\r
39     this.staff = null; \r
40     this.workstation = null;\r
41     this.authtoken = null;\r
42 \r
43     this.patron = null; \r
44     this.patronBarcodeRegex = null;\r
45 \r
46     this.checkouts = [];\r
47     this.itemsOut = [];\r
48 \r
49     // During renewals, keep track of the ID of the previous circulation. \r
50     // Previous circ is used for tracking failed renewals (for receipts).\r
51     this.prevCirc = null;\r
52 \r
53     // current item barcode\r
54     this.itemBarcode = null; \r
55 \r
56     // are we currently performing a renewal?\r
57     this.isRenewal = false; \r
58 \r
59     // dict of org unit settings for "here"\r
60     this.orgSettings = {};\r
61 \r
62     // Construct a mock checkout for debugging purposes\r
63     if(this.mockCheckouts = this.cgi.param('mock-circ')) {\r
64 \r
65         this.mockCheckout = {\r
66             payload : {\r
67                 record : new fieldmapper.mvr(),\r
68                 copy : new fieldmapper.acp(),\r
69                 circ : new fieldmapper.circ()\r
70             }\r
71         };\r
72 \r
73         this.mockCheckout.payload.record.title('Jazz improvisation for guitar');\r
74         this.mockCheckout.payload.record.author('Wise, Les');\r
75         this.mockCheckout.payload.record.isbn('0634033565');\r
76         this.mockCheckout.payload.copy.barcode('123456789');\r
77         this.mockCheckout.payload.circ.renewal_remaining(1);\r
78         this.mockCheckout.payload.circ.parent_circ(1);\r
79         this.mockCheckout.payload.circ.due_date('2012-12-21');\r
80     }\r
81 \r
82     this.initPrinter();\r
83 }\r
84 \r
85 SelfCheckManager.prototype.keepMeLoggedIn = function() {\r
86         //alert(this.timer);\r
87         if(this.timer) try {clearTimeout(this.timer)} catch(e){}\r
88         this.timer = setTimeout('selfCheckMgr.logoutPatron();', TIMEOUT*1000);\r
89 }\r
90 \r
91 /**\r
92  * Fetch the org-unit settings, initialize the display, etc.\r
93  */\r
94 SelfCheckManager.prototype.init = function() {\r
95     this.staff = openils.User.user;\r
96     this.workstation = openils.User.workstation;\r
97     this.authtoken = openils.User.authtoken;\r
98     this.loadOrgSettings();\r
99 \r
100     this.circTbody = dojo.byId('oils-selfck-circ-tbody');\r
101     this.itemsOutTbody = dojo.byId('oils-selfck-circ-out-tbody');\r
102 \r
103     // workstation is required but none provided\r
104     if(this.orgSettings[SET_WORKSTATION_REQUIRED] && !this.workstation) {\r
105         if(confirm(dojo.string.substitute(localeStrings.WORKSTATION_REQUIRED))) {\r
106             this.registerWorkstation();\r
107         }\r
108         return;\r
109     }\r
110     \r
111     var self = this;\r
112     // connect onclick handlers to the various navigation links\r
113     var linkHandlers = {\r
114         'oils-selfck-hold-details-link' : function() { self.drawHoldsPage(true); },\r
115         'oils-selfck-view-fines-link' : function() { self.drawFinesPage(); openils.Util.show('oils-selfck-fines-tbody'); openils.Util.hide('pay_fines'); },\r
116         'oils-selfck-pay-fines-link' : function() {\r
117             switchTo('step3','step3c');\r
118                         openils.Util.hide('oils-selfck-fines-tbody');\r
119                         openils.Util.show('pay_fines');\r
120                         self.keepMeLoggedIn();\r
121             self.drawPayFinesPage(\r
122                 self.patron,\r
123                 self.getSelectedFinesTotal(),\r
124                 self.getSelectedFineTransactions(),\r
125                 function(resp) {\r
126                     var evt = openils.Event.parse(resp);\r
127                     if(evt) {\r
128                         var message = evt + '';\r
129                         if(evt.textcode == 'CREDIT_PROCESSOR_DECLINED_TRANSACTION' && evt.payload)\r
130                             message += '\n' + evt.payload.error_message;\r
131                         self.handleAlert(message, true, 'payment-failure');\r
132                         return;\r
133                     }\r
134                                         self.patron.last_xact_id(resp.last_xact_id);\r
135                     self.printPaymentReceipt(\r
136                         resp,\r
137                         function() {\r
138                             self.updateFinesSummary();\r
139                             self.drawFinesPage();\r
140                         }\r
141                     );\r
142                 }\r
143             );\r
144         },\r
145         //'oils-selfck-nav-home' : function() { self.drawCircPage(); },\r
146         'oils-selfck-nav-logout' : function() { self.logoutPatron(); },\r
147         'oils-selfck-nav-logout-print' : function() { self.logoutPatron(true); },\r
148         'oils-selfck-items-out-details-link' : function() { self.drawItemsOutPage(); },\r
149         //'oils-selfck-print-list-link' : function() { self.printList(); }\r
150     }\r
151 \r
152     for(var id in linkHandlers) {\r
153                 //var obj1 = dojo.byId(id);\r
154                 //obj1.onclick = linkHandlers[id];\r
155         dojo.connect(dojo.byId(id), 'onclick', linkHandlers[id]);\r
156         }\r
157 \r
158 \r
159     if(this.cgi.param('patron')) {\r
160         \r
161         // Patron barcode via cgi param.  Mainly used for debugging and\r
162         // only works if password is not required by policy\r
163         this.loginPatron(this.cgi.param('patron'));\r
164 \r
165     } else {\r
166         this.drawLoginPage();\r
167     }\r
168 \r
169     /**\r
170      * To test printing, pass a URL param of 'testprint'.  The value for the param\r
171      * should be a JSON string like so:  [{circ:<circ_id>}, ...]\r
172      */\r
173     var testPrint = this.cgi.param('testprint');\r
174     if(testPrint) {\r
175         this.checkouts = JSON2js(testPrint);\r
176         this.printSessionReceipt();\r
177         this.checkouts = [];\r
178     }\r
179 }\r
180 \r
181 \r
182 SelfCheckManager.prototype.getSelectedFinesTotal = function() {\r
183     var total = 0;\r
184     dojo.forEach(\r
185         dojo.query("[name=selector]", this.finesTbody),\r
186         function(input) {\r
187             if(input.checked)\r
188                 total += Number(input.balance_owed);\r
189         }\r
190     );\r
191     return total.toFixed(2);\r
192 };\r
193 \r
194 SelfCheckManager.prototype.getSelectedFineTransactions = function() {\r
195     return dojo.query("[name=selector]", this.finesTbody).\r
196         filter(function (o) { return o.checked }).\r
197         map(\r
198             function (o) {\r
199                 return [\r
200                     o.getAttribute("xact"),\r
201                     Number(o.balance_owed).toFixed(2)\r
202                 ];\r
203             }\r
204         );\r
205 };\r
206 \r
207 /**\r
208  * Registers a new workstion\r
209  */\r
210 SelfCheckManager.prototype.registerWorkstation = function() {\r
211     \r
212     oilsSelfckWsDialog.show();\r
213 \r
214     new openils.User().buildPermOrgSelector(\r
215         'REGISTER_WORKSTATION', \r
216         oilsSelfckWsLocSelector, \r
217         this.staff.home_ou()\r
218     );\r
219 \r
220 \r
221     var self = this;\r
222     dojo.connect(oilsSelfckWsSubmit, 'onClick', \r
223 \r
224         function() {\r
225             oilsSelfckWsDialog.hide();\r
226             var name = oilsSelfckWsLocSelector.attr('displayedValue') + '-' + oilsSelfckWsName.attr('value');\r
227 \r
228             var res = fieldmapper.standardRequest(\r
229                 ['open-ils.actor', 'open-ils.actor.workstation.register'],\r
230                 { params : [\r
231                         self.authtoken, name, oilsSelfckWsLocSelector.attr('value')\r
232                     ]\r
233                 }\r
234             );\r
235 \r
236             if(evt = openils.Event.parse(res)) {\r
237                 if(evt.textcode == 'WORKSTATION_NAME_EXISTS') {\r
238                     if(confirm(localeStrings.WORKSTATION_EXISTS)) {\r
239                         location.href = location.href.replace(/\?.*/, '') + '?ws=' + name;\r
240                     } else {\r
241                         self.registerWorkstation();\r
242                     }\r
243                     return;\r
244                 } else {\r
245                     alert(evt);\r
246                 }\r
247             } else {\r
248                 location.href = location.href.replace(/\?.*/, '') + '?ws=' + name;\r
249             }\r
250         }\r
251     );\r
252 }\r
253 \r
254 /**\r
255  * Loads the org unit settings\r
256  */\r
257 SelfCheckManager.prototype.loadOrgSettings = function() {\r
258 \r
259     var settings = fieldmapper.aou.fetchOrgSettingBatch(\r
260         this.staff.ws_ou(), [\r
261             SET_BARCODE_REGEX,\r
262             SET_PATRON_TIMEOUT,\r
263             SET_ALERT_POPUP,\r
264             SET_ALERT_SOUND,\r
265             SET_AUTO_OVERRIDE_EVENTS,\r
266             SET_BLOCK_CHECKOUT_ON_COPY_STATUS,\r
267             SET_PATRON_PASSWORD_REQUIRED,\r
268             SET_AUTO_RENEW_INTERVAL,\r
269             SET_WORKSTATION_REQUIRED,\r
270             SET_CC_PAYMENT_ALLOWED\r
271         ]\r
272     );\r
273 \r
274     for(k in settings) {\r
275         if(settings[k])\r
276             this.orgSettings[k] = settings[k].value;\r
277     }\r
278 \r
279     if(settings[SET_BARCODE_REGEX]) \r
280         this.patronBarcodeRegex = new RegExp(settings[SET_BARCODE_REGEX].value);\r
281 }\r
282 \r
283 SelfCheckManager.prototype.drawLoginPage = function() {\r
284     var self = this;\r
285     var bcHandler = function(barcode) {\r
286         // handle patron barcode entry\r
287 \r
288         if(self.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {\r
289             \r
290             // password is required.  wire up the scan box to read it\r
291             self.updateScanBox({\r
292                 msg : 'Please enter your password', // TODO i18n \r
293                 handler : function(pw) { self.loginPatron(barcode, pw); },\r
294                 password : true\r
295             });\r
296 \r
297         } else {\r
298             // password is not required, go ahead and login\r
299             self.loginPatron(barcode);\r
300         }\r
301     };\r
302 \r
303     this.updateScanBox({\r
304         msg : 'Please log in with your library barcode.', // TODO\r
305         handler : bcHandler\r
306     });\r
307         \r
308         var txtBox = (dojo.byId('step2').style.display=='none') ? 'patron-login-username' : 'patron-login-password';\r
309         try{var a=dojo.byId(txtBox);a.focus();a.select();}catch(e){}\r
310 }\r
311 \r
312 /**\r
313  * Login the patron.  \r
314  */\r
315 SelfCheckManager.prototype.loginPatron = function(barcode, passwd) {\r
316         \r
317     //if(this.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) { // password always reqired, per KCLS - fail safe\r
318         if(!passwd) {\r
319             // would only happen in dev/debug mode when using the patron= param\r
320             alert('password required by org setting.  remove patron= from URL'); \r
321             return;\r
322         }\r
323 \r
324         // patron password is required.  Verify it.\r
325 \r
326         var res = fieldmapper.standardRequest(\r
327             ['open-ils.actor', 'open-ils.actor.verify_user_password'],\r
328             {params : [this.authtoken, barcode, null, hex_md5(passwd)]}\r
329         );\r
330 \r
331         if(res == 0) {\r
332             // user-not-found results in login failure\r
333             this.handleAlert(\r
334                 dojo.string.substitute(localeStrings.LOGIN_FAILED, [barcode]),\r
335                 false, 'login-failure'\r
336             );\r
337             this.drawLoginPage();\r
338                         openils.Util.show('back_to_login');\r
339             return;\r
340         }\r
341     //} \r
342 \r
343     // retrieve the fleshed user by barcode\r
344     this.patron = fieldmapper.standardRequest(\r
345         ['open-ils.actor', 'open-ils.actor.user.fleshed.retrieve_by_barcode'],\r
346         {params : [this.authtoken, barcode]}\r
347     );\r
348 \r
349     var evt = openils.Event.parse(this.patron);\r
350     if(evt) {\r
351         this.handleAlert(\r
352             dojo.string.substitute(localeStrings.LOGIN_FAILED, [barcode]),\r
353             false, 'login-failure'\r
354         );\r
355         this.drawLoginPage();\r
356                 openils.Util.show('back_to_login');\r
357 \r
358     } else {\r
359 \r
360         //this.handleAlert('', true, 'login-success');\r
361         dojo.byId('user_name').innerHTML = \r
362             dojo.string.substitute(localeStrings.WELCOME_BANNER, [this.patron.first_given_name()]);\r
363                 dojo.byId('oils-selfck-status-div').innerHTML = '';\r
364                 dojo.byId('oils-selfck-status-div2').innerHTML = '';\r
365                 dojo.byId('oils-selfck-status-div3').innerHTML = '';\r
366                 openils.Util.hide('back_to_login');\r
367         this.drawCircPage();\r
368     }\r
369 }\r
370 \r
371 \r
372 SelfCheckManager.prototype.handleAlert = function(message, shouldPopup, sound) {\r
373     console.log("Handling alert " + message);\r
374 \r
375     dojo.byId('oils-selfck-status-div').innerHTML = message;\r
376         if(!this.patron){\r
377                 dojo.byId('oils-selfck-status-div2').innerHTML = message;\r
378                 dojo.byId('oils-selfck-status-div3').innerHTML = message;\r
379         }\r
380         \r
381     if(shouldPopup && this.orgSettings[SET_ALERT_POPUP]) \r
382         alert(message);\r
383 \r
384     if(this.orgSettings[SET_ALERT_SOUND])\r
385         openils.Util.playAudioUrl(SelfCheckManager.audioConfig[sound]);\r
386 }\r
387 \r
388 \r
389 /**\r
390  * Manages the main input box\r
391  * @param msg The context message to display with the box\r
392  * @param clearOnly Don't update the context message, just clear the value and re-focus\r
393  * @param handler Optional "on-enter" handler.  \r
394  */\r
395 SelfCheckManager.prototype.updateScanBox = function(args) {\r
396     args = args || {};\r
397 \r
398     if(args.select) {\r
399         selfckScanBox.domNode.select();\r
400     } else {\r
401         selfckScanBox.attr('value', '');\r
402     }\r
403 \r
404     if(args.password) {\r
405         selfckScanBox.domNode.setAttribute('type', 'password');\r
406     } else {\r
407         selfckScanBox.domNode.setAttribute('type', '');\r
408     }\r
409 \r
410     if(args.value)\r
411         selfckScanBox.attr('value', args.value);\r
412 \r
413     if(args.msg) \r
414         dojo.byId('oils-selfck-scan-text').innerHTML = args.msg;\r
415 \r
416     if(selfckScanBox._lastHandler && (args.handler || args.clearHandler)) {\r
417         dojo.disconnect(selfckScanBox._lastHandler);\r
418     }\r
419 \r
420     if(args.handler) {\r
421 \r
422         selfckScanBox._lastHandler = dojo.connect(\r
423             selfckScanBox, \r
424             'onKeyDown', \r
425             function(e) {\r
426                 if(e.keyCode != dojo.keys.ENTER) \r
427                     return;\r
428                 args.handler(selfckScanBox.attr('value'));\r
429             }\r
430         );\r
431     }\r
432 \r
433     selfckScanBox.focus();\r
434 }\r
435 \r
436 /**\r
437  *  Sets up the checkout/renewal interface\r
438  */\r
439 SelfCheckManager.prototype.drawCircPage = function() {\r
440         this.keepMeLoggedIn();\r
441     openils.Util.show('oils-selfck-circ-tbody', 'table-row-group');\r
442         switchTo('step3');\r
443 \r
444     var self = this;\r
445     this.updateScanBox({\r
446         msg : 'Please enter an item barcode', // TODO i18n\r
447         handler : function(barcode) { \r
448                 openils.Util.show('oils-selfck-fines-tbody'); \r
449                 openils.Util.hide('pay_fines'); switchTo('step3'); \r
450                 self.checkout(barcode); }\r
451     });\r
452 \r
453     if(!this.circTemplate)\r
454         this.circTemplate = this.circTbody.removeChild(dojo.byId('oils-selfck-circ-row'));\r
455 \r
456     // fines summary\r
457     this.updateFinesSummary();\r
458 \r
459     // holds summary\r
460     this.updateHoldsSummary();\r
461 \r
462     // items out summary\r
463     this.updateCircSummary();\r
464 \r
465     // render mock checkouts for debugging?\r
466     if(this.mockCheckouts) {\r
467         for(var i in [1,2,3]) \r
468             this.displayCheckout(this.mockCheckout, 'checkout');\r
469     }\r
470 }\r
471 \r
472 \r
473 SelfCheckManager.prototype.updateFinesSummary = function() {\r
474     var self = this; \r
475 \r
476     // fines summary\r
477     fieldmapper.standardRequest(\r
478         ['open-ils.actor', 'open-ils.actor.user.fines.summary'],\r
479         {   async : true,\r
480             params : [this.authtoken, this.patron.id()],\r
481             oncomplete : function(r) {\r
482                 var summary = openils.Util.readResponse(r);\r
483                                 var finesSum = dojo.byId('acct_fines');\r
484                                 var bal = summary.balance_owed();\r
485                                 var bal2 = parseFloat(bal);\r
486                                 \r
487                                 if(bal2>0) {finesSum.style.color="red"; openils.Util.show('oils-selfck-pay-fines-link');}\r
488                 finesSum.innerHTML = dojo.string.substitute(localeStrings.TOTAL_FINES_ACCOUNT, [bal2.toFixed(2)]);\r
489                 self.creditPayableBalance = bal2+'';\r
490             }\r
491         }\r
492     );\r
493 }\r
494 \r
495 \r
496 SelfCheckManager.prototype.drawItemsOutPage = function() {\r
497         this.keepMeLoggedIn();\r
498         switchTo('step3','step3d');\r
499 \r
500         if(!this.outTemplate)\r
501         this.outTemplate = this.itemsOutTbody.removeChild(dojo.byId('oils-selfck-circ-out-row'));\r
502     while(this.itemsOutTbody.childNodes[0])\r
503         this.itemsOutTbody.removeChild(this.itemsOutTbody.childNodes[0]);\r
504 \r
505     progressDialog.show(true);\r
506     var self = this;\r
507         \r
508     fieldmapper.standardRequest(\r
509         ['open-ils.circ', 'open-ils.circ.actor.user.checked_out.atomic'],\r
510         {\r
511             async : true,\r
512             params : [this.authtoken, this.patron.id()],\r
513             oncomplete : function(r) {\r
514                 var resp = openils.Util.readResponse(r);\r
515 \r
516                 var circs = resp.sort(\r
517                     function(a, b) {\r
518                         if(a.circ.due_date() > b.circ.due_date())\r
519                             return -1;\r
520                         return 1;\r
521                     }\r
522                 );\r
523 \r
524                 self.itemsOut = [];\r
525                 dojo.forEach(circs,\r
526                     function(circ) {\r
527                         self.itemsOut.push(circ.circ.id());\r
528                         handleCheckedItems(circ);\r
529                     }\r
530                 );\r
531                                 progressDialog.hide();\r
532             }\r
533         }\r
534     );\r
535 }\r
536 \r
537 function handleCheckedItems(circ) {\r
538         var self = selfCheckMgr;\r
539         var row = self.outTemplate.cloneNode(true);\r
540         \r
541         self.byName(row,'barcode').innerHTML = circ.copy.barcode();\r
542         self.byName(row,'title').innerHTML = circ.record.title();\r
543         self.byName(row,'author').innerHTML = circ.record.author();\r
544         if(dojo.date.stamp.fromISOString(circ.circ.due_date())<(new Date())) self.byName(row,'due_date').style.color="red";\r
545         self.byName(row,'due_date').innerHTML = dojo.date.locale.format(dojo.date.stamp.fromISOString(circ.circ.due_date()), {selector: 'date', fullYear: true});\r
546         self.byName(row,'format').innerHTML = circ.record.types_of_resource()[0];\r
547         \r
548         self.itemsOutTbody.appendChild(row);\r
549 }\r
550 \r
551 SelfCheckManager.prototype.goToTab = function(name) {\r
552     this.tabName = name;\r
553 \r
554     openils.Util.hide('oils-selfck-fines-page');\r
555     openils.Util.hide('oils-selfck-payment-page');\r
556     openils.Util.hide('oils-selfck-holds-page');\r
557     openils.Util.hide('oils-selfck-circ-page');\r
558     openils.Util.hide('oils-selfck-pay-fines-link');\r
559     \r
560     switch(name) {\r
561         case 'checkout':\r
562             openils.Util.show('oils-selfck-circ-page');\r
563             break;\r
564         case 'items_out':\r
565             openils.Util.show('oils-selfck-circ-page');\r
566             break;\r
567         case 'holds':\r
568             openils.Util.show('oils-selfck-holds-page');\r
569             break;\r
570         case 'fines':\r
571             openils.Util.show('oils-selfck-fines-page');\r
572             break;\r
573         case 'payment':\r
574             openils.Util.show('oils-selfck-payment-page');\r
575             break;\r
576     }\r
577 }\r
578 \r
579 \r
580 SelfCheckManager.prototype.printList = function(which) {\r
581         this.keepMeLoggedIn();\r
582     switch(which) {\r
583         case 'checkout':\r
584             this.printSessionReceipt();\r
585             break;\r
586         case 'items_out':\r
587             this.printItemsOutReceipt();\r
588             break;\r
589         case 'holds':\r
590             this.printHoldsReceipt();\r
591             break;\r
592         case 'fines':\r
593             this.printFinesReceipt();\r
594             break;\r
595     }\r
596 }\r
597 \r
598 SelfCheckManager.prototype.updateHoldsSummary = function() {\r
599     if(!this.holdsSummary) {\r
600         var summary = fieldmapper.standardRequest(\r
601             ['open-ils.circ', 'open-ils.circ.holds.user_summary'],\r
602             {params : [this.authtoken, this.patron.id()]}\r
603         );\r
604 \r
605         this.holdsSummary = {};\r
606         this.holdsSummary.ready = Number(summary['4']);\r
607         this.holdsSummary.total = 0;\r
608 \r
609         for(var i in summary)\r
610             this.holdsSummary.total += Number(summary[i]);\r
611     }\r
612 \r
613     dojo.byId('oils-selfck-holds-total').innerHTML =dojo.string.substitute("${0}) Item"+(this.holdsSummary.total==1?"":"s"),[this.holdsSummary.total]);\r
614     dojo.byId('oils-selfck-holds-ready').innerHTML =dojo.string.substitute("${0}) Item"+(this.holdsSummary.ready==1?"":"s"),[this.holdsSummary.ready]);\r
615 }\r
616 \r
617 \r
618 SelfCheckManager.prototype.updateCircSummary = function(increment) {\r
619     if(!this.circSummary) {\r
620 \r
621         var summary = fieldmapper.standardRequest(\r
622             ['open-ils.actor', 'open-ils.actor.user.checked_out.count'],\r
623             {params : [this.authtoken, this.patron.id()]}\r
624         );\r
625 \r
626         this.circSummary = {\r
627             total : Number(summary.out) + Number(summary.overdue),\r
628             overdue : Number(summary.overdue),\r
629             session : 0\r
630         };\r
631     }\r
632 \r
633     if(increment) {\r
634         // local checkout occurred.  Add to the total and the session.\r
635         this.circSummary.total += 1;\r
636         this.circSummary.session += 1;\r
637     }\r
638 \r
639     dojo.byId('oils-selfck-circ-account-total').innerHTML = dojo.string.substitute("${0}) Item"+(this.circSummary.total==1?"":"s"), [this.circSummary.total]);\r
640 \r
641     /*\r
642         dojo.byId('oils-selfck-circ-session-total').innerHTML = \r
643         dojo.string.substitute(\r
644             localeStrings.TOTAL_ITEMS_SESSION, \r
645             [this.circSummary.session]\r
646         );\r
647         */\r
648 }\r
649 \r
650 \r
651 SelfCheckManager.prototype.drawHoldsPage = function(bool) {\r
652         this.keepMeLoggedIn();\r
653         if(bool) switchTo('step3','step3f'); else switchTo('step3','step3e');\r
654 \r
655     this.holdTbody = dojo.byId('oils-selfck-hold-tbody');\r
656         this.readyTbody = dojo.byId('oils-selfck-rdy-tbody');\r
657         if(!this.readyTemplate)\r
658         this.readyTemplate = this.readyTbody.removeChild(dojo.byId('oils-selfck-rdy-row'));\r
659     if(!this.holdTemplate)\r
660         this.holdTemplate = this.holdTbody.removeChild(dojo.byId('oils-selfck-hold-row'));\r
661     while(this.holdTbody.childNodes[0])\r
662         this.holdTbody.removeChild(this.holdTbody.childNodes[0]);\r
663         while(this.readyTbody.childNodes[0])\r
664         this.readyTbody.removeChild(this.readyTbody.childNodes[0]);\r
665 \r
666     progressDialog.show(true);\r
667 \r
668     var self = this;\r
669     fieldmapper.standardRequest( // fetch the hold IDs\r
670 \r
671         ['open-ils.circ', 'open-ils.circ.holds.id_list.retrieve'],\r
672         {   async : true,\r
673             params : [this.authtoken, this.patron.id()],\r
674 \r
675             oncomplete : function(r) { \r
676                 var ids = openils.Util.readResponse(r);\r
677                 if(!ids || ids.length == 0) {\r
678                     progressDialog.hide();\r
679                     return;\r
680                 }\r
681 \r
682                 fieldmapper.standardRequest( // fetch the hold objects with fleshed details\r
683                     ['open-ils.circ', 'open-ils.circ.hold.details.batch.retrieve'],\r
684                     {   async : true,\r
685                         params : [self.authtoken, ids],\r
686 \r
687                         onresponse : function(rr) {\r
688                                                         progressDialog.hide(); \r
689                             self.drawHolds(openils.Util.readResponse(rr));\r
690                         }\r
691                     }\r
692                 );\r
693             }\r
694         }\r
695     );\r
696 }\r
697 \r
698 /**\r
699  * Fetch and add a single hold to the list of holds\r
700  */\r
701 SelfCheckManager.prototype.drawHolds = function(holds) {\r
702         //this.keepMeLoggedIn();\r
703     this.holds = holds;\r
704     progressDialog.hide();\r
705         \r
706         var data = holds;\r
707         if(!data) return;\r
708         var row = this.holdTemplate.cloneNode(true);\r
709         var row2 = this.readyTemplate.cloneNode(true);\r
710 \r
711         //if(data.mvr.isbn()) {\r
712         //    this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + data.mvr.isbn());\r
713         //}\r
714         \r
715         if(data.status == 4) {\r
716                 this.byName(row2, 'title').innerHTML = data.mvr.title();\r
717                 this.byName(row2, 'format').innerHTML = data.mvr.types_of_resource()[0];\r
718                 this.byName(row2, 'lib').innerHTML = fieldmapper.aou.findOrgUnit(data.hold.pickup_lib()).name();\r
719                 if(dojo.date.stamp.fromISOString(data.hold.capture_time())<(new Date())) this.byName(row2, 'date').style.color="red";\r
720                 this.byName(row2, 'date').innerHTML = dojo.date.locale.format(dojo.date.stamp.fromISOString(data.hold.capture_time()), {selector: 'date', fullYear: true});\r
721                 this.readyTbody.appendChild(row2);\r
722         } else {\r
723 \r
724                 this.byName(row, 'title').innerHTML = data.mvr.title();\r
725                 this.byName(row, 'author').innerHTML = data.mvr.author();\r
726                 this.byName(row, 'format').innerHTML = data.mvr.types_of_resource()[0];\r
727 \r
728                         // hold is still pending\r
729                 this.byName(row, 'status').innerHTML = dojo.string.substitute(localeStrings.HOLD_STATUS_WAITING,[data.queue_position, data.potential_copies]);\r
730                 this.holdTbody.appendChild(row);\r
731         }\r
732 }\r
733 \r
734 \r
735 SelfCheckManager.prototype.drawFinesPage = function() {\r
736         this.keepMeLoggedIn();\r
737     // TODO add option to hid scanBox\r
738     // this.updateScanBox(...)\r
739 \r
740     //this.goToTab('fines');\r
741         switchTo('step3','step3c');\r
742     progressDialog.show(true);\r
743 \r
744     //if(this.creditPayableBalance > 0 && this.orgSettings[SET_CC_PAYMENT_ALLOWED])\r
745     //  openils.Util.show('oils-selfck-pay-fines-link', 'inline');\r
746     \r
747 \r
748     this.finesTbody = dojo.byId('oils-selfck-fines-tbody');\r
749     if(!this.finesTemplate)\r
750         this.finesTemplate = this.finesTbody.removeChild(dojo.byId('oils-selfck-fines-row'));\r
751     while(this.finesTbody.childNodes[0])\r
752         this.finesTbody.removeChild(this.finesTbody.childNodes[0]);\r
753 \r
754 /*\r
755     // when user clicks on a selector checkbox, update the total owed\r
756     var updateSelected = function() {\r
757         var total = 0;\r
758         dojo.forEach(\r
759             dojo.query('[name=selector]', this.finesTbody),\r
760             function(input) {\r
761                 if(input.checked)\r
762                     total += Number(input.getAttribute('balance_owed'));\r
763             }\r
764         );\r
765 \r
766         total = total.toFixed(2);\r
767         dojo.byId('oils-selfck-selected-total').innerHTML = \r
768             dojo.string.substitute(localeStrings.TOTAL_FINES_SELECTED, [total]);\r
769     }\r
770 \r
771     // wire up the batch on/off selector\r
772     var sel = dojo.byId('oils-selfck-fines-selector');\r
773     sel.onchange = function() {\r
774         dojo.forEach(\r
775             dojo.query('[name=selector]', this.finesTbody),\r
776             function(input) {\r
777                 input.checked = sel.checked;\r
778             }\r
779         );\r
780     };\r
781 */\r
782     var self = this;\r
783     var handler = function(dataList) {\r
784 \r
785         self.finesCount = dataList.length;\r
786         self.finesData = dataList;\r
787 \r
788         for(var i in dataList) {\r
789 \r
790             var data = dataList[i];\r
791             var row = self.finesTemplate.cloneNode(true);\r
792             var type = data.transaction.xact_type();\r
793 \r
794             if(type == 'circulation') {\r
795                 self.byName(row, 'title').innerHTML = data.record.title();\r
796                                 if(dojo.date.stamp.fromISOString(data.circ.due_date())<(new Date())) self.byName(row, 'due_date').style.color="red";\r
797                 self.byName(row, 'due_date').innerHTML = dojo.date.locale.format(dojo.date.stamp.fromISOString(data.circ.due_date()), {selector: 'date', fullYear: true});\r
798                                 self.byName(row, 'date_return').innerHTML = (data.circ.checkin_time())?dojo.date.locale.format(dojo.date.stamp.fromISOString(data.circ.checkin_time()), {selector: 'date', fullYear: true}):"";\r
799 \r
800             } else if(type == 'grocery') {\r
801                 self.byName(row, 'title').innerHTML = (data.transaction.last_billing_type())?("Miscellaneous - "+data.transaction.last_billing_type()):"Miscellaneous"; // Go ahead and head off any confusion around "grocery".  TODO i18n\r
802             }\r
803 \r
804             //self.byName(row, 'total_owed').innerHTML = data.transaction.total_owed();\r
805             //self.byName(row, 'total_paid').innerHTML = data.transaction.total_paid();\r
806             self.byName(row, 'balance').innerHTML = data.transaction.balance_owed();\r
807                         self.byName(row, 'selector').balance_owed = data.transaction.balance_owed();\r
808                         self.byName(row, 'selector').setAttribute('xact', data.transaction.id());\r
809 /*\r
810             // row selector\r
811             var selector = self.byName(row, 'selector')\r
812             selector.onchange = updateSelected;\r
813             selector.setAttribute('xact', data.transaction.id());\r
814             selector.setAttribute('balance_owed', data.transaction.balance_owed());\r
815             selector.checked = true;\r
816 */\r
817             self.finesTbody.appendChild(row);\r
818         }\r
819 \r
820         //updateSelected();\r
821     }\r
822 \r
823 \r
824     fieldmapper.standardRequest( \r
825         ['open-ils.actor', 'open-ils.actor.user.transactions.have_balance.fleshed'],\r
826         {   async : true,\r
827             params : [this.authtoken, this.patron.id()],\r
828             oncomplete : function(r) { \r
829                 progressDialog.hide();\r
830                 handler(openils.Util.readResponse(r));\r
831             }\r
832         }\r
833     );\r
834 }\r
835 \r
836 SelfCheckManager.prototype.checkin = function(barcode, abortTransit) {\r
837     var resp = fieldmapper.standardRequest(\r
838         ['open-ils.circ', 'open-ils.circ.transit.abort'],\r
839         {params : [this.authtoken, {barcode : barcode}]}\r
840     );\r
841 \r
842     // resp == 1 on success\r
843     if(openils.Event.parse(resp))\r
844         return false;\r
845 \r
846     var resp = fieldmapper.standardRequest(\r
847         ['open-ils.circ', 'open-ils.circ.checkin.override'],\r
848         {params : [\r
849             this.authtoken, {\r
850                 patron_id : this.patron.id(),\r
851                 copy_barcode : barcode,\r
852                 noop : true\r
853             }\r
854         ]}\r
855     );\r
856 \r
857     if(!resp.length) resp = [resp];\r
858     for(var i = 0; i < resp.length; i++) {\r
859         var tc = openils.Event.parse(resp[i]).textcode;\r
860         if(tc == 'SUCCESS' || tc == 'NO_CHANGE') {\r
861             continue;\r
862         } else {\r
863             return false;\r
864         }\r
865     }\r
866 \r
867     return true;\r
868 }\r
869 \r
870 /**\r
871  * Check out a single item.  If the item is already checked \r
872  * out to the patron, redirect to renew()\r
873  */\r
874 SelfCheckManager.prototype.checkout = function(barcode, override) {\r
875         this.keepMeLoggedIn();\r
876     this.prevCirc = null;\r
877 \r
878     if(!barcode) {\r
879         this.updateScanbox(null, true);\r
880         return;\r
881     }\r
882 \r
883     if(this.mockCheckouts) {\r
884         // if we're in mock-checkout mode, just insert another\r
885         // fake circ into the table and get out of here.\r
886         this.displayCheckout(this.mockCheckout, 'checkout');\r
887         return;\r
888     }\r
889 \r
890     // TODO see if it's a patron barcode\r
891     // TODO see if this item has already been checked out in this session\r
892 \r
893     var method = 'open-ils.circ.checkout.full';\r
894     if(override) method += '.override';\r
895 \r
896     console.log("Checkout out item " + barcode + " with method " + method);\r
897 \r
898     var result = fieldmapper.standardRequest(\r
899         ['open-ils.circ', method],\r
900         {params: [\r
901             this.authtoken, {\r
902                 patron_id : this.patron.id(),\r
903                 copy_barcode : barcode\r
904             }\r
905         ]}\r
906     );\r
907 \r
908     var stat = this.handleXactResult('checkout', barcode, result);\r
909 \r
910     if(stat.override) {\r
911         this.checkout(barcode, true);\r
912     } else if(stat.doOver) {\r
913         this.checkout(barcode);\r
914     } else if(stat.renew) {\r
915         this.renew(barcode);\r
916     }\r
917 }\r
918 \r
919 SelfCheckManager.prototype.failPartMessage = function(result) {\r
920     if (result.payload && result.payload.fail_part) {\r
921         var stringKey = "FAIL_PART_" +\r
922             result.payload.fail_part.replace(/\./g, "_");\r
923         return localeStrings[stringKey];\r
924     } else {\r
925         return null;\r
926     }\r
927 }\r
928 \r
929 SelfCheckManager.prototype.handleXactResult = function(action, item, result) {\r
930     var displayText = '';\r
931 \r
932     // If true, the display message is important enough to pop up.  Whether or not\r
933     // an alert() actually occurs, depends on org unit settings\r
934     var popup = false;  \r
935     var sound = ''; // sound file reference\r
936     var payload = result.payload || {};\r
937     var overrideEvents = this.orgSettings[SET_AUTO_OVERRIDE_EVENTS];\r
938     var blockStatuses = this.orgSettings[SET_BLOCK_CHECKOUT_ON_COPY_STATUS];\r
939         result.payload = payload;\r
940         \r
941     if(result.textcode == 'NO_SESSION') {\r
942 \r
943         return this.logoutStaff();\r
944 \r
945     } else if(result.textcode == 'SUCCESS') {\r
946 \r
947         if(action == 'checkout') {\r
948 \r
949             displayText = dojo.string.substitute(localeStrings.CHECKOUT_SUCCESS, [item]);\r
950             this.displayCheckout(result, 'checkout');\r
951 \r
952             if(payload.holds_fulfilled && payload.holds_fulfilled.length) {\r
953                 // A hold was fulfilled, update the hold numbers in the circ summary\r
954                 console.log("fulfilled hold " + payload.holds_fulfilled + " during checkout");\r
955                 this.holdsSummary = null;\r
956                 this.updateHoldsSummary();\r
957             }\r
958 \r
959             this.updateCircSummary(true);\r
960 \r
961         } else if(action == 'renew') {\r
962 \r
963             displayText = dojo.string.substitute(localeStrings.RENEW_SUCCESS, [item]);\r
964             this.displayCheckout(result, 'renew');\r
965         }\r
966 \r
967         this.checkouts.push({circ : result.payload.circ.id()});\r
968         sound = 'checkout-success';\r
969         this.updateScanBox();\r
970 \r
971     } else if(result.textcode == 'OPEN_CIRCULATION_EXISTS' && action == 'checkout') {\r
972 \r
973         // Server says the item is already checked out.  If it's checked out to the\r
974         // current user, we may need to renew it.  \r
975 \r
976         if(payload.old_circ) { \r
977 \r
978             /*\r
979             old_circ refers to the previous checkout IFF it's for the same user. \r
980             If no auto-renew interval is not defined, assume we should renew it\r
981             If an auto-renew interval is defined and the payload comes back with\r
982             auto_renew set to true, do the renewal.  Otherwise, let the patron know\r
983             the item is already checked out to them.  */\r
984 \r
985             if( !this.orgSettings[SET_AUTO_RENEW_INTERVAL] ||\r
986                 (this.orgSettings[SET_AUTO_RENEW_INTERVAL] && payload.auto_renew) ) {\r
987                 this.prevCirc = payload.old_circ.id();\r
988                 return { renew : true };\r
989             }\r
990 \r
991             popup = false;\r
992             sound = 'checkout-failure';\r
993             displayText = dojo.string.substitute(localeStrings.ALREADY_OUT, [item]);\r
994 \r
995         } else {\r
996 \r
997             if( // copy is marked lost.  if configured to do so, check it in and try again.\r
998                 result.payload.copy && \r
999                 result.payload.copy.status() == /* LOST */ 3 &&\r
1000                 overrideEvents && overrideEvents.length &&\r
1001                 overrideEvents.indexOf('COPY_STATUS_LOST') != -1) {\r
1002 \r
1003                     if(this.checkin(item)) {\r
1004                         return { doOver : true };\r
1005                     }\r
1006             }\r
1007 \r
1008             \r
1009             // item is checked out to some other user\r
1010             popup = false;\r
1011             sound = 'checkout-failure';\r
1012             displayText = dojo.string.substitute(localeStrings.OPEN_CIRCULATION_EXISTS, [item]);\r
1013         }\r
1014 \r
1015         this.updateScanBox();\r
1016 \r
1017     } else {\r
1018 \r
1019     \r
1020         if(overrideEvents && overrideEvents.length) {\r
1021             \r
1022             // see if the events we received are all in the list of\r
1023             // events to override\r
1024     \r
1025             if(!result.length) result = [result];\r
1026     \r
1027             var override = true;\r
1028             for(var i = 0; i < result.length; i++) {\r
1029 \r
1030                 var match = overrideEvents.filter(function(e) { return (e == result[i].textcode); })[0];\r
1031 \r
1032                 if(!match) {\r
1033                     override = false;\r
1034                     break;\r
1035                 }\r
1036 \r
1037                 if(result[i].textcode == 'COPY_NOT_AVAILABLE' && blockStatuses && blockStatuses.length) {\r
1038 \r
1039                     var stat = result[i].payload.status(); // copy status\r
1040                     if(typeof stat == 'object') stat = stat.id();\r
1041 \r
1042                     var match2 = blockStatuses.filter(function(e) { return (e == stat); })[0];\r
1043 \r
1044                     if(match2) { // copy is in a blocked status\r
1045                         override = false;\r
1046                         break;\r
1047                     }\r
1048                 }\r
1049 \r
1050                 if(result[i].textcode == 'COPY_IN_TRANSIT') {\r
1051                     // to override a transit, we have to abort the transit and check it in first\r
1052                     if(this.checkin(item, true)) {\r
1053                         return { doOver : true };\r
1054                     } else {\r
1055                         override = false;\r
1056                     }\r
1057                 }\r
1058             }\r
1059 \r
1060             if(override) \r
1061                 return { override : true };\r
1062         }\r
1063     \r
1064         this.updateScanBox();\r
1065         popup = false;\r
1066         sound = 'checkout-failure';\r
1067 \r
1068         if(action == 'renew')\r
1069             this.checkouts.push({circ : this.prevCirc, renewal_failure : true});\r
1070 \r
1071         if(result.length) \r
1072             result = result[0];\r
1073 \r
1074         switch(result.textcode) {\r
1075 \r
1076             // TODO custom handler for blocking penalties\r
1077 \r
1078             case 'MAX_RENEWALS_REACHED' :\r
1079                 displayText = dojo.string.substitute(\r
1080                     localeStrings.MAX_RENEWALS, [item]);\r
1081                 break;\r
1082 \r
1083             case 'ITEM_NOT_CATALOGED' :\r
1084                 displayText = dojo.string.substitute(\r
1085                     localeStrings.ITEM_NOT_CATALOGED, [item]);\r
1086                 break;\r
1087 \r
1088             case 'OPEN_CIRCULATION_EXISTS' :\r
1089                 displayText = dojo.string.substitute(\r
1090                     localeStrings.OPEN_CIRCULATION_EXISTS, [item]);\r
1091 \r
1092                 break;\r
1093 \r
1094             default:\r
1095                 console.error('Unhandled event ' + result.textcode);\r
1096 \r
1097                 if (!(displayText = this.failPartMessage(result))) {\r
1098                     if (action == 'checkout' || action == 'renew') {\r
1099                         displayText = dojo.string.substitute(\r
1100                             localeStrings.GENERIC_CIRC_FAILURE, [item]);\r
1101                     } else {\r
1102                         displayText = dojo.string.substitute(\r
1103                             localeStrings.UNKNOWN_ERROR, [result.textcode]);\r
1104                     }\r
1105                 }\r
1106         }\r
1107     }\r
1108 \r
1109     this.handleAlert(displayText, popup, sound);\r
1110     return {};\r
1111 }\r
1112 \r
1113 \r
1114 /**\r
1115  * Renew an item\r
1116  */\r
1117 SelfCheckManager.prototype.renew = function(barcode, override) {\r
1118 \r
1119     var method = 'open-ils.circ.renew';\r
1120     if(override) method += '.override';\r
1121 \r
1122     console.log("Renewing item " + barcode + " with method " + method);\r
1123 \r
1124     var result = fieldmapper.standardRequest(\r
1125         ['open-ils.circ', method],\r
1126         {params: [\r
1127             this.authtoken, {\r
1128                 patron_id : this.patron.id(),\r
1129                 copy_barcode : barcode\r
1130             }\r
1131         ]}\r
1132     );\r
1133 \r
1134     console.log(js2JSON(result));\r
1135 \r
1136     var stat = this.handleXactResult('renew', barcode, result);\r
1137 \r
1138     if(stat.override)\r
1139         this.renew(barcode, true);\r
1140 }\r
1141 \r
1142 /**\r
1143  * Display the result of a checkout or renewal in the items out table\r
1144  */\r
1145 SelfCheckManager.prototype.displayCheckout = function(evt, type, itemsOut) {\r
1146     var copy = evt.payload.copy;\r
1147     var record = evt.payload.record;\r
1148     var circ = evt.payload.circ;\r
1149     var row = this.circTemplate.cloneNode(true);\r
1150 \r
1151     //if(record.isbn()) {\r
1152     //    this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + record.isbn());\r
1153     //}\r
1154 \r
1155     this.byName(row, 'barcode').innerHTML = copy.barcode();\r
1156     this.byName(row, 'title').innerHTML = record.title();\r
1157     //this.byName(row, 'author').innerHTML = record.author();\r
1158     //this.byName(row, 'remaining').innerHTML = circ.renewal_remaining();\r
1159     openils.Util.show(this.byName(row, type));\r
1160 \r
1161     var date = dojo.date.stamp.fromISOString(circ.due_date());\r
1162     this.byName(row, 'due_date').innerHTML = \r
1163         dojo.date.locale.format(date, {selector : 'date'});\r
1164 \r
1165     // put new circs at the top of the list\r
1166     var tbody = this.circTbody;\r
1167     if(itemsOut) tbody = this.itemsOutTbody;\r
1168     tbody.insertBefore(row, tbody.getElementsByTagName('tr')[0]);\r
1169 }\r
1170 \r
1171 \r
1172 SelfCheckManager.prototype.byName = function(node, name) {\r
1173     return dojo.query('[name=' + name+']', node)[0];\r
1174 }\r
1175 \r
1176 \r
1177 SelfCheckManager.prototype.initPrinter = function() {\r
1178     try { // Mozilla only\r
1179                 netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");\r
1180         netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');\r
1181         netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesRead');\r
1182         netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesWrite');\r
1183         var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);\r
1184         if (pref)\r
1185             pref.setBoolPref('print.always_print_silent', true);\r
1186     } catch(E) {\r
1187         console.log("Unable to initialize auto-printing"); \r
1188     }\r
1189 }\r
1190 \r
1191 /**\r
1192  * Print a receipt for this session's checkouts\r
1193  */\r
1194 SelfCheckManager.prototype.printSessionReceipt = function(callback) {\r
1195     var circIds = [];\r
1196     var circCtx = []; // circ context data.  in this case, renewal_failure info\r
1197 \r
1198     // collect the circs and failure info\r
1199     dojo.forEach(\r
1200         this.checkouts, \r
1201         function(blob) {\r
1202             circIds.push(blob.circ);\r
1203             circCtx.push({renewal_failure:blob.renewal_failure});\r
1204         }\r
1205     );\r
1206 \r
1207     var params = [\r
1208         this.authtoken, \r
1209         this.staff.ws_ou(),\r
1210         null,\r
1211         'format.selfcheck.checkout',\r
1212         'print-on-demand',\r
1213         circIds,\r
1214         circCtx\r
1215     ];\r
1216 \r
1217     var self = this;\r
1218     fieldmapper.standardRequest(\r
1219         ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],\r
1220         {   \r
1221             async : true,\r
1222             params : params,\r
1223             oncomplete : function(r) {\r
1224                 var resp = openils.Util.readResponse(r);\r
1225                 var output = resp.template_output();\r
1226                 if(output) {\r
1227                     self.printData(output.data(), self.checkouts.length, callback); \r
1228                 } else {\r
1229                     var error = resp.error_output();\r
1230                     if(error) {\r
1231                         throw new Error("Error creating receipt: " + error.data());\r
1232                     } else {\r
1233                         throw new Error("No receipt data returned from server");\r
1234                     }\r
1235                 }\r
1236             }\r
1237         }\r
1238     );\r
1239 }\r
1240 \r
1241 SelfCheckManager.prototype.printData = function(data, numItems, callback) {\r
1242     var win = window.open('', '', 'resizable,width=350,height=250,scrollbars=1'); \r
1243     win.document.body.innerHTML = data;\r
1244     win.print();\r
1245 \r
1246     /*\r
1247      * There is no way to know when the browser is done printing.\r
1248      * Make a best guess at when to close the print window by basing\r
1249      * the setTimeout wait on the number of items to be printed plus\r
1250      * a small buffer\r
1251      */\r
1252     var sleepTime = 1000;\r
1253     if(numItems > 0) \r
1254         sleepTime += (numItems / 2) * 1000;\r
1255 \r
1256     setTimeout(\r
1257         function() { \r
1258             win.close(); // close the print window\r
1259             if(callback) callback(); // fire optional post-print callback\r
1260         },\r
1261         sleepTime \r
1262     );\r
1263 }\r
1264 \r
1265 \r
1266 /**\r
1267  * Print a receipt for this user's items out\r
1268  */\r
1269 SelfCheckManager.prototype.printItemsOutReceipt = function(callback) {\r
1270     if(!this.itemsOut.length) return;\r
1271 \r
1272     progressDialog.show(true);\r
1273 \r
1274     var params = [\r
1275         this.authtoken, \r
1276         this.staff.ws_ou(),\r
1277         null,\r
1278         'format.selfcheck.items_out',\r
1279         'print-on-demand',\r
1280         this.itemsOut\r
1281     ];\r
1282 \r
1283     var self = this;\r
1284     fieldmapper.standardRequest(\r
1285         ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],\r
1286         {   \r
1287             async : true,\r
1288             params : params,\r
1289             oncomplete : function(r) {\r
1290                 progressDialog.hide();\r
1291                 var resp = openils.Util.readResponse(r);\r
1292                 var output = resp.template_output();\r
1293                 if(output) {\r
1294                     self.printData(output.data(), self.itemsOut.length, callback); \r
1295                 } else {\r
1296                     var error = resp.error_output();\r
1297                     if(error) {\r
1298                         throw new Error("Error creating receipt: " + error.data());\r
1299                     } else {\r
1300                         throw new Error("No receipt data returned from server");\r
1301                     }\r
1302                 }\r
1303             }\r
1304         }\r
1305     );\r
1306 }\r
1307 \r
1308 /**\r
1309  * Print a receipt for this user's items out\r
1310  */\r
1311 SelfCheckManager.prototype.printHoldsReceipt = function(callback) {\r
1312     if(!this.holds.length) return;\r
1313 \r
1314     progressDialog.show(true);\r
1315 \r
1316     var holdIds = [];\r
1317     var holdData = [];\r
1318 \r
1319     dojo.forEach(this.holds,\r
1320         function(data) {\r
1321             holdIds.push(data.hold.id());\r
1322             if(data.status == 4) {\r
1323                 holdData.push({ready : true});\r
1324             } else {\r
1325                 holdData.push({\r
1326                     queue_position : data.queue_position, \r
1327                     potential_copies : data.potential_copies\r
1328                 });\r
1329             }\r
1330         }\r
1331     );\r
1332 \r
1333     var params = [\r
1334         this.authtoken, \r
1335         this.staff.ws_ou(),\r
1336         null,\r
1337         'format.selfcheck.holds',\r
1338         'print-on-demand',\r
1339         holdIds,\r
1340         holdData\r
1341     ];\r
1342 \r
1343     var self = this;\r
1344     fieldmapper.standardRequest(\r
1345         ['open-ils.circ', 'open-ils.circ.fire_hold_trigger_events'],\r
1346         {   \r
1347             async : true,\r
1348             params : params,\r
1349             oncomplete : function(r) {\r
1350                 progressDialog.hide();\r
1351                 var resp = openils.Util.readResponse(r);\r
1352                 var output = resp.template_output();\r
1353                 if(output) {\r
1354                     self.printData(output.data(), self.holds.length, callback); \r
1355                 } else {\r
1356                     var error = resp.error_output();\r
1357                     if(error) {\r
1358                         throw new Error("Error creating receipt: " + error.data());\r
1359                     } else {\r
1360                         throw new Error("No receipt data returned from server");\r
1361                     }\r
1362                 }\r
1363             }\r
1364         }\r
1365     );\r
1366 }\r
1367 \r
1368 \r
1369 SelfCheckManager.prototype.printPaymentReceipt = function(paymentIds, callback) {\r
1370     var self = this;\r
1371     progressDialog.show(true);\r
1372 \r
1373     fieldmapper.standardRequest(\r
1374         ['open-ils.circ', 'open-ils.circ.money.payment_receipt.print'],\r
1375         {\r
1376             async : true,\r
1377             params : [this.authtoken, paymentIds],\r
1378             oncomplete : function(r) {\r
1379                 var resp = openils.Util.readResponse(r);\r
1380                 var output = resp.template_output();\r
1381                 progressDialog.hide();\r
1382                 if(output) {\r
1383                     self.printData(output.data(), 1, callback); \r
1384                 } else {\r
1385                     var error = resp.error_output();\r
1386                     if(error) {\r
1387                         throw new Error("Error creating receipt: " + error.data());\r
1388                     } else {\r
1389                         throw new Error("No receipt data returned from server");\r
1390                     }\r
1391                 }\r
1392             }\r
1393         }\r
1394     );\r
1395 }\r
1396 \r
1397 /**\r
1398  * Print a receipt for this user's items out\r
1399  */\r
1400 SelfCheckManager.prototype.printFinesReceipt = function(callback) {\r
1401     progressDialog.show(true);\r
1402 \r
1403     var params = [\r
1404         this.authtoken, \r
1405         this.staff.ws_ou(),\r
1406         null,\r
1407         'format.selfcheck.fines',\r
1408         'print-on-demand',\r
1409         [this.patron.id()]\r
1410     ];\r
1411 \r
1412     var self = this;\r
1413     fieldmapper.standardRequest(\r
1414         ['open-ils.circ', 'open-ils.circ.fire_user_trigger_events'],\r
1415         {   \r
1416             async : true,\r
1417             params : params,\r
1418             oncomplete : function(r) {\r
1419                 progressDialog.hide();\r
1420                 var resp = openils.Util.readResponse(r);\r
1421                 var output = resp.template_output();\r
1422                 if(output) {\r
1423                     self.printData(output.data(), self.finesCount, callback); \r
1424                 } else {\r
1425                     var error = resp.error_output();\r
1426                     if(error) {\r
1427                         throw new Error("Error creating receipt: " + error.data());\r
1428                     } else {\r
1429                         throw new Error("No receipt data returned from server");\r
1430                     }\r
1431                 }\r
1432             }\r
1433         }\r
1434     );\r
1435 }\r
1436 \r
1437 \r
1438 /**\r
1439  * Logout the patron and return to the login page\r
1440  */\r
1441 SelfCheckManager.prototype.logoutPatron = function(print) {\r
1442     progressDialog.show(true); // prevent patron from clicking logout link twice\r
1443     if(print && this.checkouts.length) {\r
1444         this.printSessionReceipt(\r
1445             function() {\r
1446                 location.href = location.href;\r
1447             }\r
1448         );\r
1449     } else {\r
1450         location.href = location.href;\r
1451     }\r
1452 }\r
1453 \r
1454 \r
1455 function checkLogin() {\r
1456         selfCheckMgr.keepMeLoggedIn();\r
1457         if(selfCheckMgr.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {\r
1458                 switchTo('step2');\r
1459                 try{dojo.byId('patron-login-password').focus();}catch(e){}\r
1460         } else {\r
1461                 selfCheckMgr.loginPatron(dojo.byId('patron-login-username').value);\r
1462         }\r
1463 }\r
1464 \r
1465 \r
1466 function cancelLogin() {\r
1467         dojo.byId('oils-selfck-status-div').innerHTML = '';\r
1468         dojo.byId('oils-selfck-status-div2').innerHTML = '';\r
1469         dojo.byId('oils-selfck-status-div3').innerHTML = '';\r
1470         dojo.byId('patron-login-password').value = '';\r
1471         openils.Util.hide('back_to_login');\r
1472         switchTo('step1');\r
1473         try {\r
1474                 dojo.byId('patron-login-username').focus();\r
1475                 dojo.byId('patron-login-username').select();\r
1476         } catch(e) {}\r
1477 }\r
1478 \r
1479 /**\r
1480  * Fire up the manager on page load\r
1481  */\r
1482 openils.Util.addOnLoad(\r
1483     function() {\r
1484         new SelfCheckManager().init();\r
1485                 openils.Util.registerEnterHandler(dojo.byId('patron-login-username'), function(){checkLogin();});\r
1486                 openils.Util.registerEnterHandler(dojo.byId('patron-login-password'), function(){selfCheckMgr.loginPatron(dojo.byId('patron-login-username').value,dojo.byId('patron-login-password').value);});\r
1487     }\r
1488 );\r