From: Jason Etheridge Date: Mon, 6 Mar 2017 16:02:33 +0000 (-0500) Subject: webstaff: toward label printing X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=30e52d6fb749e55984e5544b0910ebce12cbc796;p=working%2FEvergreen.git webstaff: toward label printing ...better stock template for labels, and a | wrap filter ...pull in some Library Settings for Print Labels ...Reset to Default button for templates for both receipt and item print labels ...toward tabs for Print Label interface ...template management for print labels ...bundle the Call Number Template in with saved templates ...manual editing of cn's for print labels ...And affixes in the stock CN template. ...Settings tab for print labels Signed-off-by: Jason Etheridge --- diff --git a/Open-ILS/src/templates/staff/admin/workstation/t_print_templates.tt2 b/Open-ILS/src/templates/staff/admin/workstation/t_print_templates.tt2 index b049c5684d..064bfff811 100644 --- a/Open-ILS/src/templates/staff/admin/workstation/t_print_templates.tt2 +++ b/Open-ILS/src/templates/staff/admin/workstation/t_print_templates.tt2 @@ -46,6 +46,7 @@
+
diff --git a/Open-ILS/src/templates/staff/cat/printlabels/index.tt2 b/Open-ILS/src/templates/staff/cat/printlabels/index.tt2 new file mode 100644 index 0000000000..ac89fc5b00 --- /dev/null +++ b/Open-ILS/src/templates/staff/cat/printlabels/index.tt2 @@ -0,0 +1,30 @@ +[% + WRAPPER "staff/base.tt2"; + ctx.page_title = l("Print Item Labels"); + ctx.page_app = "egPrintLabels"; +%] + +[% BLOCK APP_JS %] + + + + + + + +[% END %] + + + +
+ +[% END %] + + diff --git a/Open-ILS/src/templates/staff/cat/printlabels/t_view.tt2 b/Open-ILS/src/templates/staff/cat/printlabels/t_view.tt2 new file mode 100644 index 0000000000..8829e85482 --- /dev/null +++ b/Open-ILS/src/templates/staff/cat/printlabels/t_view.tt2 @@ -0,0 +1,232 @@ + + +

[% l('Print Item Labels') %]

+ +
+
+
+
+ [% l('Template') %] +
+
+ +
+
+ +
+
+ [% l('Printer') %] +
+
+ +
+
+
+
+
+ + +
+
+
+
+ + [% l('Import') %] + + + +
+
+
+ +
+
+ +
+ +
+
+ +
+
+
+

+ [% l('Call Number Preview') %] +

+
+

+ [% l('Call Number Template') %] +

+
[% l('Changes here will wipe out manual changes in the Call Numbers tab.') %]
+ +
+
+
+
+
+

+ [% l('Formatted Call Numbers') %] +

+
[% l('Manual adjustments may be made here. These do not get saved with templates.') %]
+
+ +
+
+
+
[% l('These settings do get saved with templates and will override corresponding Library Settings.') %]
+ +
+
[% l('Spine and pocket label font family') %]
+
+
+ +
+
[% l('Set the preferred font family for spine and pocket labels. You can specify a list of fonts, separated by commas, in order of preference; the system will use the first font it finds with a matching name. For example, "Arial, Helvetica, serif".') %]
+
+ +
+
[% l('Spine and pocket label font size') %]
+
+
+ +
+
[% l('Set the default font size for spine and pocket labels') %]
+
+ +
+
[% l('Spine and pocket label font weight') %]
+
+
+ +
+
[% l('Set the preferred font weight for spine and pocket labels. You can specify "normal", "bold", "bolder", or "lighter".') %]
+
+ +
+
[% l('Spine label maximum lines') %]
+
+
+ +
+
[% l('Set the default maximum number of lines for spine labels.') %]
+
+ +
+
[% l('Spine label left margin') %]
+
+
+ +
+
[% l('Set the left margin for spine labels in number of characters.') %]
+
+ +
+
[% l('Spine label line width') %]
+
+
+ +
+
[% l('Set the default line width for spine labels in number of characters. This specifies the boundary at which lines must be wrapped.') %]
+
+ +
+
[% l('Pocket label maximum lines') %]
+
+
+ +
+
[% l('Set the default maximum number of lines for pocket labels.') %]
+
+ +
+
[% l('Pocket label left margin') %]
+
+
+ +
+
[% l('Set the left margin for pocket labels in number of characters.') %]
+
+ +
+
[% l('Pocket label line width') %]
+
+
+ +
+
[% l('Set the default line width for pocket labels in number of characters. This specifies the boundary at which lines must be wrapped.') %]
+
+ +
+
+
+ [% l( + "Unable to load template '[_1]'. The web server returned an error.", + '{{print.template_name}}') + %] +
+
+ +
+
+
+
+
+
+

