]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/serials/directives/prediction_wizard.js
LP#1708291: web staff client serials module
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / serials / directives / prediction_wizard.js
1 angular.module('egSerialsAppDep')
2
3 .directive('egPredictionWizard', function() {
4     return {
5         transclude: true,
6         restrict:   'E',
7         scope: {
8             patternCode : '=',
9             onSave      : '=',
10             showShare   : '=',
11             viewOnly    : '='
12         },
13         templateUrl: './serials/t_prediction_wizard',
14         controller:
15        ['$scope','$q','egSerialsCoreSvc','egCore','egGridDataProvider',
16 function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider) {
17
18     $scope.tab = { active : 0 };
19     if (angular.isUndefined($scope.showShare)) {
20         $scope.showShare = true;
21     }
22     if (angular.isUndefined($scope.viewOnly)) {
23         $scope.viewOnly = false;
24     }
25
26     // for use by ng-value
27     $scope.True = true;
28     $scope.False = false;
29
30     // class for MARC21 serial prediction pattern
31     // TODO move elsewhere
32     function PredictionPattern(patternCode) {
33         var self = this;
34         this.use_enum = false;
35         this.use_alt_enum = false;
36         this.use_chron = false;
37         this.use_alt_chron = false;
38         this.use_calendar_changes = false;
39         this.calendar_change = [];
40         this.compress_expand = '3';
41         this.caption_evaluation = '0';        
42         this.enum_levels = [];
43         this.alt_enum_levels = [];
44         this.chron_levels = [];
45         this.alt_chron_levels = [{ caption : null, display_caption: false }];
46         this.frequency_type = 'preset';
47         this.use_regularity = false;
48         this.regularity = [];
49
50         var nr_sf_map = {
51             '8' : 'link',
52             'n' : 'note',
53             'p' : 'pieces_per_issuance',
54             'w' : 'frequency',
55             't' : 'copy_caption'
56         }
57         var enum_level_map = {
58             'a' : 0,
59             'b' : 1,
60             'c' : 2,
61             'd' : 3,
62             'e' : 4,
63             'f' : 5
64         }
65         var alt_enum_level_map = {
66             'g' : 0,
67             'h' : 1
68         }
69         var chron_level_map = {
70             'i' : 0,
71             'j' : 1,
72             'k' : 2,
73             'l' : 3
74         }
75         var alt_chron_level_map = {
76             'm' : 0
77         }
78
79         var curr_enum_level = -1;
80         var curr_alt_enum_level = -1;
81         var curr_chron_level = -1;
82         var curr_alt_chron_level = -1;
83         if (patternCode && patternCode.length > 2 && (patternCode.length % 2 == 0)) {
84             // set indicator values
85             this.compress_expand = patternCode[0];
86             this.caption_evaluation = patternCode[1];
87             for (var i = 2; i < patternCode.length; i += 2) {
88                 var sf = patternCode[i];
89                 var value = patternCode[i + 1]; 
90                 if (sf in nr_sf_map) {
91                     this[nr_sf_map[sf]] = value;
92                     continue;
93                 }
94                 if (sf in enum_level_map) {
95                     this.use_enum = true;
96                     curr_enum_level = enum_level_map[sf];
97                     this.enum_levels[curr_enum_level] = {
98                         caption : value,
99                         restart : false
100                     }
101                     continue;
102                 }
103                 if (sf in alt_enum_level_map) {
104                     this.use_enum = true;
105                     this.use_alt_enum = true;
106                     curr_enum_level = -1;
107                     curr_alt_enum_level = alt_enum_level_map[sf];
108                     this.alt_enum_levels[curr_alt_enum_level] = {
109                         caption : value,
110                         restart : false
111                     }
112                     continue;
113                 }
114                 if (sf in chron_level_map) {
115                     this.use_chron = true;
116                     curr_chron_level = chron_level_map[sf];
117                     var chron = {};
118                     if (value.match(/^\(.*\)$/)) {
119                         chron.display_caption = false;
120                         chron.caption = value.replace(/^\(/, '').replace(/\)$/, '');
121                     } else {
122                         chron.display_caption = true;
123                         chron.caption = value;
124                     }
125                     this.chron_levels[curr_chron_level] = chron;
126                     continue;
127                 }
128                 if (sf in alt_chron_level_map) {
129                     this.use_alt_chron = true;
130                     curr_chron_level = -1;
131                     curr_alt_chron_level = alt_chron_level_map[sf];
132                     var chron = {};
133                     if (value.match(/^\(.*\)$/)) {
134                         chron.display_caption = false;
135                         chron.caption = value.replace(/^\(/, '').replace(/\)$/, '');
136                     } else {
137                         chron.display_caption = true;
138                         chron.caption = value;
139                     }
140                     this.alt_chron_levels[curr_alt_chron_level] = chron;
141                     continue;
142                 }
143
144                 if (sf == 'u') {
145                     var units = {
146                         type : 'number'
147                     };
148                     if (value == 'und' || value == 'var') {
149                         units.type = value;
150                     } else if (!isNaN(parseInt(value))) {
151                         units.value = parseInt(value);
152                     } else {
153                         continue; // escape garbage
154                     }
155                     if (curr_enum_level > 0) {
156                         this.enum_levels[curr_enum_level].units_per_next_higher = units;
157                     } else if (curr_alt_enum_level > 0) {
158                         this.alt_enum_levels[curr_alt_enum_level].units_per_next_higher = units;
159                     }
160                 }
161                 if (sf == 'v' && value == 'r') {
162                     if (curr_enum_level > 0) {
163                         this.enum_levels[curr_enum_level].restart = true;
164                     } else if (curr_alt_enum_level > 0) {
165                         this.alt_enum_levels[curr_alt_enum_level].restart = true;
166                     }
167                 }
168                 if (sf == 'z') {
169                     if (curr_enum_level > -1) {
170                         this.enum_levels[curr_enum_level].numbering_scheme = value;
171                     } else if (curr_alt_enum_level > -1) {
172                         this.alt_enum_levels[curr_alt_enum_level].numbering_scheme = value;
173                     }
174                 }
175                 if (sf == 'x') {
176                     this.use_calendar_change = true;
177                     value.split(',').forEach(function(chg) {
178                         var calendar_change = {
179                             type   : null,
180                             season : null,
181                             month  : null,
182                             day    : null
183                         }
184                         if (chg.length == 2) {
185                             if (chg >= '21') {
186                                 calendar_change.type = 'season';
187                                 calendar_change.season = chg;
188                             } else {
189                                 calendar_change.type = 'month';
190                                 calendar_change.month = chg;
191                             }
192                         } else if (chg.length == 4) {
193                             calendar_change.type = 'date';
194                             calendar_change.month = chg.substring(0, 2);
195                             calendar_change.day   = chg.substring(2, 4);
196                         }
197                         self.calendar_change.push(calendar_change);
198                     });
199                 }
200                 if (sf == 'y') {
201                     this.use_regularity = true;
202                     var regularity_type = value.substring(0, 1);
203                     var parts = [];
204                     var chron_type = value.substring(1, 2);
205                     value.substring(2).split(/,/).forEach(function(value) {
206                         var piece = {};
207                         if (regularity_type == 'c') {
208                             piece.combined_code = value;
209                         } else if (chron_type == 'd') {
210                             if (value.match(/^\d\d$/)) {
211                                 piece.sub_type = 'day_of_month';
212                                 piece.day_of_month = value;
213                             } else if (value.match(/^\d\d\d\d$/)) {
214                                 piece.sub_type = 'specific_date';
215                                 piece.specific_date = value;
216                             } else {
217                                 piece.sub_type = 'day_of_week';
218                                 piece.day_of_week = value;
219                             }
220                         } else if (chron_type == 'm') {
221                             piece.sub_type = 'month';
222                             piece.month = value;
223                         } else if (chron_type == 's') {
224                             piece.sub_type = 'season';
225                             piece.season = value;
226                         } else if (chron_type == 'w') {
227                             if (value.match(/^\d\d\d\d$/)) {
228                                 piece.sub_type = 'week_in_month';
229                                 piece.week   = value.substring(0, 2);
230                                 piece.month  = value.substring(2, 4);
231                             } else if (value.match(/^\d\d[a-z][a-z]$/)) {
232                                 piece.sub_type = 'week_day';
233                                 piece.week = value.substring(0, 2);
234                                 piece.day  = value.substring(2, 4);
235                             } else if (value.length == 6) {
236                                 piece.sub_type = 'week_day_in_month';
237                                 piece.month = value.substring(0, 2);
238                                 piece.week  = value.substring(2, 4);
239                                 piece.day   = value.substring(4, 6);
240                             }
241                         } else if (chron_type == 'y') {
242                             piece.sub_type = 'year';
243                             piece.year = value;
244                         }
245                         parts.push(piece);
246                     });
247                     self.regularity.push({
248                         regularity_type  : regularity_type,
249                         chron_type       : chron_type,
250                         parts            : parts
251                     });
252                 }
253             }
254         }
255
256         if (self.frequency) {
257             if (self.frequency.match(/^\d+$/)) {
258                 self.frequency_type = 'numeric';
259                 self.frequency_numeric = self.frequency;
260             } else {
261                 self.frequency_type = 'preset';
262                 self.frequency_preset = self.frequency;
263             }
264         }
265
266         // return current pattern compiled to subfield list
267         this.compile = function() {
268             var patternCode = [];
269             patternCode.push(self.compress_expand);
270             patternCode.push(self.caption_evaluation);
271             patternCode.push('8');
272             patternCode.push(self.link);
273             if (self.use_enum) {
274                 for (var i = 0; i < self.enum_levels.length; i++) {
275                     patternCode.push(['a', 'b', 'c', 'd', 'e', 'f'][i]);
276                     patternCode.push(self.enum_levels[i].caption);
277                     if (i > 0 && self.enum_levels[i].units_per_next_higher) {
278                         patternCode.push('u');
279                         if (self.enum_levels[i].units_per_next_higher.type == 'number') {
280                             patternCode.push(self.enum_levels[i].units_per_next_higher.value.toString());
281                         } else {
282                             patternCode.push(self.enum_levels[i].units_per_next_higher.type);
283                         }
284                     }
285                     if (i > 0 && self.enum_levels[i].restart != null) {
286                         patternCode.push('v');
287                         patternCode.push(self.enum_levels[i].restart ? 'r' : 'c');
288                     }
289                 }
290             }
291             if (self.use_enum && self.use_alt_enum) {
292                 for (var i = 0; i < self.alt_enum_levels.length; i++) {
293                     patternCode.push(['g','h'][i]);
294                     patternCode.push(self.alt_enum_levels[i].caption);
295                     if (i > 0 && self.alt_enum_levels[i].units_per_next_higher) {
296                         patternCode.push('u');
297                         if (self.alt_enum_levels[i].units_per_next_higher.type == 'number') {
298                             patternCode.push(self.alt_enum_levels[i].units_per_next_higher.value);
299                         } else {
300                             patternCode.push(self.alt_enum_levels[i].units_per_next_higher.type);
301                         }
302                     }
303                     if (i > 0 && self.alt_enum_levels[i].restart != null) {
304                         patternCode.push('v');
305                         patternCode.push(self.alt_enum_levels[i].restart ? 'r' : 'c');
306                     }
307                 }
308             }
309             var chron_sfs = (self.use_enum) ? ['i', 'j', 'k', 'l'] : ['a', 'b', 'c', 'd'];
310             if (self.use_chron) {
311                 for (var i = 0; i < self.chron_levels.length; i++) {
312                     patternCode.push(chron_sfs[i],
313                         self.chron_levels[i].display_caption ?
314                            self.chron_levels[i].caption :
315                            '(' + self.chron_levels[i].caption + ')'
316                     );
317                 }
318             }
319             var alt_chron_sf = (self.use_enum) ? 'm' : 'g';
320             if (self.use_alt_chron) {
321                 patternCode.push(alt_chron_sf,
322                     self.alt_chron_levels[0].display_caption ?
323                        self.alt_chron_levels[0].caption :
324                        '(' + self.alt_chron_levels[0].caption + ')'
325                 );
326             }
327             // frequency
328             patternCode.push('w',
329                 self.frequency_type == 'numeric' ?
330                     self.frequency_numeric :
331                     self.frequency_preset
332             );
333             // calendar change
334             if (self.use_enum && self.use_calendar_change) {
335                 patternCode.push('x');
336                 patternCode.push(self.calendar_change.map(function(chg) {
337                     if (chg.type == 'season') {
338                         return chg.season;
339                     } else if (chg.type == 'month') {
340                         return chg.month;
341                     } else if (chg.type == 'date') {
342                         return chg.month + chg.day;
343                     }
344                 }).join(','));
345             }
346             // regularity
347             if (self.use_regularity) {
348                 self.regularity.forEach(function(reg) {
349                     patternCode.push('y');
350                     var val = reg.regularity_type + reg.chron_type;
351                     val += reg.parts.map(function(part) {
352                         if (reg.regularity_type == 'c') {
353                             return part.combined_code;
354                         } else if (reg.chron_type == 'd') {
355                             return part[part.sub_type];
356                         } else if (reg.chron_type == 'm') {
357                             return part.month;
358                         } else if (reg.chron_type == 'w') {
359                             if (part.sub_type == 'week_in_month') {
360                                 return part.week + part.month;
361                             } else if (part.sub_type == 'week_day') {
362                                 return part.week + part.day;
363                             } else if (part.sub_type == 'week_day_in_month') {
364                                 return part.month + part.week + part.day;
365                             }
366                         } else if (reg.chron_type == 's') {
367                             return part.season;
368                         } else if (reg.chron_type == 'y') {
369                             return part.year;
370                         }
371                     }).join(',');
372                     patternCode.push(val);
373                 });
374             }
375             return patternCode;
376         }
377
378         this.compile_stringify = function() {
379             return JSON.stringify(this.compile(), null, 2);
380         }
381
382         this.add_enum_level = function() {
383             if (self.enum_levels.length < 6) {
384                 self.enum_levels.push({
385                     caption : null,
386                     units_per_next_higher : { type : 'und' },
387                     restart : false
388                 });
389             }
390         }
391         this.drop_enum_level = function() {
392             if (self.enum_levels.length > 1) {
393                 self.enum_levels.pop();
394             }
395         }
396
397         this.add_alt_enum_level = function() {
398             if (self.alt_enum_levels.length < 2) {
399                 self.alt_enum_levels.push({
400                     caption : null,
401                     units_per_next_higher : { type : 'und' },
402                     restart : false
403                 });
404             }
405         }
406         this.drop_alt_enum_level = function() {
407             if (self.alt_enum_levels.length > 1) {
408                 self.alt_enum_levels.pop();
409             }
410         }
411         this.remove_calendar_change = function(idx) {
412             if (self.calendar_change.length > idx) {
413                 self.calendar_change.splice(idx, 1);
414             }
415         }
416         this.add_calendar_change = function() {
417             self.calendar_change.push({
418                 type   : null,
419                 season : null,
420                 month  : null,
421                 day    : null
422             });
423         }
424
425         this.add_chron_level = function() {
426             if (self.chron_levels.length < 4) {
427                 self.chron_levels.push({
428                     caption : null,
429                     display_caption : false
430                 });
431             }
432         }
433         this.drop_chron_level = function() {
434             if (self.chron_levels.length > 1) {
435                 self.chron_levels.pop();
436             }
437         }
438         this.add_regularity = function() {
439             self.regularity.push({
440                 regularity_type : null,
441                 chron_type : null,
442                 parts : [{ sub_type : null }]
443             });
444         }
445         this.remove_regularity = function(idx) {
446             if (self.regularity.length > idx) {
447                 self.regularity.splice(idx, 1);
448             }
449             // and add a blank entry back if need be
450             if (self.regularity.length == 0) {
451                 self.add_regularity();
452             }
453         }
454         this.add_regularity_part = function(reg) {
455             reg.parts.push({
456                 sub_type : null
457             });
458         }
459         this.remove_regularity_part = function(reg, idx) {
460             if (reg.parts.length > idx) {
461                 reg.parts.splice(idx, 1);
462             }
463             // and add a blank entry back if need be
464             if (reg.parts.length == 0) {
465                 self.add_regularity_part(reg);
466             }
467         }
468
469         this.display_enum_captions = function() {
470             return self.enum_levels.map(function(lvl) {
471                 return lvl.caption;
472             }).join(', ');
473         }
474         this.display_alt_enum_captions = function() {
475             return self.alt_enum_levels.map(function(lvl) {
476                 return lvl.caption;
477             }).join(', ');
478         }
479         this.display_chron_captions = function() {
480             return self.chron_levels.map(function(lvl) {
481                 return lvl.caption;
482             }).join(', ');
483         }
484         this.display_alt_chron_captions = function() {
485             return self.alt_chron_levels.map(function(lvl) {
486                 return lvl.caption;
487             }).join(', ');
488         }
489
490         if (!patternCode) {
491             // starting from scratch, ensure there's
492             // enough so that the input wizard can be used
493             this.use_enum = true;
494             this.use_chron = true;
495             this.link = 0;
496             self.add_enum_level();
497             self.add_alt_enum_level();
498             self.add_chron_level();
499             self.add_calendar_change();
500             self.add_regularity();
501         } else {
502             // fill in potential missing bits
503             if (!self.use_enum && self.enum_levels.length == 0) self.add_enum_level();
504             if (!self.use_alt_enum && self.alt_enum_levels.length == 0) self.add_alt_enum_level();
505             if (!self.use_chron && self.chron_levels.length == 0) self.add_chron_level();
506             if (!self.use_calendar_change) self.add_calendar_change();
507             if (!self.use_regularity) self.add_regularity();
508         }
509     }
510     // TODO chron only
511
512     if ($scope.patternCode) {
513         $scope.pattern = new PredictionPattern(JSON.parse($scope.patternCode));
514     } else {
515         $scope.pattern = new PredictionPattern();
516     }
517
518     // possible sharing
519     $scope.share = {
520         pattern_name : null,
521         depth        : 0
522     };
523
524     $scope.chron_captions = [];
525     $scope.alt_chron_captions = [];
526
527     $scope.handle_save = function() {
528         $scope.patternCode = JSON.stringify($scope.pattern.compile());
529         if ($scope.share.pattern_name !== null) {
530             var spt = new egCore.idl.spt();
531             spt.name($scope.share.pattern_name);
532             spt.pattern_code($scope.patternCode);
533             spt.share_depth($scope.share.depth);
534             spt.owning_lib(egCore.auth.user().ws_ou());
535             egCore.pcrud.create(spt).then(function() {
536                 if (angular.isFunction($scope.onSave)) {
537                     $scope.onSave($scope.patternCode);
538                 }
539             });
540         } else {
541             if (angular.isFunction($scope.onSave)) {
542                 $scope.onSave($scope.patternCode);
543             }
544         }
545     }
546
547 }]
548     }
549 })
550
551 .directive('egChronSelector', function() {
552     return {
553         transclude: true,
554         restrict:   'E',
555         scope: {
556             ngModel        : '=',
557             chronLevel     : '=',
558             linkedSelector : '=',
559         },
560         templateUrl: './serials/t_chron_selector',
561         controller:
562        ['$scope','$q','egCore',
563 function($scope , $q , egCore) {
564         $scope.options = [
565             { value : 'year',   label : egCore.strings.CHRON_LABEL_YEAR,   disabled: false },
566             { value : 'season', label : egCore.strings.CHRON_LABEL_SEASON, disabled: false },
567             { value : 'month',  label : egCore.strings.CHRON_LABEL_MONTH,  disabled: false },
568             { value : 'week',   label : egCore.strings.CHRON_LABEL_WEEK,   disabled: false },
569             { value : 'day',    label : egCore.strings.CHRON_LABEL_DAY,    disabled: false },
570             { value : 'hour',   label : egCore.strings.CHRON_LABEL_HOUR,   disabled: false }
571         ];
572         var levels = {
573             'year'   : 0,
574             'season' : 1,
575             'month'  : 1,
576             'week'   : 2,
577             'day'    : 3,
578             'hour'   : 4
579         };
580         $scope.$watch('ngModel', function(newVal, oldVal) {
581             $scope.linkedSelector[$scope.chronLevel] = $scope.ngModel;
582         });
583         $scope.$watch('linkedSelector', function(newVal, oldVal) {
584             if ($scope.chronLevel > 0 && $scope.linkedSelector[$scope.chronLevel - 1]) {
585                 var level_to_disable = levels[ $scope.linkedSelector[$scope.chronLevel - 1] ];
586                 for (var i = 0; i < $scope.options.length; i++) {
587                     $scope.options[i].disabled =
588                         (levels[ $scope.options[i].value ] <= level_to_disable);
589                 }
590             }
591         }, true);
592 }]
593     }
594 })
595
596 .directive('egMonthSelector', function() {
597     return {
598         transclude: true,
599         restrict:   'E',
600         scope: {
601             ngModel : '='
602         },
603         templateUrl: './serials/t_month_selector',
604         controller:
605        ['$scope','$q',
606 function($scope , $q) {
607 }]
608     }
609 })
610
611 .directive('egSeasonSelector', function() {
612     return {
613         transclude: true,
614         restrict:   'E',
615         scope: {
616             ngModel : '='
617         },
618         templateUrl: './serials/t_season_selector',
619         controller:
620        ['$scope','$q',
621 function($scope , $q) {
622 }]
623     }
624 })
625
626 .directive('egWeekInMonthSelector', function() {
627     return {
628         transclude: true,
629         restrict:   'E',
630         scope: {
631             ngModel : '='
632         },
633         templateUrl: './serials/t_week_in_month_selector',
634         controller:
635        ['$scope','$q',
636 function($scope , $q) {
637 }]
638     }
639 })
640
641 .directive('egDayOfWeekSelector', function() {
642     return {
643         transclude: true,
644         restrict:   'E',
645         scope: {
646             ngModel : '='
647         },
648         templateUrl: './serials/t_day_of_week_selector',
649         controller:
650        ['$scope','$q',
651 function($scope , $q) {
652 }]
653     }
654 })
655
656 .directive('egMonthDaySelector', function() {
657     return {
658         transclude: true,
659         restrict:   'E',
660         scope: {
661             month : '=',
662             day   : '='
663         },
664         templateUrl: './serials/t_month_day_selector',
665         controller:
666        ['$scope','$q',
667 function($scope , $q) {
668     if ($scope.month == null) $scope.month = '01';
669     if ($scope.day   == null) $scope.day   = '01';
670     $scope.dt = new Date(2012, parseInt($scope.month) - 1, parseInt($scope.day), 1);
671     $scope.options = {
672         minMode : 'day',
673         maxMode : 'day',
674         datepickerMode : 'day',
675         showWeeks : false,
676         // use a leap year, though any publisher who uses 29 February as a
677         // calendar change is simply trolling
678         // also note that when https://github.com/angular-ui/bootstrap/issues/1993
679         // is fixed, setting minDate and maxDate would make sense, as
680         // user wouldn't be able to keeping hit the left or right arrows
681         // past the end of the range
682         // minDate : new Date('2012-01-01 00:00:01'),
683         // maxDate : new Date('2012-12-31 23:59:59'),
684         formatDayTitle : 'MMMM',
685     }
686     $scope.datePickerIsOpen = false;
687     $scope.$watch('dt', function(newVal, oldVal) {
688         if (newVal != oldVal) {
689             $scope.day   = ('00' + $scope.dt.getDate() ).slice(-2);
690             $scope.month = ('00' + ($scope.dt.getMonth() + 1)).slice(-2);
691         }
692     });
693 }]
694     }
695 })
696
697 .directive('egPredictionPatternSummary', function() {
698     return {
699         transclude: true,
700         restrict:   'E',
701         scope: {
702             pattern : '<'
703         },
704         templateUrl: './serials/t_pattern_summary',
705         controller:
706        ['$scope','$q',
707 function($scope , $q) {
708 }]
709     }
710 })
711