]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/cat/vandelay/match-set-expression.component.ts
LP2061136 - Stamping 1405 DB upgrade script
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / cat / vandelay / match-set-expression.component.ts
1 import {Component, ViewChild, Input} from '@angular/core';
2 import {IdlObject, IdlService} from '@eg/core/idl.service';
3 import {PcrudService} from '@eg/core/pcrud.service';
4 import {NetService} from '@eg/core/net.service';
5 import {AuthService} from '@eg/core/auth.service';
6 import {OrgService} from '@eg/core/org.service';
7 import {Tree, TreeNode} from '@eg/share/tree/tree';
8 import {StringService} from '@eg/share/string/string.service';
9 import {MatchSetNewPointComponent} from './match-set-new-point.component';
10
11 @Component({
12     selector: 'eg-match-set-expression',
13     templateUrl: 'match-set-expression.component.html'
14 })
15 export class MatchSetExpressionComponent {
16
17     // Match set arrives from parent async.
18     matchSet_: IdlObject;
19     @Input() set matchSet(ms: IdlObject) {
20         this.matchSet_ = ms;
21         if (ms && !this.initDone) {
22             this.matchSetType = ms.mtype();
23             this.initDone = true;
24             this.refreshTree();
25         }
26     }
27
28     tree: Tree;
29     initDone: boolean;
30     matchSetType: string;
31     changesMade: boolean;
32
33     // Current type of new match point
34     newPointType: string;
35     newId: number;
36
37     @ViewChild('newPoint', { static: true }) newPoint: MatchSetNewPointComponent;
38
39     constructor(
40         private idl: IdlService,
41         private pcrud: PcrudService,
42         private net: NetService,
43         private auth: AuthService,
44         private org: OrgService,
45         private strings: StringService
46     ) {
47         this.newId = -1;
48     }
49
50     refreshTree(): Promise<any> {
51         if (!this.matchSet_) { return Promise.resolve(); }
52
53         return this.pcrud.search('vmsp',
54             {match_set: this.matchSet_.id()}, {},
55             {atomic: true, authoritative: true}
56         ).toPromise().then(points => {
57             if (points.length > 0) {
58                 this.ingestMatchPoints(points);
59             } else {
60                 this.addRootNode();
61             }
62         });
63     }
64
65     // When creating a new tree, add a stub boolean node
66     // as the root so the tree has something to render.
67     addRootNode() {
68
69         const point = this.idl.create('vmsp');
70         point.id(this.newId--);
71         point.isnew(true);
72         point.match_set(this.matchSet_.id());
73         point.children([]);
74         point.bool_op('AND');
75
76         const node: TreeNode = new TreeNode({
77             id: point.id(),
78             callerData: {point: point}
79         });
80
81         this.tree = new Tree(node);
82         this.setNodeLabel(node, point);
83     }
84
85     // Tree-ify a set of match points.
86     ingestMatchPoints(points: IdlObject[]) {
87         const nodes = [];
88         const idmap: any = {};
89
90         // massage data, create tree nodes
91         points.forEach(point => {
92
93             point.negate(point.negate() === 't' ? true : false);
94             point.heading(point.heading() === 't' ? true : false);
95             point.children([]);
96
97             const node = new TreeNode({
98                 id: point.id(),
99                 expanded: true,
100                 callerData: {point: point}
101             });
102             idmap[node.id + ''] = node;
103             this.setNodeLabel(node, point).then(() => nodes.push(node));
104         });
105
106         // apply the tree parent/child relationships
107         points.forEach(point => {
108             const node = idmap[point.id() + ''];
109             if (point.parent()) {
110                 idmap[point.parent() + ''].children.push(node);
111             } else {
112                 this.tree = new Tree(node);
113             }
114         });
115     }
116
117     setNodeLabel(node: TreeNode, point: IdlObject): Promise<any> {
118         if (node.label) { return Promise.resolve(null); }
119         return Promise.all([
120             this.getPointLabel(point, true).then(txt => node.label = txt),
121             this.getPointLabel(point, false).then(
122                 txt => node.callerData.slimLabel = txt)
123         ]);
124     }
125
126     getPointLabel(point: IdlObject, showmatch?: boolean): Promise<string> {
127         return this.strings.interpolate(
128             'staff.cat.vandelay.matchpoint.label',
129             {point: point, showmatch: showmatch}
130         );
131     }
132
133     nodeClicked(node: TreeNode) {}
134
135     deleteNode() {
136         this.changesMade = true;
137         const node = this.tree.selectedNode();
138         this.tree.removeNode(node);
139     }
140
141     hasSelectedNode(): boolean {
142         return Boolean(this.tree.selectedNode());
143     }
144
145     isRootNode(): boolean {
146         const node = this.tree.selectedNode();
147         if (node && this.tree.findParentNode(node) === null) {
148             return true;
149         }
150         return false;
151     }
152
153     selectedIsBool(): boolean {
154         if (this.tree) {
155             const node = this.tree.selectedNode();
156             return node && node.callerData.point.bool_op();
157         }
158         return false;
159     }
160
161     addChildNode(replace?: boolean) {
162         this.changesMade = true;
163
164         const targetNode: TreeNode = this.tree.selectedNode();
165         let point;
166
167         if (replace) {
168             point = targetNode.callerData.point;
169             point.ischanged(true);
170             // Clear previous data
171             ['bool_op', 'svf', 'tag', 'subfield', 'heading']
172                 .forEach(f => point[f](null));
173
174         } else {
175             point = this.idl.create('vmsp');
176             point.id(this.newId--);
177             point.isnew(true);
178             point.parent(targetNode.id);
179             point.match_set(this.matchSet_.id());
180             point.children([]);
181         }
182
183         const ptype = this.newPoint.values.pointType;
184
185         if (ptype === 'bool') {
186             point.bool_op(this.newPoint.values.boolOp);
187
188         } else {
189
190             if (ptype === 'attr') {
191                 point.svf(this.newPoint.values.recordAttr);
192
193             } else if (ptype === 'marc') {
194                 point.tag(this.newPoint.values.marcTag);
195                 point.subfield(this.newPoint.values.marcSf);
196             } else if (ptype === 'heading') {
197                 point.heading(true);
198             }
199
200             point.negate(this.newPoint.values.negate);
201             point.quality(this.newPoint.values.matchScore);
202         }
203
204         if (replace) {
205
206             targetNode.label = null;
207             this.setNodeLabel(targetNode, point);
208
209         } else {
210
211             const node: TreeNode = new TreeNode({
212                 id: point.id(),
213                 callerData: {point: point}
214             });
215
216             // Match points are added to the DB only when the tree is saved.
217             this.setNodeLabel(node, point)
218                 .then(() => targetNode.children.push(node));
219         }
220     }
221
222     expressionAsString(): string {
223         if (!this.tree) { return ''; }
224
225         const renderNode = (node: TreeNode): string => {
226             if (!node) { return ''; }
227
228             if (node.children.length) {
229                 return '(' + node.children.map(renderNode).join(
230                     ' ' + node.callerData.slimLabel + ' ') + ')';
231             } else if (!node.callerData.point.bool_op()) {
232                 return node.callerData.slimLabel;
233             } else {
234                 return '()';
235             }
236         };
237
238         return renderNode(this.tree.rootNode);
239     }
240
241     // Server API deletes and recreates the tree on update.
242     // It manages parent/child relationships via the children array.
243     // We only need send the current tree in a form the API recognizes.
244     saveTree(): Promise<any> {
245
246
247         const compileTree = (node?: TreeNode) => {
248
249             if (!node) { node = this.tree.rootNode; }
250
251             const point = node.callerData.point;
252
253             node.children.forEach(child =>
254                 point.children().push(compileTree(child)));
255
256             return point;
257         };
258
259         const rootPoint: IdlObject = compileTree();
260
261         return this.net.request(
262             'open-ils.vandelay',
263             'open-ils.vandelay.match_set.update',
264             this.auth.token(), this.matchSet_.id(), rootPoint
265         ).toPromise().then(
266             ok => this.refreshTree(),
267             err => console.error(err)
268         );
269     }
270 }
271