]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/acq/lineitem/copies.component.ts
LP1850473: manual and automated eslint fixes
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / acq / lineitem / copies.component.ts
1 import {Component, OnInit, AfterViewInit, Input, Output, EventEmitter,
2     ViewChild} from '@angular/core';
3 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
4 import {Observable, of} from 'rxjs';
5 import {tap, map} from 'rxjs/operators';
6 import {Pager} from '@eg/share/util/pager';
7 import {IdlService, IdlObject} from '@eg/core/idl.service';
8 import {OrgService} from '@eg/core/org.service';
9 import {NetService} from '@eg/core/net.service';
10 import {PcrudService} from '@eg/core/pcrud.service';
11 import {AuthService} from '@eg/core/auth.service';
12 import {LineitemService, FleshCacheParams} from './lineitem.service';
13 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
14 import {ItemLocationService} from '@eg/share/item-location-select/item-location-select.service';
15 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
16
17 const FORMULA_FIELDS = [
18     'owning_lib',
19     'location',
20     'fund',
21     'circ_modifier',
22     'collection_code'
23 ];
24
25 interface FormulaApplication {
26     formula: IdlObject;
27     count: number;
28 }
29
30 @Component({
31     selector: 'eg-lineitem-copies',
32     templateUrl: 'copies.component.html'
33 })
34 export class LineitemCopiesComponent implements OnInit, AfterViewInit {
35
36     static newCopyId = -1;
37
38     // modes are 'normal' and 'multiAdd'
39     //   normal   = manage copies for a single line item whose
40     //              ID is taken from the route
41     //   multiAdd = embedded in a modal and applying the results
42     //              to selected LIs
43     @Input() mode = 'normal';
44
45     // emited only in multiAdd mode
46     @Output() lineitemWithCopies = new EventEmitter<IdlObject>();
47
48     lineitemId: number;
49     lineitem: IdlObject;
50     copyCount = 1;
51     batchOwningLib: IdlObject;
52     batchFund: ComboboxEntry;
53     batchCopyLocId: number;
54     dirty = false;
55     saving = false;
56     progressMax = 0;
57     progressValue = 0;
58     formulaFilter = {owner: []};
59     formulaOffset = 0;
60     formulaValues: {[field: string]: {[val: string]: boolean}} = {};
61
62     // Can any changes be applied?
63     liLocked = false;
64
65     @ViewChild('leaveConfirm', { static: true }) leaveConfirm: ConfirmDialogComponent;
66
67     constructor(
68         private route: ActivatedRoute,
69         private idl: IdlService,
70         private org: OrgService,
71         private net: NetService,
72         private pcrud: PcrudService,
73         private auth: AuthService,
74         private loc: ItemLocationService,
75         private liService: LineitemService
76     ) {}
77
78     ngOnInit() {
79
80         this.formulaFilter.owner =
81             this.org.fullPath(this.auth.user().ws_ou(), true);
82
83         if (this.mode === 'multiAdd') {
84             this.load();
85         } else {
86             // normal mode, we're checking the route to initalize
87             // ourselves
88             this.route.paramMap.subscribe((params: ParamMap) => {
89                 const id = +params.get('lineitemId');
90                 if (id !== this.lineitemId) {
91                     this.lineitemId = id;
92                     if (id) { this.load(); }
93                 }
94             });
95         }
96
97         this.liService.getLiAttrDefs();
98     }
99
100     load(params?: FleshCacheParams): Promise<any> {
101         this.lineitem = null;
102         this.copyCount = 1;
103
104         if (!params) {
105             params = {toCache: true, fromCache: true};
106         }
107
108         if (this.mode === 'multiAdd') {
109             this.lineitem = this.idl.create('jub');
110             this.lineitem.lineitem_details([]);
111             this.lineitem.distribution_formulas([]);
112             this.liLocked = false; // trusting our invoker in multiAdd mode
113             this.applyCount();
114             this.lineitemWithCopies.emit(this.lineitem);
115             return Promise.resolve(true);
116         } else {
117             return this.liService.getFleshedLineitems([this.lineitemId], params)
118                 .pipe(tap(liStruct => this.lineitem = liStruct.lineitem)).toPromise()
119                 .then(_ => {
120                     this.liLocked =
121                 this.lineitem.state().match(/on-order|received|cancelled/);
122                 })
123                 .then(_ => this.applyCount());
124         }
125     }
126
127     ngAfterViewInit() {
128         setTimeout(() => {
129             const node = document.getElementById('copy-count-input');
130             if (node) { (node as HTMLInputElement).select(); }
131         });
132     }
133
134     applyCount() {
135         const copies = this.lineitem.lineitem_details();
136         while (copies.length < this.copyCount) {
137             const copy = this.idl.create('acqlid');
138             copy.id(LineitemCopiesComponent.newCopyId--);
139             copy.owning_lib(this.auth.user().ws_ou());
140             copy.isnew(true);
141             copy.lineitem(this.lineitem.id());
142             copies.push(copy);
143             this.dirty = true;
144         }
145
146         if (copies.length > this.copyCount) {
147             this.copyCount = copies.length;
148         }
149     }
150
151     applyFormula(id: number) {
152
153         const copies = this.lineitem.lineitem_details();
154         if (this.formulaOffset >= copies.length) {
155             // We have already applied a formula entry to every item.
156             return;
157         }
158
159         this.formulaValues = {};
160
161         this.pcrud.retrieve('acqdf', id,
162             {flesh: 1, flesh_fields: {acqdf: ['entries']}})
163             .subscribe(formula => {
164
165                 formula.entries(
166                     formula.entries().sort((e1, e2) =>
167                         e1.position() < e2.position() ? -1 : 1));
168
169                 let rowIdx = this.formulaOffset - 1;
170
171                 while (++rowIdx < copies.length) {
172                     this.formulateOneCopy(formula, rowIdx, true);
173                 }
174
175                 // No new values will be applied
176                 if (!Object.keys(this.formulaValues)) { return; }
177
178                 this.fetchFormulaValues().then(_ => {
179
180                     let applied = 0;
181                     let rowIdx2 = this.formulaOffset - 1;
182
183                     while (++rowIdx2 < copies.length) {
184                         applied += this.formulateOneCopy(formula, rowIdx2);
185                     }
186
187                     if (applied) {
188                         this.formulaOffset += applied;
189                         this.saveAppliedFormula(formula);
190                     }
191                 });
192             });
193     }
194
195     saveAppliedFormula(formula: IdlObject) {
196         const app = this.idl.create('acqdfa');
197         app.lineitem(this.lineitem.id());
198         app.creator(this.auth.user().id());
199         app.formula(formula.id());
200
201         if (this.mode === 'multiAdd') {
202             app.isnew(true);
203             this.lineitem.distribution_formulas().push(app);
204             this.dirty = true;
205         } else {
206             this.pcrud.create(app).toPromise().then(a => {
207                 a.creator(this.auth.user());
208                 a.formula(formula);
209                 this.lineitem.distribution_formulas().push(a);
210             });
211         }
212     }
213
214     // Grab values applied by distribution formulas and cache them before
215     // applying them to their target copies, so the comboboxes, etc.
216     // are not required to go fetch them en masse / en duplicato.
217     fetchFormulaValues(): Promise<any> {
218
219         let funds = [];
220         if (this.formulaValues.fund) {
221             funds = Object.keys(this.formulaValues.fund).map(id => Number(id));
222         }
223
224         let locs = [];
225         if (this.formulaValues.location) {
226             locs = Object.keys(this.formulaValues.location).map(id => Number(id));
227         }
228
229         const mods = this.formulaValues.circ_modifier ?
230             Object.keys(this.formulaValues.circ_modifier) : [];
231
232         return this.liService.fetchFunds(funds)
233             .then(_ => this.liService.fetchLocations(locs))
234             .then(_ => this.liService.fetchCircMods(mods));
235     }
236
237     // Apply a formula entry to a single copy.
238     // extracOnly means we are only collecting the new values we wish to
239     // apply from the formula w/o applying them to the copy in question.
240     formulateOneCopy(formula: IdlObject,
241         rowIdx: number, extractOnly?: boolean): number {
242
243         let targetEntry = null;
244         let entryIdx = this.formulaOffset;
245         const copy = this.lineitem.lineitem_details()[rowIdx];
246
247         // Find the correct entry for the current copy.
248         formula.entries().forEach(entry => {
249             if (!targetEntry) {
250                 entryIdx += entry.item_count();
251                 if (entryIdx > rowIdx) {
252                     targetEntry = entry;
253                 }
254             }
255         });
256
257         // We ran out of copies.
258         if (!targetEntry) { return 0; }
259
260         FORMULA_FIELDS.forEach(field => {
261             const val = targetEntry[field]();
262             if (val === undefined || val === null) { return; }
263
264             if (extractOnly) {
265                 if (!this.formulaValues[field]) {
266                     this.formulaValues[field] = {};
267                 }
268                 this.formulaValues[field][val] = true;
269
270             } else {
271                 copy[field](val);
272                 this.dirty = true;
273             }
274         });
275
276         return 1;
277     }
278
279     save() {
280         this.saving = true;
281         this.progressMax = null;
282         this.progressValue = 0;
283
284         this.liService.updateLiDetails(this.lineitem).subscribe(
285             struct => {
286                 this.progressMax = struct.total;
287                 this.progressValue++;
288             },
289             (err: unknown) => {},
290             () => this.load({toCache: true}).then(_ => {
291                 this.liService.activateStateChange.emit(this.lineitem.id());
292                 this.saving = false;
293                 this.dirty = false;
294             })
295         );
296     }
297
298     deleteFormula(formula: IdlObject) {
299         this.pcrud.remove(formula).subscribe(_ => {
300             this.lineitem.distribution_formulas(
301                 this.lineitem.distribution_formulas()
302                     .filter(f => f.id() !== formula.id())
303             );
304         });
305     }
306
307     getTitle(li: IdlObject): string {
308         if (!li) { return ''; }
309         return this.liService.getFirstAttributeValue(li, 'title');
310     }
311
312     canDeactivate(): Observable<boolean> {
313         if (this.dirty) {
314             return this.leaveConfirm.open().pipe(map(confirmed => {
315                 if (confirmed) {
316                     // fire-and-forget fetching the line item to restore it
317                     // to its previous state
318                     this.liService.getFleshedLineitems([ this.lineitemId ], {toCache: true}).toPromise();
319                 }
320                 return confirmed;
321             }));
322         } else {
323             return of(true);
324         }
325     }
326 }
327
328