]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-sources.component.ts
LP#1904244: Angular funds interface
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / admin / acq / funds / funding-sources.component.ts
1 import {Component, Input, ViewChild, OnInit, AfterViewInit} from '@angular/core';
2 import {Location} from '@angular/common';
3 import {FormatService} from '@eg/core/format.service';
4 import {GridDataSource, GridCellTextGenerator} from '@eg/share/grid/grid';
5 import {GridComponent} from '@eg/share/grid/grid.component';
6 import {AdminPageComponent} from '@eg/staff/share/admin-page/admin-page.component';
7 import {Pager} from '@eg/share/util/pager';
8 import {ActivatedRoute} from '@angular/router';
9 import {IdlService, IdlObject} from '@eg/core/idl.service';
10 import {ToastService} from '@eg/share/toast/toast.service';
11 import {PcrudService} from '@eg/core/pcrud.service';
12 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
13 import {OrgService} from '@eg/core/org.service';
14 import {PermService} from '@eg/core/perm.service';
15 import {AuthService} from '@eg/core/auth.service';
16 import {NetService} from '@eg/core/net.service';
17 import {map, mergeMap} from 'rxjs/operators';
18 import {StringComponent} from '@eg/share/string/string.component';
19 import {Observable, forkJoin, of} from 'rxjs';
20 import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
21 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
22 import {FundingSourceTransactionsDialogComponent} from './funding-source-transactions-dialog.component';
23
24 @Component({
25     selector: 'eg-funding-sources',
26     templateUrl: './funding-sources.component.html'
27 })
28
29 export class FundingSourcesComponent extends AdminPageComponent implements OnInit, AfterViewInit {
30     idlClass = 'acqfs';
31     classLabel: string;
32
33     @Input() startId: number;
34
35     @ViewChild('grid', { static: true }) grid: GridComponent;
36     @ViewChild('fundingSourceTransactionsDialog', { static: false })
37         fundingSourceTransactionsDialog: FundingSourceTransactionsDialogComponent;
38     @ViewChild('applyCreditDialog', { static: true }) applyCreditDialog: FmRecordEditorComponent;
39     @ViewChild('allocateToFundDialog', { static: true }) allocateToFundDialog: FmRecordEditorComponent;
40     @ViewChild('alertDialog', {static: false}) private alertDialog: AlertDialogComponent;
41     @ViewChild('confirmDel', { static: true }) confirmDel: ConfirmDialogComponent;
42
43     cellTextGenerator: GridCellTextGenerator;
44     notOneSelectedRow: (rows: IdlObject[]) => boolean;
45
46     constructor(
47         route: ActivatedRoute,
48         ngLocation: Location,
49         format: FormatService,
50         idl: IdlService,
51         org: OrgService,
52         auth: AuthService,
53         pcrud: PcrudService,
54         perm: PermService,
55         toast: ToastService,
56         private net: NetService
57     ) {
58         super(route, ngLocation, format, idl, org, auth, pcrud, perm, toast);
59         this.dataSource = new GridDataSource();
60     }
61
62     ngOnInit() {
63         this.cellTextGenerator = {
64             name: row => row.name()
65         };
66         this.notOneSelectedRow = (rows: IdlObject[]) => (rows.length !== 1);
67         this.fieldOrder = 'name,code,year,org,active,currency_type,balance_stop_percentage,balance_warning_percentage,propagate,rollover';
68         this.defaultNewRecord = this.idl.create('acqfs');
69         this.defaultNewRecord.owner(this.auth.user().ws_ou());
70
71         this.dataSource.getRows = (pager: Pager, sort: any[]) => {
72             const orderBy: any = {};
73             if (sort.length) {
74                 // Sort specified from grid
75                 orderBy[this.idlClass] = sort[0].name + ' ' + sort[0].dir;
76             } else if (this.sortField) {
77                 // Default sort field
78                 orderBy[this.idlClass] = this.sortField;
79             }
80
81             const searchOps = {
82                 offset: pager.offset,
83                 limit: pager.limit,
84                 order_by: orderBy,
85                 flesh: 1,
86                 flesh_fields: {
87                     acqfs: ['credits', 'allocations']
88                 }
89             };
90             const reqOps = {
91                 fleshSelectors: true,
92             };
93
94             if (!this.contextOrg && !Object.keys(this.dataSource.filters).length) {
95                 // No org filter -- fetch all rows
96                 return this.pcrud.retrieveAll(
97                     this.idlClass, searchOps, reqOps)
98                     .pipe(mergeMap((row) => this.calculateSummary(row)));
99             }
100
101             const search: any = new Array();
102             const orgFilter: any = {};
103
104             if (this.orgField && (this.searchOrgs || this.contextOrg)) {
105                 orgFilter[this.orgField] =
106                     this.searchOrgs.orgIds || [this.contextOrg.id()];
107                 search.push(orgFilter);
108             }
109
110             Object.keys(this.dataSource.filters).forEach(key => {
111                 Object.keys(this.dataSource.filters[key]).forEach(key2 => {
112                     search.push(this.dataSource.filters[key][key2]);
113                 });
114             });
115
116             return this.pcrud.search(
117                 this.idlClass, search, searchOps, reqOps)
118                 .pipe(mergeMap((row) => this.calculateSummary(row)));
119         };
120
121         super.ngOnInit();
122
123         this.classLabel = this.idlClassDef.label;
124         this.includeOrgDescendants = true;
125     }
126
127     ngAfterViewInit() {
128         if (this.startId) {
129             this.pcrud.retrieve('acqfs', this.startId).subscribe(
130                 acqfs => this.openTransactionsDialog([acqfs], 'allocations'),
131                 err => {},
132                 () => this.startId = null
133             );
134         }
135     }
136
137     calculateSummary(row: IdlObject): Observable<IdlObject> {
138         row['balance'] = 0;
139         row['total_credits'] = 0;
140         row['total_allocations'] = 0;
141
142         row.credits().forEach((c) => row['total_credits'] += Number(c.amount()));
143         row.allocations().forEach((a) => row['total_allocations'] += Number(a.amount()));
144         row['balance'] = row['total_credits'] - row['total_allocations'];
145         return of(row);
146     }
147
148     deleteSelected(rows: IdlObject[]) {
149         if (rows.length > 0) {
150             const id = rows[0].id();
151             let can = true;
152             forkJoin([
153                 this.pcrud.search('acqfa',      { funding_source: id }, { limit: 1 }, { atomic: true }),
154                 this.pcrud.search('acqfscred',  { funding_source: id }, { limit: 1 }, { atomic: true }),
155             ]).subscribe(
156                 results => {
157                     results.forEach((res) => {
158                         if (res.length > 0) {
159                             can = false;
160                         }
161                     });
162                 },
163                 err => {},
164                 () => {
165                     if (can) {
166                         this.confirmDel.open().subscribe(confirmed => {
167                             if (!confirmed) { return; }
168                             super.deleteSelected([ rows[0] ]);
169                         });
170                     } else {
171                         this.alertDialog.open();
172                     }
173                 }
174             );
175         }
176     }
177
178     openTransactionsDialog(rows: IdlObject[], tab: string) {
179         if (rows.length !== 1) { return; }
180         this.fundingSourceTransactionsDialog.fundingSourceId = rows[0].id();
181         this.fundingSourceTransactionsDialog.activeTab = tab;
182         this.fundingSourceTransactionsDialog.open({size: 'xl'}).subscribe(
183             res => {},
184             err => {},
185             () => this.grid.reload()
186         );
187     }
188
189     createCredit(rows: IdlObject[]) {
190         if (rows.length !== 1) { return; }
191         const fundingSourceId = rows[0].id();
192         const credit = this.idl.create('acqfscred');
193         credit.funding_source(fundingSourceId);
194         this.applyCreditDialog.defaultNewRecord = credit;
195         this.applyCreditDialog.mode = 'create';
196         this.applyCreditDialog.hiddenFieldsList = ['id', 'funding_source'];
197         this.applyCreditDialog.fieldOrder = 'amount,note,effective_date,deadline_date';
198         this.applyCreditDialog.open().subscribe(
199              result => {
200                 this.successString.current()
201                     .then(str => this.toast.success(str));
202                 this.grid.reload();
203             },
204             error => {
205                 this.updateFailedString.current()
206                     .then(str => this.toast.danger(str));
207             }
208         );
209     }
210
211     allocateToFund(rows: IdlObject[]) {
212         if (rows.length !== 1) { return; }
213         const fundingSourceId = rows[0].id();
214         const allocation = this.idl.create('acqfa');
215         allocation.funding_source(fundingSourceId);
216         allocation.allocator(this.auth.user().id());
217         this.allocateToFundDialog.defaultNewRecord = allocation;
218         this.allocateToFundDialog.mode = 'create';
219
220         this.allocateToFundDialog.hiddenFieldsList = ['id', 'funding_source', 'allocator', 'create_time'];
221         this.allocateToFundDialog.fieldOrder = 'fund,amount,note';
222         this.allocateToFundDialog.open().subscribe(
223              result => {
224                 this.successString.current()
225                     .then(str => this.toast.success(str));
226                 this.grid.reload();
227             },
228             error => {
229                 this.updateFailedString.current()
230                     .then(str => this.toast.danger(str));
231             }
232         );
233     }
234 }