]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/booking/create-reservation-dialog.component.ts
LP 1816655: Add a patron search button to create booking screen
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / booking / create-reservation-dialog.component.ts
1 import {Component, Input, Output, OnInit, ViewChild, EventEmitter} from '@angular/core';
2 import {FormGroup, FormControl, Validators, ValidatorFn, ValidationErrors} from '@angular/forms';
3 import {Router} from '@angular/router';
4 import {Observable, of} from 'rxjs';
5 import {switchMap, single, startWith, tap} from 'rxjs/operators';
6 import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
7 import {AuthService} from '@eg/core/auth.service';
8 import {FormatService} from '@eg/core/format.service';
9 import {IdlObject} from '@eg/core/idl.service';
10 import {NetService} from '@eg/core/net.service';
11 import {OrgService} from '@eg/core/org.service';
12 import {PcrudService} from '@eg/core/pcrud.service';
13 import {DialogComponent} from '@eg/share/dialog/dialog.component';
14 import {notBeforeMomentValidator} from '@eg/share/validators/not_before_moment_validator.directive';
15 import {PatronBarcodeValidator} from '@eg/share/validators/patron_barcode_validator.directive';
16 import {PatronSearchDialogComponent} from '@eg/staff/share/patron/search-dialog.component';
17 import {ToastService} from '@eg/share/toast/toast.service';
18 import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
19 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
20 import * as moment from 'moment-timezone';
21
22 const startTimeIsBeforeEndTimeValidator: ValidatorFn = (fg: FormGroup): ValidationErrors | null => {
23     const start = fg.get('startTime').value;
24     const end = fg.get('endTime').value;
25     return start !== null && end !== null &&
26         start.isBefore(end)
27         ? null
28         : { startTimeNotBeforeEndTime: true };
29 };
30
31 @Component({
32   selector: 'eg-create-reservation-dialog',
33   templateUrl: './create-reservation-dialog.component.html'
34 })
35
36 export class CreateReservationDialogComponent
37     extends DialogComponent implements OnInit {
38
39     @Input() targetResource: number;
40     @Input() targetResourceBarcode: string;
41     @Input() targetResourceType: ComboboxEntry;
42     @Input() patronId: number;
43     @Input() attributes: number[] = [];
44     @Input() resources: IdlObject[] = [];
45     @Output() onComplete: EventEmitter<boolean>;
46
47     create: FormGroup;
48     patron$: Observable<{first_given_name: string, second_given_name: string, family_name: string}>;
49     pickupLibId: number;
50     timezone: string = this.format.wsOrgTimezone;
51     pickupLibraryUsesDifferentTz: boolean;
52
53     public disableOrgs: () => number[];
54     addBresv$: () => Observable<any>;
55     @ViewChild('fail', { static: true }) private fail: AlertDialogComponent;
56     @ViewChild('patronSearch') patronSearch: PatronSearchDialogComponent;
57
58     handlePickupLibChange: ($event: IdlObject) => void;
59
60     constructor(
61         private auth: AuthService,
62         private format: FormatService,
63         private net: NetService,
64         private org: OrgService,
65         private pcrud: PcrudService,
66         private router: Router,
67         private modal: NgbModal,
68         private pbv: PatronBarcodeValidator,
69         private toast: ToastService
70     ) {
71         super(modal);
72         this.onComplete = new EventEmitter<boolean>();
73     }
74
75     ngOnInit() {
76
77         this.create = new FormGroup({
78             'patronBarcode': new FormControl('',
79                 [Validators.required],
80                 [this.pbv.validate]
81             ),
82             'emailNotify': new FormControl(true),
83             'startTime': new FormControl(null, notBeforeMomentValidator(moment().add('15', 'minutes'))),
84             'endTime': new FormControl(),
85             'resourceList': new FormControl(),
86             'note': new FormControl(),
87         }, [startTimeIsBeforeEndTimeValidator]
88         );
89         if (this.patronId) {
90             this.pcrud.search('au', {id: this.patronId}, {
91                 flesh: 1,
92                 flesh_fields: {'au': ['card']}
93             }).subscribe((usr) =>
94                 this.create.patchValue({patronBarcode: usr.card().barcode()})
95             );
96         }
97
98         this.addBresv$ = () => {
99             let selectedResourceId = this.targetResource ? [this.targetResource] : null;
100             if (!selectedResourceId &&
101                 this.resourceListSelection !== null &&
102                 'any' !== this.resourceListSelection.id) {
103                 selectedResourceId = [this.resourceListSelection.id];
104             }
105             return this.net.request(
106                 'open-ils.booking',
107                 'open-ils.booking.reservations.create',
108                 this.auth.token(),
109                 this.patronBarcode.value.trim(),
110                 this.selectedTimes,
111                 this.pickupLibId,
112                 this.targetResourceType.id,
113                 selectedResourceId,
114                 this.attributes.filter(Boolean),
115                 this.emailNotify,
116                 this.bresvNote
117             ).pipe(tap(
118                 (success) => {
119                     if (success.ilsevent) {
120                         console.warn(success);
121                         this.fail.open();
122                     } else {
123                         this.toast.success('Reservation successfully created');
124                         console.debug(success);
125                         this.close();
126                    }
127                 }, (fail) => {
128                     console.warn(fail);
129                     this.fail.open();
130                 }, () => this.onComplete.emit(true)
131             ));
132         };
133
134         this.handlePickupLibChange = ($event) => {
135             this.pickupLibId = $event.id();
136             this.org.settings('lib.timezone', this.pickupLibId).then((tz) => {
137                 this.timezone = tz['lib.timezone'] || this.format.wsOrgTimezone;
138                 this.pickupLibraryUsesDifferentTz = (tz['lib.timezone'] && (this.format.wsOrgTimezone !== tz['lib.timezone']));
139             });
140         };
141
142         this.disableOrgs = () => this.org.filterList( { canHaveVolumes : false }, true);
143
144         this.patron$ = this.patronBarcode.statusChanges.pipe(
145             startWith({first_given_name: '', second_given_name: '', family_name: ''}),
146             switchMap(() => {
147                 if ('VALID' === this.patronBarcode.status) {
148                     return this.net.request(
149                         'open-ils.actor',
150                         'open-ils.actor.get_barcodes',
151                         this.auth.token(),
152                         this.auth.user().ws_ou(),
153                         'actor', this.patronBarcode.value.trim()).pipe(
154                             single(),
155                             switchMap((result) => {
156                                 return this.pcrud.retrieve('au', result[0]['id']).pipe(
157                                     switchMap((au) => {
158                                         return of({
159                                             first_given_name: au.first_given_name(),
160                                             second_given_name: au.second_given_name(),
161                                             family_name: au.family_name()});
162                                     })
163                                 );
164                             })
165                         );
166                 } else {
167                     return of({
168                         first_given_name: '',
169                         second_given_name: '',
170                         family_name: ''
171                     });
172                 }
173             })
174         );
175     }
176
177     setDefaultTimes(times: moment.Moment[], granularity: number) {
178         this.create.patchValue({startTime: moment.min(times),
179         endTime: moment.max(times).clone().add(granularity, 'minutes')
180         });
181     }
182
183     openPatronReservations = (): void => {
184         this.net.request(
185             'open-ils.actor',
186             'open-ils.actor.get_barcodes',
187             this.auth.token(),
188             this.auth.user().ws_ou(),
189             'actor', this.patronBarcode.value
190         ).subscribe((patron) => this.router.navigate(['/staff', 'booking', 'manage_reservations', 'by_patron', patron[0]['id']]));
191     }
192
193     addBresvAndOpenPatronReservations = (): void => {
194         this.addBresv$()
195         .subscribe(() => this.openPatronReservations());
196     }
197
198     searchPatrons() {
199         this.patronSearch.open({size: 'xl'}).toPromise().then(
200             patrons => {
201                 if (!patrons || patrons.length === 0) { return; }
202                 const user = patrons[0];
203                 this.create.patchValue({patronBarcode: user.card().barcode()});
204             }
205         );
206     }
207
208     get emailNotify() {
209         return this.create.get('emailNotify').value;
210     }
211
212     get bresvNote() {
213         return this.create.get('note').value;
214     }
215
216     get patronBarcode() {
217         return this.create.get('patronBarcode');
218     }
219
220     get resourceListSelection() {
221       return this.create.get('resourceList').value;
222     }
223
224     get selectedTimes() {
225         return [this.create.get('startTime').value.toISOString(),
226             this.create.get('endTime').value.toISOString()];
227     }
228 }
229