]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.component.ts
LP2061136 - Stamping 1405 DB upgrade script
[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     static domIdAuto = 0;
34
35     // Limit copy locations to those owned at or above org units where
36     // the user has work permissions for the provided permission code.
37     @Input() permFilter: string;
38
39     // Limit copy locations to those owned at or above this org unit.
40     private _contextOrgId: number;
41     @Input() set contextOrgId(value: number) {
42         this._contextOrgId = value;
43         this.ngOnInit();
44     }
45
46     get contextOrgId(): number {
47         return this._contextOrgId;
48     }
49
50     // Load locations for multiple context org units.
51     private _contextOrgIds = [];
52     @Input() set contextOrgIds(value: number[]) {
53         this._contextOrgIds = value;
54     }
55
56     get contextOrgIds(): number[] {
57         return this._contextOrgIds;
58     }
59
60     @Input() orgUnitLabelField = 'shortname';
61
62     // Emits an acpl object or null on combobox value change
63     @Output() valueChange: EventEmitter<IdlObject>;
64
65     @Input() required: boolean;
66
67     @Input() domId = 'eg-item-location-select-' +
68         ItemLocationSelectComponent.domIdAuto++;
69
70     @ViewChild('comboBox', {static: false}) comboBox: ComboboxComponent;
71     @ViewChild('unsetString', {static: false}) unsetString: StringComponent;
72
73     startId: number = null;
74     filterOrgs: number[] = [];
75     cache: {[id: number]: IdlObject} = {};
76
77     initDone = false; // true after first data load
78     propagateChange = (id: number) => {};
79     propagateTouch = () => {};
80
81     constructor(
82         private org: OrgService,
83         private auth: AuthService,
84         private perm: PermService,
85         private pcrud: PcrudService
86     ) {
87         this.valueChange = new EventEmitter<IdlObject>();
88     }
89
90     ngOnInit() {
91         this.setFilterOrgs()
92         .then(_ => this.getLocations())
93         .then(_ => this.initDone = true);
94     }
95
96     ngAfterViewInit() {
97
98         // Format the display of locations to include the org unit
99         this.comboBox.formatDisplayString = (result: ComboboxEntry) => {
100             let display = result.label || result.id;
101             display = (display + '').trim();
102             if (result.userdata) {
103                 display += ' (' +
104                     this.orgName(result.userdata.owning_lib()) + ')';
105             }
106             return display;
107         };
108     }
109
110     getLocations(): Promise<any> {
111
112         if (this.filterOrgs.length === 0) {
113             this.comboBox.entries = [];
114             return Promise.resolve();
115         }
116
117         const search: any = {deleted: 'f'};
118
119         if (this.startId) {
120             // Guarantee we have the load-time copy location, which
121             // may not be included in the org-scoped set of locations
122             // we fetch by default.
123             search['-or'] = [
124                 {id: this.startId},
125                 {owning_lib: this.filterOrgs}
126             ];
127         } else {
128             search.owning_lib = this.filterOrgs;
129         }
130
131         const entries: ComboboxEntry[] = [];
132
133         if (!this.required) {
134             entries.push({id: null, label: this.unsetString.text});
135         }
136
137         return this.pcrud.search('acpl', search, {order_by: {acpl: 'name'}}
138         ).pipe(map(loc => {
139             this.cache[loc.id()] = loc;
140             entries.push({id: loc.id(), label: loc.name(), userdata: loc});
141         })).toPromise().then(_ => {
142             this.comboBox.entries = entries;
143         });
144     }
145
146     registerOnChange(fn) {
147         this.propagateChange = fn;
148     }
149
150     registerOnTouched(fn) {
151         this.propagateTouch = fn;
152     }
153
154     cboxChanged(entry: ComboboxEntry) {
155         const id = entry ? entry.id : null;
156         this.propagateChange(id);
157         this.valueChange.emit(id ? this.cache[id] : null);
158     }
159
160     writeValue(id: number) {
161         if (this.initDone) {
162             this.getOneLocation(id).then(_ => this.comboBox.selectedId = id);
163         } else {
164             this.startId = id;
165         }
166     }
167
168     getOneLocation(id: number) {
169         if (!id || this.cache[id]) { return Promise.resolve(); }
170
171         return this.pcrud.retrieve('acpl', id).toPromise()
172         .then(loc => {
173             this.cache[loc.id()] = loc;
174             this.comboBox.addAsyncEntry(
175                 {id: loc.id(), label: loc.name(), userdata: loc});
176         });
177     }
178
179     setFilterOrgs(): Promise<number[]> {
180         let contextOrgIds: number[] = [];
181
182         if (this.contextOrgIds) {
183             contextOrgIds = this.contextOrgIds;
184         } else {
185             contextOrgIds = [this.contextOrgId || this.auth.user().ws_ou()];
186         }
187
188         let orgIds = [];
189         contextOrgIds.forEach(id => orgIds = orgIds.concat(this.org.ancestors(id, true)));
190
191         if (!this.permFilter) {
192             return Promise.resolve(this.filterOrgs = orgIds);
193         }
194
195         return this.perm.hasWorkPermAt([this.permFilter], true)
196         .then(values => {
197             // Always limit the org units to /at most/ those within
198             // scope of the context org ID.
199
200             const permOrgIds = values[this.permFilter];
201             const trimmedOrgIds = [];
202             permOrgIds.forEach(orgId => {
203                 if (orgIds.includes(orgId)) {
204                     trimmedOrgIds.push(orgId);
205                 }
206             });
207
208             return this.filterOrgs = trimmedOrgIds;
209         });
210     }
211
212     orgName(orgId: number): string {
213         return this.org.get(orgId)[this.orgUnitLabelField]();
214     }
215 }
216
217
218