]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-details-dialog.component.ts
LP#1904244: Angular funds interface
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / admin / acq / funds / fund-details-dialog.component.ts
1 import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core';
2 import {DialogComponent} from '@eg/share/dialog/dialog.component';
3 import {IdlService, IdlObject} from '@eg/core/idl.service';
4 import {FormatService} from '@eg/core/format.service';
5 import {EventService} from '@eg/core/event.service';
6 import {NetService} from '@eg/core/net.service';
7 import {AuthService} from '@eg/core/auth.service';
8 import {PcrudService} from '@eg/core/pcrud.service';
9 import {StoreService} from '@eg/core/store.service';
10 import {OrgService} from '@eg/core/org.service';
11 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
12 import {GridDataSource, GridCellTextGenerator} from '@eg/share/grid/grid';
13 import {Pager} from '@eg/share/util/pager';
14 import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
15 import {StringComponent} from '@eg/share/string/string.component';
16 import {ToastService} from '@eg/share/toast/toast.service';
17 import {FundTagsComponent} from './fund-tags.component';
18 import {FundTransferDialogComponent} from './fund-transfer-dialog.component';
19 import {map, mergeMap} from 'rxjs/operators';
20 import {Observable, forkJoin, of} from 'rxjs';
21
22 @Component({
23   selector: 'eg-fund-details-dialog',
24   templateUrl: './fund-details-dialog.component.html'
25 })
26
27 export class FundDetailsDialogComponent
28   extends DialogComponent implements OnInit {
29
30     @Input() fundId: number;
31     fund: IdlObject;
32     idlDef: any;
33     fieldOrder: any;
34     acqfaDataSource: GridDataSource;
35     acqftrDataSource: GridDataSource;
36     acqfdebDataSource: GridDataSource;
37
38     @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
39     @ViewChild('transferDialog', { static: false }) transferDialog: FundTransferDialogComponent;
40     @ViewChild('allocateToFundDialog', { static: true }) allocateToFundDialog: FmRecordEditorComponent;
41     @ViewChild('successString', { static: true }) successString: StringComponent;
42     @ViewChild('updateFailedString', { static: false }) updateFailedString: StringComponent;
43
44     activeTab = 'summary';
45     defaultTabType = 'summary';
46     cellTextGenerator: GridCellTextGenerator;
47
48     constructor(
49         private idl: IdlService,
50         private evt: EventService,
51         private net: NetService,
52         private auth: AuthService,
53         private pcrud: PcrudService,
54         private store: StoreService,
55         private org: OrgService,
56         private format: FormatService,
57         private toast: ToastService,
58         private modal: NgbModal
59     ) {
60         super(modal);
61     }
62
63     ngOnInit() {
64
65         this.defaultTabType =
66             this.store.getLocalItem('eg.acq.fund_details.default_tab') || 'summary';
67         this.activeTab = this.defaultTabType;
68
69         this.fund = null;
70         this.onOpen$.subscribe(() => {
71             this.activeTab = this.defaultTabType;
72             this._initRecord();
73         });
74         this.idlDef = this.idl.classes['acqf'];
75         this.fieldOrder = 'name,code,year,org,active,currency_type,balance_stop_percentage,balance_warning_percentage,propagate,rollover';
76
77         this.cellTextGenerator = {
78             src_fund: row =>
79                 row.src_fund().code() + ' (' +
80                 row.src_fund().year() + ') (' +
81                 this.getOrgShortname(row.src_fund().org()) + ')',
82             dest_fund: row =>
83                 row.dest_fund().code() + ' (' +
84                 row.dest_fund().year() + ') (' +
85                 this.getOrgShortname(row.dest_fund().org()) + ')',
86             funding_source: row =>
87                 row.funding_source().code() + ' (' +
88                     this.getOrgShortname(row.funding_source().owner()) + ')',
89             funding_source_credit: row =>
90                 row.funding_source_credit().funding_source().code() + ' (' +
91                     this.getOrgShortname(row.funding_source_credit().funding_source().owner()) + ')',
92             li: row => row.li_id,
93             po: row => row.po_name,
94             invoice: row => row.vendor_invoice_id
95         };
96     }
97
98     private _initRecord() {
99         this.fund = null;
100         this.acqfaDataSource = this._getDataSource('acqfa', 'create_time ASC');
101         this.acqftrDataSource = this._getDataSource('acqftr', 'transfer_time ASC');
102         this.acqfdebDataSource = this._getDataSource('acqfdeb', 'create_time ASC');
103         this.pcrud.retrieve('acqf', this.fundId, {
104             flesh: 1,
105             flesh_fields: {
106                 acqf: [
107                     'spent_balance',
108                     'combined_balance',
109                     'spent_total',
110                     'encumbrance_total',
111                     'debit_total',
112                     'allocation_total',
113                     'org',
114                     'currency_type'
115                 ]
116             }
117         }).subscribe(res => this.fund = res);
118     }
119
120     _getDataSource(idlClass: string, sortField: string): GridDataSource {
121         const gridSource = new GridDataSource();
122
123         gridSource.getRows = (pager: Pager, sort: any[]) => {
124             const orderBy: any = {};
125             if (sort.length) {
126                 // Sort specified from grid
127                 orderBy[idlClass] = sort[0].name + ' ' + sort[0].dir;
128             } else if (sortField) {
129                 // Default sort field
130                 orderBy[idlClass] = sortField;
131             }
132
133             const searchOps = {
134                 offset: pager.offset,
135                 limit: pager.limit,
136                 order_by: orderBy,
137             };
138             const reqOps = {
139                 fleshSelectors: true,
140             };
141
142             const search: any = new Array();
143             if (idlClass === 'acqfa') {
144                 search.push({ fund: this.fundId });
145             } else if (idlClass === 'acqftr') {
146                 search.push({
147                     '-or': [
148                         { src_fund: this.fundId },
149                         { dest_fund: this.fundId }
150                     ]
151                 });
152                 searchOps['flesh'] = 2;
153                 searchOps['flesh_fields'] = {
154                     'acqftr': ['funding_source_credit'],
155                     'acqfscred': ['funding_source']
156                 };
157             } else if (idlClass === 'acqfdeb') {
158                 search.push({ fund: this.fundId });
159                 searchOps['flesh'] = 3;
160                 searchOps['flesh_fields'] = {
161                     'acqfdeb': ['invoice_entry', 'invoice_items', 'po_items', 'lineitem_details'],
162                     'acqie': ['invoice', 'purchase_order', 'lineitem'],
163                     'acqii': ['invoice'],
164                     'acqpoi': ['purchase_order'],
165                     'acqlid': ['lineitem'],
166                     'jub': ['purchase_order']
167                 };
168             }
169
170             Object.keys(gridSource.filters).forEach(key => {
171                 Object.keys(gridSource.filters[key]).forEach(key2 => {
172                     search.push(gridSource.filters[key][key2]);
173                 });
174             });
175
176             return this.pcrud.search(idlClass, search, searchOps, reqOps)
177             .pipe(mergeMap((row) => this.doExtraFleshing(row)));
178         };
179
180         return gridSource;
181     }
182
183     doExtraFleshing(row: IdlObject): Observable<IdlObject> {
184         if (row.classname === 'acqfdeb') {
185             row['vendor_invoice_id'] = null;
186             row['invoice_id'] = null;
187             row['po_id'] = null;
188             row['po_name'] = null;
189             row['li_id'] = null;
190             // TODO need to verify this, but we may be able to get away with
191             //      the assumption that a given fund debit never has more than
192             //      one line item, purchase order, or invoice associated with it.
193             if (row.invoice_entry()) {
194                 if (row.invoice_entry().invoice()) {
195                     row['invoice_id'] = row.invoice_entry().invoice().id();
196                     row['vendor_invoice_id'] = row.invoice_entry().invoice().inv_ident();
197                 }
198                 if (row.invoice_entry().purchase_order()) {
199                     row['po_id'] = row.invoice_entry().purchase_order().id();
200                     row['po_name'] = row.invoice_entry().purchase_order().name();
201                 }
202                 if (row.invoice_entry().lineitem()) {
203                     row['li_id'] = row.invoice_entry().lineitem().id();
204                 }
205             }
206             if (row.lineitem_details() && row.lineitem_details().length) {
207                 if (row.lineitem_details()[0].lineitem().purchase_order()) {
208                     row['li_id'] = row.lineitem_details()[0].lineitem().id();
209                     row['po_id'] = row.lineitem_details()[0].lineitem().purchase_order().id();
210                     row['po_name'] = row.lineitem_details()[0].lineitem().purchase_order().name();
211                 }
212             }
213             if (row.po_items() && row.po_items().length) {
214                 if (row.po_items()[0].purchase_order()) {
215                     row['po_id'] = row.po_items()[0].purchase_order().id();
216                     row['po_name'] = row.po_items()[0].purchase_order().name();
217                 }
218             }
219             if (row.invoice_items() && row.invoice_items().length) {
220                 if (row.invoice_items()[0].invoice()) {
221                     row['invoice_id'] = row.invoice_items()[0].invoice().id();
222                     row['vendor_invoice_id'] = row.invoice_items()[0].invoice().inv_ident();
223                 }
224             }
225         }
226         return of(row);
227     }
228     formatCurrency(value: any) {
229         return this.format.transform({
230             value: value,
231             datatype: 'money'
232         });
233     }
234
235     openEditDialog() {
236         this.editDialog.recordId = this.fundId;
237         this.editDialog.mode = 'update';
238         this.editDialog.open({size: 'lg'}).subscribe(
239             result => {
240                 this.successString.current()
241                     .then(str => this.toast.success(str));
242                 this._initRecord();
243             },
244             error => {
245                 this.updateFailedString.current()
246                     .then(str => this.toast.danger(str));
247             }
248         );
249     }
250
251     allocateToFund() {
252         const allocation = this.idl.create('acqfa');
253         allocation.fund(this.fundId);
254         allocation.allocator(this.auth.user().id());
255         this.allocateToFundDialog.defaultNewRecord = allocation;
256         this.allocateToFundDialog.mode = 'create';
257
258         this.allocateToFundDialog.hiddenFieldsList = ['id', 'fund', 'allocator', 'create_time'];
259         this.allocateToFundDialog.fieldOrder = 'funding_source,amount,note';
260         this.allocateToFundDialog.open().subscribe(
261              result => {
262                 this.successString.current()
263                     .then(str => this.toast.success(str));
264                 this._initRecord();
265             },
266             error => {
267                 this.updateFailedString.current()
268                     .then(str => this.toast.danger(str));
269             }
270         );
271     }
272
273     doTransfer() {
274         this.transferDialog.sourceFund = this.fund;
275         this.transferDialog.open().subscribe(
276             ok => this._initRecord()
277         );
278     }
279
280     setDefaultTab() {
281         this.defaultTabType = this.activeTab;
282         this.store.setLocalItem('eg.acq.fund_details.default_tab', this.activeTab);
283     }
284
285     getOrgShortname(ou: any) {
286         if (typeof ou === 'object') {
287             return ou.shortname();
288         } else {
289             return this.org.get(ou).shortname();
290         }
291     }
292
293     checkNegativeAmount(val: any) {
294         return Number(val) < 0;
295     }
296 }