]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.component.ts
LP2061136 - Stamping 1405 DB upgrade script
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / admin / server / org-unit.component.ts
1 import {Component, Input, ViewChild, OnInit} from '@angular/core';
2 import {Tree, TreeNode} from '@eg/share/tree/tree';
3 import {IdlService, IdlObject} from '@eg/core/idl.service';
4 import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
5 import {OrgService} from '@eg/core/org.service';
6 import {AuthService} from '@eg/core/auth.service';
7 import {PcrudService} from '@eg/core/pcrud.service';
8 import {ToastService} from '@eg/share/toast/toast.service';
9 import {StringComponent} from '@eg/share/string/string.component';
10 import {StringService} from '@eg/share/string/string.service';
11 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
12 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
13 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
14
15 @Component({
16     templateUrl: './org-unit.component.html'
17 })
18 export class OrgUnitComponent implements OnInit {
19
20     tree: Tree;
21     selected: TreeNode;
22     @ViewChild('editString', { static: true }) editString: StringComponent;
23     @ViewChild('errorString', { static: true }) errorString: StringComponent;
24     @ViewChild('delConfirm', { static: true }) delConfirm: ConfirmDialogComponent;
25
26     constructor(
27         private idl: IdlService,
28         private org: OrgService,
29         private auth: AuthService,
30         private pcrud: PcrudService,
31         private strings: StringService,
32         private toast: ToastService
33     ) {}
34
35
36     ngOnInit() {
37         this.loadAouTree(this.org.root().id());
38     }
39
40     tabChanged(evt: NgbTabChangeEvent) {
41         const tab = evt.nextId;
42         // stubbing out in case we need it.
43     }
44
45     orgSaved(orgId: number | IdlObject) {
46         let id;
47
48         if (orgId) { // new org created, focus it.
49             id = typeof orgId === 'object' ? orgId.id() : orgId;
50         } else if (this.currentOrg()) {
51             id = this.currentOrg().id();
52         }
53
54         this.loadAouTree(id).then(_ => this.postUpdate(this.editString));
55     }
56
57     orgDeleted() {
58         this.loadAouTree();
59     }
60
61     loadAouTree(selectNodeId?: number): Promise<any> {
62
63         const flesh = ['children', 'ou_type', 'hours_of_operation'];
64
65         return this.pcrud.search('aou', {parent_ou : null},
66             {flesh : -1, flesh_fields : {aou : flesh}}, {authoritative: true}
67
68         ).toPromise().then(tree => {
69             this.ingestAouTree(tree);
70             if (!selectNodeId) { selectNodeId = this.org.root().id(); }
71
72             const node = this.tree.findNode(selectNodeId);
73             this.selected = node;
74             this.tree.selectNode(node);
75         });
76     }
77
78     // Translate the org unt type tree into a structure EgTree can use.
79     ingestAouTree(aouTree) {
80
81         const handleNode = (orgNode: IdlObject): TreeNode => {
82             if (!orgNode) { return; }
83
84             if (!orgNode.hours_of_operation()) {
85                 this.generateHours(orgNode);
86             }
87
88             const treeNode = new TreeNode({
89                 id: orgNode.id(),
90                 label: orgNode.name(),
91                 callerData: {orgUnit: orgNode}
92             });
93
94             // Apply the compiled label asynchronously
95             this.strings.interpolate(
96                 'admin.server.org_unit.treenode', {org: orgNode}
97             ).then(label => treeNode.label = label);
98
99             // Tree node labels are "name -- shortname".  Sorting
100             // by name suffices and bypasses the need the wait
101             // for all of the labels to interpolate.
102             orgNode.children()
103             .sort((a, b) => a.name() < b.name() ? -1 : 1)
104             .forEach(childNode =>
105                 treeNode.children.push(handleNode(childNode))
106             );
107
108             return treeNode;
109         };
110
111         const rootNode = handleNode(aouTree);
112         this.tree = new Tree(rootNode);
113     }
114
115     nodeClicked($event: any) {
116         this.selected = $event;
117     }
118
119     generateHours(org: IdlObject) {
120         const hours = this.idl.create('aouhoo');
121         hours.id(org.id());
122         hours.isnew(true);
123
124         [0, 1, 2, 3, 4, 5, 6].forEach(dow => {
125             this.hours(dow, 'open', '09:00:00', hours);
126             this.hours(dow, 'close', '17:00:00', hours);
127         });
128
129         org.hours_of_operation(hours);
130     }
131
132     // if a 'value' is passed, it will be applied to the optional
133     // hours-of-operation object, otherwise the hours on the currently
134     // selected org unit.
135     hours(dow: number, which: 'open' | 'close', value?: string, hoo?: IdlObject): string {
136         if (!hoo && !this.selected) { return null; }
137
138         const hours = hoo || this.selected.callerData.orgUnit.hours_of_operation();
139
140         if (value) {
141             hours[`dow_${dow}_${which}`](value);
142             hours.ischanged(true);
143         }
144
145         return hours[`dow_${dow}_${which}`]();
146     }
147
148     isClosed(dow: number): boolean {
149         return (
150             this.hours(dow, 'open') === '00:00:00' &&
151             this.hours(dow, 'close') === '00:00:00'
152         );
153     }
154
155     closedOn(dow: number) {
156         this.hours(dow, 'open', '00:00:00');
157         this.hours(dow, 'close', '00:00:00');
158     }
159
160     saveHours() {
161         const org = this.currentOrg();
162         const hours = org.hours_of_operation();
163         this.pcrud.autoApply(hours).subscribe(
164             result => {
165                 console.debug('Hours saved ', result);
166                 this.editString.current()
167                     .then(msg => this.toast.success(msg));
168             },
169             error => {
170                 this.errorString.current()
171                     .then(msg => this.toast.danger(msg));
172             },
173             () => this.loadAouTree(this.selected.id)
174         );
175     }
176
177     deleteHours() {
178         const hours = this.currentOrg().hours_of_operation();
179         const promise = hours.isnew() ? Promise.resolve() :
180             this.pcrud.remove(hours).toPromise();
181
182         promise.then(_ => this.generateHours(this.currentOrg()));
183     }
184
185     currentOrg(): IdlObject {
186         return this.selected ? this.selected.callerData.orgUnit : null;
187     }
188
189     orgHasChildren(): boolean {
190         const org = this.currentOrg();
191         return (org && org.children().length > 0);
192     }
193
194     postUpdate(message: StringComponent) {
195         // Modifying org unit types means refetching the org unit
196         // data normally fetched on page load, since it includes
197         // org unit type data.
198         this.org.fetchOrgs().then(() =>
199             message.current().then(str => this.toast.success(str)));
200     }
201
202     remove() {
203         this.delConfirm.open().subscribe(confirmed => {
204             if (!confirmed) { return; }
205
206             const org = this.selected.callerData.orgUnit;
207
208             this.pcrud.remove(org).subscribe(
209                 ok2 => {},
210                 err => {
211                     this.errorString.current()
212                       .then(str => this.toast.danger(str));
213                 },
214                 ()  => {
215                     // Avoid updating until we know the entire
216                     // pcrud action/transaction completed.
217                     // After removal, select the parent org if available
218                     // otherwise the root org.
219                     const orgId = org.parent_ou() ?
220                         org.parent_ou() : this.org.root().id();
221                     this.loadAouTree(orgId).then(_ =>
222                         this.postUpdate(this.editString));
223                 }
224             );
225         });
226     }
227
228     orgTypeOptions(): ComboboxEntry[] {
229         let ouType = this.currentOrg().ou_type();
230
231         if (typeof ouType === 'number') {
232             // May not be fleshed for new org units
233             ouType = this.org.typeMap()[ouType];
234         }
235         const curDepth = ouType.depth();
236
237         return this.org.typeList()
238             .filter(type_ => type_.depth() === curDepth)
239             .map(type_ => ({id: type_.id(), label: type_.name()}));
240     }
241
242     orgChildTypes(): IdlObject[] {
243         let ouType = this.currentOrg().ou_type();
244
245         if (typeof ouType === 'number') {
246             // May not be fleshed for new org units
247             ouType = this.org.typeMap()[ouType];
248         }
249
250         const depth = ouType.depth();
251         return this.org.typeList()
252             .filter(type_ => type_.depth() === depth + 1);
253     }
254
255     addChild() {
256         const parentTreeNode = this.selected;
257         const parentOrg = this.currentOrg();
258         const newType = this.orgChildTypes()[0];
259
260         const org = this.idl.create('aou');
261         org.isnew(true);
262         org.parent_ou(parentOrg.id());
263         org.ou_type(newType.id());
264         org.children([]);
265
266         // Create a dummy, detached org node to keep the UI happy.
267         this.selected = new TreeNode({
268             id: org.id(),
269             label: org.name(),
270             callerData: {orgUnit: org}
271         });
272     }
273
274     addressChanged(thing: any) {
275         // Reload to pick up org unit address changes.
276         this.orgSaved(this.currentOrg().id());
277     }
278 }
279