+ [% l('Label Preview') %] +

+
+
+
+ diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2 index 38dde8fe86..7a17c39aa9 100644 --- a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2 +++ b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2 @@ -33,7 +33,7 @@
- +
diff --git a/Open-ILS/src/templates/staff/share/print_templates/t_item_label.tt2 b/Open-ILS/src/templates/staff/share/print_templates/t_item_label.tt2 new file mode 100644 index 0000000000..fed5e331d1 --- /dev/null +++ b/Open-ILS/src/templates/staff/share/print_templates/t_item_label.tt2 @@ -0,0 +1,75 @@ + + + + + + + +
+ + +
+{{get_cn_for(copy)}}
+
+
+ +
+{{copy.barcode}}
+{{copy['call_number.label']}}
+{{copy['call_number.record.simple_record.author']}}
+{{copy['call_number.record.simple_record.title'] | wrap:28:'once':' '}}
+
+
diff --git a/Open-ILS/src/templates/staff/share/print_templates/t_item_label_cn.tt2 b/Open-ILS/src/templates/staff/share/print_templates/t_item_label_cn.tt2 new file mode 100644 index 0000000000..8e163810e8 --- /dev/null +++ b/Open-ILS/src/templates/staff/share/print_templates/t_item_label_cn.tt2 @@ -0,0 +1,11 @@ +
+{{copy['call_number.prefix.label'] || copy['location.label_prefix']}}
+{{
+       
+       copy['call_number.label']
+       
+    |   wrap:5
+
+}}
+{{copy['call_number.suffix.label'] || copy['location.label_suffix']}}
+
diff --git a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js index a25d7190f9..dd5ac606fd 100644 --- a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js +++ b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js @@ -555,6 +555,16 @@ function($scope , $q , egCore , ngToast) { }); } + $scope.reset_to_default = function() { + egCore.print.removePrintTemplate( + $scope.print.template_name + ); + egCore.print.removePrintTemplateContext( + $scope.print.template_name + ); + $scope.template_changed(); + } + $scope.save_locally = function() { egCore.print.storePrintTemplate( $scope.print.template_name, diff --git a/Open-ILS/web/js/ui/default/staff/cat/printlabels/app.js b/Open-ILS/web/js/ui/default/staff/cat/printlabels/app.js new file mode 100644 index 0000000000..15a6a7413c --- /dev/null +++ b/Open-ILS/web/js/ui/default/staff/cat/printlabels/app.js @@ -0,0 +1,477 @@ +/** + * Vol/Copy Editor + */ + +angular.module('egPrintLabels', + ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod']) + +.config(function($routeProvider, $locationProvider, $compileProvider) { + $locationProvider.html5Mode(true); + $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export + + var resolver = { + delay : ['egStartup', function(egStartup) { return egStartup.go(); }] + }; + + $routeProvider.when('/cat/printlabels/:dataKey', { + templateUrl: './cat/printlabels/t_view', + controller: 'LabelCtrl', + resolve : resolver + }); + +}) + +.factory('itemSvc', + ['egCore', +function(egCore) { + + var service = { + copies : [], // copy barcode search results + index : 0 // search grid index + }; + + service.flesh = { + flesh : 3, + flesh_fields : { + acp : ['call_number','location','status','location','floating','circ_modifier','age_protect'], + acn : ['record','prefix','suffix'], + bre : ['simple_record','creator','editor'] + }, + select : { + // avoid fleshing MARC on the bre + // note: don't add simple_record.. not sure why + bre : ['id','tcn_value','creator','editor'], + } + } + + // resolved with the last received copy + service.fetch = function(barcode, id, noListDupes) { + var promise; + + if (barcode) { + promise = egCore.pcrud.search('acp', + {barcode : barcode, deleted : 'f'}, service.flesh); + } else { + promise = egCore.pcrud.retrieve('acp', id, service.flesh); + } + + var lastRes; + return promise.then( + function() {return lastRes}, + null, // error + + // notify reads the stream of copies, one at a time. + function(copy) { + + var flatCopy; + if (noListDupes) { + // use the existing copy if possible + flatCopy = service.copies.filter( + function(c) {return c.id == copy.id()})[0]; + } + + if (!flatCopy) { + flatCopy = egCore.idl.toHash(copy, true); + flatCopy.index = service.index++; + service.copies.unshift(flatCopy); + } + + return lastRes = { + copy : copy, + index : flatCopy.index + } + } + ); + } + + return service; +}]) + +/** + * Label controller! + */ +.controller('LabelCtrl', + ['$scope','$q','$window','$routeParams','$location','$timeout','egCore','egNet','ngToast','itemSvc', +function($scope , $q , $window , $routeParams , $location , $timeout , egCore , egNet , ngToast , itemSvc ) { + + var dataKey = $routeParams.dataKey; + console.debug('dataKey: ' + dataKey); + + $scope.print = { + template_name : 'item_label', + template_output : '', + template_context : 'default' + }; + + + if (dataKey && dataKey.length > 0) { + + egNet.request( + 'open-ils.actor', + 'open-ils.actor.anon_cache.get_value', + dataKey, 'print-labels-these-copies' + ).then(function (data) { + + if (data) { + + $scope.preview_scope = { + 'copies' : [] + ,'settings' : {} + ,'get_cn_for' : function(copy) { + var key = $scope.rendered_cn_key_by_copy_id[copy.id]; + if (key) { + var manual_cn = $scope.rendered_call_number_set[key]; + if (manual_cn && manual_cn.value) { + return manual_cn.value; + } else { + return '..'; + } + } else { + return '...'; + } + } + }; + + var promises = []; + + promises.push( + egCore.org.settings([ + 'cat.label.font.family' + ,'cat.label.font.size' + ,'cat.label.font.weight' + ,'cat.spine.line.height' + ,'cat.spine.line.width' + ,'cat.spine.line.margin' + ]).then(function(res) { + $scope.preview_scope.settings = res; + egCore.hatch.getItem('cat.printlabels.last_settings').then(function(last_settings) { + if (last_settings) { + for (s in last_settings) { + $scope.preview_scope.settings[s] = last_settings[s]; + } + } + }); + }) + ); + + angular.forEach(data.copies, function(copy) { + promises.push( + itemSvc.fetch(null,copy).then(function(res) { + $scope.preview_scope.copies.push(egCore.idl.toHash(res.copy, true)); + }) + ) + }); + + $q.all(promises).then(function() { + + // today, staff, current_location, etc. + egCore.print.fleshPrintScope($scope.preview_scope); + + $scope.template_changed(); // load the default + $scope.rebuild_cn_set(); + + }) + } else { + ngToast.danger(egCore.strings.KEY_EXPIRED); + } + + }); + + } + + $scope.fetchTemplates = function () { + egCore.hatch.getItem('cat.printlabels.templates').then(function(t) { + if (t) { + $scope.templates = t; + $scope.template_name_list = Object.keys(t); + } + }); + } + $scope.fetchTemplates(); + + $scope.applyTemplate = function (n) { + $scope.print.cn_template_content = $scope.templates[n].cn_content; + $scope.print.template_content = $scope.templates[n].content; + $scope.print.template_context = $scope.templates[n].context; + for (var s in $scope.templates[n].settings) { + $scope.preview_scope.settings[s] = $scope.templates[n].settings[s]; + } + } + + $scope.deleteTemplate = function (n) { + if (n) { + delete $scope.templates[n] + $scope.template_name_list = Object.keys($scope.templates); + $scope.template_name = ''; + egCore.hatch.setItem('cat.printlabels.templates', $scope.templates); + $scope.fetchTemplates(); + ngToast.create(egCore.strings.PRINT_LABEL_TEMPLATE_SUCCESS_DELETE); + } + } + + $scope.saveTemplate = function (n) { + if (n) { + + $scope.templates[n] = { + content : $scope.print.template_content + ,context : $scope.print.template_context + ,cn_content : $scope.print.cn_template_content + ,settings : $scope.preview_scope.settings + }; + $scope.template_name_list = Object.keys($scope.templates); + + egCore.hatch.setItem('cat.printlabels.templates', $scope.templates); + $scope.fetchTemplates(); + + $scope.dirty = false; + } else { + // save all templates, as we might do after an import + egCore.hatch.setItem('cat.printlabels.templates', $scope.templates); + $scope.fetchTemplates(); + } + ngToast.create(egCore.strings.PRINT_LABEL_TEMPLATE_SUCCESS_SAVE); + } + + $scope.templates = {}; + $scope.imported_templates = { data : '' }; + $scope.template_name = ''; + $scope.template_name_list = []; + + $scope.print_labels = function() { + $scope.save_locally(); + return egCore.print.print({ + context : $scope.print.template_context, + template : $scope.print.template_name, + scope : $scope.preview_scope, + }); + } + + $scope.template_changed = function() { + $scope.print.load_failed = false; + egCore.print.getPrintTemplate('item_label') + .then( + function(html) { + $scope.print.template_content = html; + }, + function() { + $scope.print.template_content = ''; + $scope.print.load_failed = true; + } + ); + egCore.print.getPrintTemplateContext('item_label') + .then(function(template_context) { + $scope.print.template_context = template_context; + }); + egCore.print.getPrintTemplate('item_label_cn') + .then( + function(html) { + $scope.print.cn_template_content = html; + }, + function() { + $scope.print.cn_template_content = ''; + $scope.print.load_failed = true; + } + ); + egCore.hatch.getItem('cat.printlabels.last_settings').then(function(s) { + if (s) { + $scope.preview_scope.settings = s; + } + }); + + } + + $scope.reset_to_default = function() { + egCore.print.removePrintTemplate( + 'item_label' + ); + egCore.print.removePrintTemplateContext( + 'item_label' + ); + egCore.print.removePrintTemplate( + 'item_label_cn' + ); + egCore.hatch.removeItem('cat.printlabels.last_settings'); + for (s in $scope.preview_scope.settings) { + $scope.preview_scope.settings[s] = undefined; + } + $scope.preview_scope.settings = {}; + egCore.org.settings([ + 'cat.label.font.family' + ,'cat.label.font.size' + ,'cat.label.font.weight' + ,'cat.spine.line.height' + ,'cat.spine.line.width' + ,'cat.spine.line.margin' + ]).then(function(res) { + $scope.preview_scope.settings = res; + }) + + $scope.template_changed(); + } + + $scope.save_locally = function() { + egCore.print.storePrintTemplate( + 'item_label', + $scope.print.template_content + ); + egCore.print.storePrintTemplateContext( + 'item_label', + $scope.print.template_context + ); + egCore.print.storePrintTemplate( + 'item_label_cn', + $scope.print.cn_template_content + ); + egCore.hatch.setItem('cat.printlabels.last_settings', $scope.preview_scope.settings); + } + + $scope.imported_print_templates = { data : '' }; + $scope.$watch('imported_templates.data', function(newVal, oldVal) { + if (newVal && newVal != oldVal) { + try { + var data = JSON.parse(newVal); + angular.forEach(data, function(el,k) { + $scope.templates[k] = { + content : el.content + ,context : el.context + ,cn_content : el.cn_content + ,settings : el.settings + }; + }); + $scope.saveTemplate(); + $scope.template_changed(); // refresh + ngToast.create(egCore.strings.PRINT_TEMPLATES_SUCCESS_IMPORT); + } catch (E) { + ngToast.warning(egCore.strings.PRINT_TEMPLATES_FAIL_IMPORT); + } + } + }); + + $scope.rendered_call_number_set = {}; + $scope.rendered_cn_key_by_copy_id = {}; + $scope.rebuild_cn_set = function() { + $timeout(function(){ + $scope.rendered_call_number_set = {}; + $scope.rendered_cn_key_by_copy_id = {}; + for (var i = 0; i < $scope.preview_scope.copies.length; i++) { + var copy = $scope.preview_scope.copies[i]; + var rendered_cn = document.getElementById('cn_for_copy_'+copy.id); + if (rendered_cn && rendered_cn.textContent) { + var key = rendered_cn.textContent; + if (typeof $scope.rendered_call_number_set[key] == 'undefined') { + $scope.rendered_call_number_set[key] = { + value : key + }; + } + $scope.rendered_cn_key_by_copy_id[copy.id] = key; + } + } + $scope.preview_scope.tickle = Date() + ' ' + Math.random(); + }); + } + + $scope.$watch('print.cn_template_content', function(newVal, oldVal) { + if (newVal && newVal != oldVal) { + $scope.rebuild_cn_set(); + } + }); + + $scope.current_tab = 'call_numbers'; + $scope.set_tab = function(tab) { + $scope.current_tab = tab; + } + +}]) + +// +.directive('egPrintTemplateOutput', ['$compile',function($compile) { + return function(scope, element, attrs) { + scope.$watch( + function(scope) { + return scope.$eval(attrs.content); + }, + function(value) { + // create an isolate scope and copy the print context + // data into the new scope. + // TODO: see also print security concerns in egHatch + var result = element.html(value); + var context = scope.$eval(attrs.context); + var print_scope = scope.$new(true); + angular.forEach(context, function(val, key) { + print_scope[key] = val; + }) + $compile(element.contents())(print_scope); + } + ); + }; +}]) + +.filter('wrap', function() { + return function(input, w, wrap_type, indent) { + var output; + + if (!w) return input; + if (!indent) indent = ''; + + function wrap_on_space( + text, + length, + wrap_just_once, + if_cant_wrap_then_truncate, + idx + ) { + if (idx>10) { + console.log('possible infinite recursion, aborting'); + return ''; + } + if (String(text).length <= length) { + return text; + } else { + var truncated_text = String(text).substr(0,length); + var pivot_pos = truncated_text.lastIndexOf(' '); + var left_chunk = text.substr(0,pivot_pos).replace(/\s*$/,''); + var right_chunk = String(text).substr(pivot_pos+1); + + var wrapped_line; + if (left_chunk.length == 0) { + if (if_cant_wrap_then_truncate) { + wrapped_line = truncated_text; + } else { + wrapped_line = text; + } + } else { + wrapped_line = + left_chunk + '\n' + + indent + ( + wrap_just_once + ? right_chunk + : ( + right_chunk.length > length + ? wrap_on_space( + right_chunk, + length, + false, + if_cant_wrap_then_truncate, + idx+1) + : right_chunk + ) + ) + ; + } + return wrapped_line; + } + } + + switch(wrap_type) { + case 'once': + output = wrap_on_space(input,w,true,false,0); + break; + default: + output = wrap_on_space(input,w,false,false,0); + break; + } + + return output; + } +}) + diff --git a/Open-ILS/web/js/ui/default/staff/services/print.js b/Open-ILS/web/js/ui/default/staff/services/print.js index 97f8333091..1b950e31da 100644 --- a/Open-ILS/web/js/ui/default/staff/services/print.js +++ b/Open-ILS/web/js/ui/default/staff/services/print.js @@ -206,6 +206,10 @@ function($q , $window , $timeout , $http , egHatch , egAuth , egIDL , egOrg , eg return egHatch.setItem('eg.print.template.' + name, html); } + service.removePrintTemplate = function(name) { + return egHatch.removeItem('eg.print.template.' + name); + } + service.getPrintTemplateContext = function(name) { var deferred = $q.defer(); @@ -220,6 +224,9 @@ function($q , $window , $timeout , $http , egHatch , egAuth , egIDL , egOrg , eg service.storePrintTemplateContext = function(name, context) { return egHatch.setItem('eg.print.template_context.' + name, context); } + service.removePrintTemplateContext = function(name) { + return egHatch.removeItem('eg.print.template_context.' + name); + } return service; }])