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';
17 const FORMULA_FIELDS = [
25 interface FormulaApplication {
31 selector: 'eg-lineitem-copies',
32 templateUrl: 'copies.component.html'
34 export class LineitemCopiesComponent implements OnInit, AfterViewInit {
36 static newCopyId = -1;
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
43 @Input() mode = 'normal';
45 // emited only in multiAdd mode
46 @Output() lineitemWithCopies = new EventEmitter<IdlObject>();
51 batchOwningLib: IdlObject;
52 batchFund: ComboboxEntry;
53 batchCopyLocId: number;
58 formulaFilter = {owner: []};
60 formulaValues: {[field: string]: {[val: string]: boolean}} = {};
62 // Can any changes be applied?
65 @ViewChild('leaveConfirm', { static: true }) leaveConfirm: ConfirmDialogComponent;
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
80 this.formulaFilter.owner =
81 this.org.fullPath(this.auth.user().ws_ou(), true);
83 if (this.mode === 'multiAdd') {
86 // normal mode, we're checking the route to initalize
88 this.route.paramMap.subscribe((params: ParamMap) => {
89 const id = +params.get('lineitemId');
90 if (id !== this.lineitemId) {
92 if (id) { this.load(); }
97 this.liService.getLiAttrDefs();
100 load(params?: FleshCacheParams): Promise<any> {
101 this.lineitem = null;
105 params = {toCache: true, fromCache: true};
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
114 this.lineitemWithCopies.emit(this.lineitem);
115 return Promise.resolve(true);
117 return this.liService.getFleshedLineitems([this.lineitemId], params)
118 .pipe(tap(liStruct => this.lineitem = liStruct.lineitem)).toPromise()
121 this.lineitem.state().match(/on-order|received|cancelled/);
123 .then(_ => this.applyCount());
129 const node = document.getElementById('copy-count-input');
130 if (node) { (node as HTMLInputElement).select(); }
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());
141 copy.lineitem(this.lineitem.id());
146 if (copies.length > this.copyCount) {
147 this.copyCount = copies.length;
151 applyFormula(id: number) {
153 const copies = this.lineitem.lineitem_details();
154 if (this.formulaOffset >= copies.length) {
155 // We have already applied a formula entry to every item.
159 this.formulaValues = {};
161 this.pcrud.retrieve('acqdf', id,
162 {flesh: 1, flesh_fields: {acqdf: ['entries']}})
163 .subscribe(formula => {
166 formula.entries().sort((e1, e2) =>
167 e1.position() < e2.position() ? -1 : 1));
169 let rowIdx = this.formulaOffset - 1;
171 while (++rowIdx < copies.length) {
172 this.formulateOneCopy(formula, rowIdx, true);
175 // No new values will be applied
176 if (!Object.keys(this.formulaValues)) { return; }
178 this.fetchFormulaValues().then(_ => {
181 let rowIdx2 = this.formulaOffset - 1;
183 while (++rowIdx2 < copies.length) {
184 applied += this.formulateOneCopy(formula, rowIdx2);
188 this.formulaOffset += applied;
189 this.saveAppliedFormula(formula);
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());
201 if (this.mode === 'multiAdd') {
203 this.lineitem.distribution_formulas().push(app);
206 this.pcrud.create(app).toPromise().then(a => {
207 a.creator(this.auth.user());
209 this.lineitem.distribution_formulas().push(a);
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> {
220 if (this.formulaValues.fund) {
221 funds = Object.keys(this.formulaValues.fund).map(id => Number(id));
225 if (this.formulaValues.location) {
226 locs = Object.keys(this.formulaValues.location).map(id => Number(id));
229 const mods = this.formulaValues.circ_modifier ?
230 Object.keys(this.formulaValues.circ_modifier) : [];
232 return this.liService.fetchFunds(funds)
233 .then(_ => this.liService.fetchLocations(locs))
234 .then(_ => this.liService.fetchCircMods(mods));
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 {
243 let targetEntry = null;
244 let entryIdx = this.formulaOffset;
245 const copy = this.lineitem.lineitem_details()[rowIdx];
247 // Find the correct entry for the current copy.
248 formula.entries().forEach(entry => {
250 entryIdx += entry.item_count();
251 if (entryIdx > rowIdx) {
257 // We ran out of copies.
258 if (!targetEntry) { return 0; }
260 FORMULA_FIELDS.forEach(field => {
261 const val = targetEntry[field]();
262 if (val === undefined || val === null) { return; }
265 if (!this.formulaValues[field]) {
266 this.formulaValues[field] = {};
268 this.formulaValues[field][val] = true;
281 this.progressMax = null;
282 this.progressValue = 0;
284 this.liService.updateLiDetails(this.lineitem).subscribe(
286 this.progressMax = struct.total;
287 this.progressValue++;
289 (err: unknown) => {},
290 () => this.load({toCache: true}).then(_ => {
291 this.liService.activateStateChange.emit(this.lineitem.id());
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())
307 getTitle(li: IdlObject): string {
308 if (!li) { return ''; }
309 return this.liService.getFirstAttributeValue(li, 'title');
312 canDeactivate(): Observable<boolean> {
314 return this.leaveConfirm.open().pipe(map(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();