]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/cat/marcbatch/marcbatch.component.ts
LP2045292 Color contrast for AngularJS patron bills
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / cat / marcbatch / marcbatch.component.ts
1 import {Component, OnInit, Renderer2} from '@angular/core';
2 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
3 import {HttpClient} from '@angular/common/http';
4 import {tap} from 'rxjs/operators';
5 import {NetService} from '@eg/core/net.service';
6 import {AuthService} from '@eg/core/auth.service';
7 import {PcrudService} from '@eg/core/pcrud.service';
8 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
9 import {MarcRecord} from '@eg/staff/share/marc-edit/marcrecord';
10 import {AnonCacheService} from '@eg/share/util/anon-cache.service';
11 import {ServerStoreService} from '@eg/core/server-store.service';
12
13 const SESSION_POLL_INTERVAL = 2; // seconds
14 const MERGE_TEMPLATE_PATH = '/opac/extras/merge_template';
15
16 interface TemplateRule {
17     ruleType: 'r' | 'a' | 'd';
18     marcTag?: string;
19     marcSubfields?: string;
20     marcData?: string;
21     advSubfield?: string;
22     advRegex?: string;
23 }
24
25 @Component({
26     templateUrl: 'marcbatch.component.html'
27 })
28 export class MarcBatchComponent implements OnInit {
29
30     session: string;
31     source: 'b' | 'c' | 'r' = 'b';
32     buckets: ComboboxEntry[];
33     bucket: number;
34     recordId: number;
35     csvColumn = 0;
36     csvFile: File;
37     templateRules: TemplateRule[] = [];
38     record: MarcRecord;
39
40     processing = false;
41     progressMax: number = null;
42     progressValue: number = null;
43     numSucceeded = 0;
44     numFailed = 0;
45
46     constructor(
47         private router: Router,
48         private route: ActivatedRoute,
49         private http: HttpClient,
50         private renderer: Renderer2,
51         private net: NetService,
52         private pcrud: PcrudService,
53         private auth: AuthService,
54         private store: ServerStoreService,
55         private cache: AnonCacheService
56     ) {}
57
58     ngOnInit() {
59
60         this.route.paramMap.subscribe((params: ParamMap) => {
61             this.bucket = +params.get('bucketId');
62             this.recordId = +params.get('recordId');
63
64             if (this.bucket) {
65                 this.source = 'b';
66             } else if (this.recordId) {
67                 this.source = 'r';
68             }
69         });
70
71         this.load();
72     }
73
74     load() {
75         this.addRule();
76         this.getBuckets();
77     }
78
79     rulesetToRecord(resetRuleData?: boolean) {
80         this.record = new MarcRecord();
81
82         this.templateRules.forEach(rule => {
83
84             if (!rule.marcTag) { return; }
85
86             let ruleText = rule.marcTag + (rule.marcSubfields || '');
87             if (rule.advSubfield) {
88                 ruleText +=
89                     `[${rule.advSubfield || ''} ~ ${rule.advRegex || ''}]`;
90             }
91
92             // Merge behavior is encoded in the 905 field.
93             const ruleTag = this.record.newField({
94                 tag: '905',
95                 ind1: ' ',
96                 ind2: ' ',
97                 subfields: [[rule.ruleType, ruleText, 0]]
98             });
99
100             this.record.insertOrderedFields(ruleTag);
101
102             if (rule.ruleType === 'd') {
103                 rule.marcData = '';
104                 return;
105             }
106
107             const dataRec = new MarcRecord();
108             if (resetRuleData || !rule.marcData) {
109
110                 // Build a new value for the 'MARC Data' field based on
111                 // changes to the selected tag or subfields.
112
113                 const subfields = rule.marcSubfields ?
114                     rule.marcSubfields.split('').map((sf, idx) => [sf, '', idx])
115                     : [];
116
117                 dataRec.appendFields(
118                     dataRec.newField({
119                         tag: rule.marcTag,
120                         ind1: ' ',
121                         ind2: ' ',
122                         subfields: subfields
123                     })
124                 );
125
126                 console.log(dataRec.toBreaker());
127                 rule.marcData = dataRec.toBreaker().split(/\n/)[1];
128
129             } else {
130
131                 // Absorb the breaker data already in the 'MARC Data' field
132                 // so it can be added to the template record in progress.
133
134                 dataRec.breakerText = rule.marcData;
135                 dataRec.absorbBreakerChanges();
136             }
137
138             this.record.appendFields(dataRec.fields[0]);
139         });
140     }
141
142     breakerRows(): number {
143         if (this.record) {
144             const breaker = this.record.toBreaker();
145             if (breaker) {
146                 return breaker.split(/\n/).length + 1;
147             }
148         }
149         // eslint-disable-next-line no-magic-numbers
150         return 3;
151     }
152
153     breaker(): string {
154         return this.record ? this.record.toBreaker() : '';
155     }
156
157     addRule() {
158         this.templateRules.push({ruleType: 'r'});
159     }
160
161     removeRule(idx: number) {
162         this.templateRules.splice(idx, 1);
163     }
164
165     getBuckets(): Promise<any> {
166         if (this.buckets) { return Promise.resolve(); }
167
168         return this.net.request(
169             'open-ils.actor',
170             'open-ils.actor.container.retrieve_by_class',
171             this.auth.token(), this.auth.user().id(),
172             'biblio', ['staff_client', 'vandelay_queue']
173
174         ).pipe(tap(buckets => {
175             this.buckets = buckets
176                 .sort((b1, b2) => b1.name() < b2.name() ? -1 : 1)
177                 .map(b => ({id: b.id(), label: b.name()}));
178
179         })).toPromise();
180     }
181
182     bucketChanged(entry: ComboboxEntry) {
183         this.bucket = entry ? entry.id : null;
184     }
185
186     fileSelected($event) {
187         this.csvFile = $event.target.files[0];
188     }
189
190     disableSave(): boolean {
191         if (!this.record || !this.source || this.processing) {
192             return true;
193         }
194
195         if (this.source === 'b') {
196             return !this.bucket;
197
198         } else if (this.source === 'c') {
199             return (this.csvColumn < 0 || !this.csvFile);
200
201         } else if (this.source === 'r') {
202             return !this.recordId;
203         }
204     }
205
206     process() {
207         this.processing = true;
208         this.progressValue = null;
209         this.progressMax = null;
210         this.numSucceeded = 0;
211         this.numFailed = 0;
212         this.setReplaceMode();
213         this.postForm().then(_ => this.pollProgress());
214     }
215
216     setReplaceMode() {
217         if (this.record.subfield('905', 'r').length === 0) {
218             // Force replace mode w/ no-op replace rule.
219             this.record.appendFields(
220                 this.record.newField({
221                     tag : '905',
222                     ind1 : ' ',
223                     ind2 : ' ',
224                     subfields : [['r', '901c']]
225                 })
226             );
227         }
228     }
229
230     postForm(): Promise<any> {
231
232         const formData: FormData = new FormData();
233         formData.append('ses', this.auth.token());
234         formData.append('skipui', '1');
235         formData.append('template', this.record.toXml());
236         formData.append('recordSource', this.source);
237         formData.append('xactPerRecord', '1');
238
239         if (this.source === 'b') {
240             formData.append('containerid', this.bucket + '');
241
242         } else if (this.source === 'c') {
243             formData.append('idcolumn', this.csvColumn + '');
244             formData.append('idfile', this.csvFile, this.csvFile.name);
245
246         } else if (this.source === 'r') {
247             formData.append('recid', this.recordId + '');
248         }
249
250         return this.http.post(
251             MERGE_TEMPLATE_PATH, formData, {responseType: 'text'})
252             .pipe(tap(cacheKey => this.session = cacheKey))
253             .toPromise();
254     }
255
256     pollProgress(): Promise<any> {
257         console.debug('Polling session ', this.session);
258
259         return this.cache.getItem(this.session, 'batch_edit_progress')
260             .then(progress => {
261             // {"success":"t","complete":1,"failed":0,"succeeded":252}
262
263                 if (!progress) {
264                     console.error('No batch edit session found for ', this.session);
265                     return;
266                 }
267
268                 this.progressValue = progress.succeeded;
269                 this.progressMax = progress.total;
270                 this.numSucceeded = progress.succeeded;
271                 this.numFailed = progress.failed;
272
273                 if (progress.complete) {
274                     this.processing = false;
275                     return;
276                 }
277
278                 setTimeout(() => this.pollProgress(), SESSION_POLL_INTERVAL * 1000);
279             });
280     }
281 }
282