]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.component.ts
LP#1904244: item-location-select: include ancestors
[Evergreen.git] / Open-ILS / src / eg2 / src / app / share / item-location-select / item-location-select.component.ts
1 import {Component, OnInit, AfterViewInit, Input, Output, ViewChild,
2     EventEmitter, forwardRef} from '@angular/core';
3 import {ControlValueAccessor, FormGroup, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';
4 import {Observable} from 'rxjs';
5 import {map} from 'rxjs/operators';
6 import {IdlObject} from '@eg/core/idl.service';
7 import {OrgService} from '@eg/core/org.service';
8 import {AuthService} from '@eg/core/auth.service';
9 import {PermService} from '@eg/core/perm.service';
10 import {PcrudService} from '@eg/core/pcrud.service';
11 import {ComboboxComponent, ComboboxEntry} from '@eg/share/combobox/combobox.component';
12 import {StringComponent} from '@eg/share/string/string.component';
13
14 /**
15  * Item (Copy) Location Selector.
16  *
17  * <eg-item-location-select [(ngModel)]="myAcplId"
18     [contextOrgId]="anOrgId" permFilter="ADMIN_STUFF">
19  * </eg-item-location-select>
20  */
21
22 @Component({
23   selector: 'eg-item-location-select',
24   templateUrl: './item-location-select.component.html',
25   providers: [{
26       provide: NG_VALUE_ACCESSOR,
27       useExisting: forwardRef(() => ItemLocationSelectComponent),
28       multi: true
29   }]
30 })
31 export class ItemLocationSelectComponent
32     implements OnInit, AfterViewInit, ControlValueAccessor {
33
34     // Limit copy locations to those owned at or above org units where
35     // the user has work permissions for the provided permission code.
36     @Input() permFilter: string;
37
38     // Limit copy locations to those owned at or above this org unit.
39     @Input() contextOrgId: number;
40
41     @Input() orgUnitLabelField = 'shortname';
42
43     // Emits an acpl object or null on combobox value change
44     @Output() valueChange: EventEmitter<IdlObject>;
45
46     @Input() required: boolean;
47
48     @ViewChild('comboBox', {static: false}) comboBox: ComboboxComponent;
49     @ViewChild('unsetString', {static: false}) unsetString: StringComponent;
50
51     startId: number = null;
52     filterOrgs: number[] = [];
53     cache: {[id: number]: IdlObject} = {};
54
55     initDone = false; // true after first data load
56     propagateChange = (id: number) => {};
57     propagateTouch = () => {};
58
59     constructor(
60         private org: OrgService,
61         private auth: AuthService,
62         private perm: PermService,
63         private pcrud: PcrudService
64     ) {
65         this.valueChange = new EventEmitter<IdlObject>();
66     }
67
68     ngOnInit() {
69         this.setFilterOrgs()
70         .then(_ => this.getLocations())
71         .then(_ => this.initDone = true);
72     }
73
74     ngAfterViewInit() {
75
76         // Format the display of locations to include the org unit
77         this.comboBox.formatDisplayString = (result: ComboboxEntry) => {
78             let display = result.label || result.id;
79             display = (display + '').trim();
80             if (result.userdata) {
81                 display += ' (' +
82                     this.orgName(result.userdata.owning_lib()) + ')';
83             }
84             return display;
85         };
86     }
87
88     getLocations(): Promise<any> {
89
90         if (this.filterOrgs.length === 0) {
91             this.comboBox.entries = [];
92             return Promise.resolve();
93         }
94
95         const search: any = {deleted: 'f'};
96
97         if (this.startId) {
98             // Guarantee we have the load-time copy location, which
99             // may not be included in the org-scoped set of locations
100             // we fetch by default.
101             search['-or'] = [
102                 {id: this.startId},
103                 {owning_lib: this.filterOrgs}
104             ];
105         } else {
106             search.owning_lib = this.filterOrgs;
107         }
108
109         const entries: ComboboxEntry[] = [];
110
111         if (!this.required) {
112             entries.push({id: null, label: this.unsetString.text});
113         }
114
115         return this.pcrud.search('acpl', search, {order_by: {acpl: 'name'}}
116         ).pipe(map(loc => {
117             this.cache[loc.id()] = loc;
118             entries.push({id: loc.id(), label: loc.name(), userdata: loc});
119         })).toPromise().then(_ => {
120             this.comboBox.entries = entries;
121         });
122     }
123
124     registerOnChange(fn) {
125         this.propagateChange = fn;
126     }
127
128     registerOnTouched(fn) {
129         this.propagateTouch = fn;
130     }
131
132     cboxChanged(entry: ComboboxEntry) {
133         const id = entry ? entry.id : null;
134         this.propagateChange(id);
135         this.valueChange.emit(id ? this.cache[id] : null);
136     }
137
138     writeValue(id: number) {
139         if (this.initDone) {
140             this.getOneLocation(id).then(_ => this.comboBox.selectedId = id);
141         } else {
142             this.startId = id;
143         }
144     }
145
146     getOneLocation(id: number) {
147         if (!id || this.cache[id]) { return Promise.resolve(); }
148
149         return this.pcrud.retrieve('acpl', id).toPromise()
150         .then(loc => {
151             this.cache[loc.id()] = loc;
152             this.comboBox.entries.push(
153                 {id: loc.id(), label: loc.name(), userdata: loc});
154         });
155     }
156
157     setFilterOrgs(): Promise<number[]> {
158         if (this.permFilter) {
159             return this.perm.hasWorkPermAt([this.permFilter], true)
160                 .then(values => {
161                     this.filterOrgs = values[this.permFilter];
162                     // then include ancestors
163                     this.filterOrgs.forEach(ou => {
164                         this.org.ancestors(ou, true).forEach(anc => this.filterOrgs.push(anc));
165                     });
166                     return this.filterOrgs;
167                 });
168         }
169
170         const org = this.contextOrgId || this.auth.user().ws_ou();
171         this.filterOrgs = this.org.ancestors(this.contextOrgId, true);
172
173         return Promise.resolve(this.filterOrgs);
174     }
175
176     orgName(orgId: number): string {
177         return this.org.get(orgId)[this.orgUnitLabelField]();
178     }
179 }
180
181
182