]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/booking/schedule-grid.service.ts
46929deb06974b805420c664b10ae21430af8464
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / booking / schedule-grid.service.ts
1 import {Injectable} from '@angular/core';
2 import {Observable, of} from 'rxjs';
3 import {switchMap} from 'rxjs/operators';
4 import {NgbTimeStruct} from '@ng-bootstrap/ng-bootstrap';
5 import {AuthService} from '@eg/core/auth.service';
6 import {IdlObject} from '@eg/core/idl.service';
7 import {PcrudService} from '@eg/core/pcrud.service';
8 import {GridRowFlairEntry} from '@eg/share/grid/grid';
9 import {DateRange} from '@eg/share/daterange-select/daterange-select.component';
10
11 import * as moment from 'moment-timezone';
12
13 export interface ReservationPatron {
14   patronId: number;
15   patronLabel: string;
16   reservationId: number;
17 }
18
19 interface ScheduleRowPatrons {
20     [key: string]: ReservationPatron[];
21 }
22
23 export interface ScheduleRow {
24     time: moment.Moment;
25     patrons: ScheduleRowPatrons;
26 }
27
28 // Various methods that fetch data for and process the schedule of reservations
29
30 @Injectable({providedIn: 'root'})
31 export class ScheduleGridService {
32
33     constructor(
34         private auth: AuthService,
35         private pcrud: PcrudService,
36     ) {
37     }
38     hoursOfOperation = (date: Date): Observable<{startOfDay: NgbTimeStruct, endOfDay: NgbTimeStruct}> => {
39         const defaultStartHour = 9;
40         const defaultEndHour = 17;
41         return this.pcrud.retrieve('aouhoo', this.auth.user().ws_ou())
42             .pipe(switchMap((hours) => {
43                 const startArray = hours[this.evergreenStyleDow(date) + '_open']().split(':');
44                 const endArray = hours[this.evergreenStyleDow(date) + '_close']().split(':');
45                 return of({
46                     startOfDay: {
47                         hour: ('00' === startArray[0]) ? defaultStartHour : +startArray[0],
48                         minute: +startArray[1],
49                         second: 0},
50                     endOfDay: {
51                         hour: ('00' === endArray[0]) ? defaultEndHour : +endArray[0],
52                         minute: +endArray[1],
53                         second: 0}
54                 });
55             }));
56     }
57
58     resourceAvailabilityIcon = (row: ScheduleRow, numResources: number): GridRowFlairEntry => {
59         let icon = {icon: 'event_busy', title: 'All resources are reserved at this time'};
60         let busyColumns = 0;
61         for (const key in row.patrons) {
62             if (row.patrons[key] instanceof Array && row.patrons[key].length) {
63                 busyColumns += 1;
64             }
65         }
66         if (busyColumns < numResources) {
67             icon = {icon: 'event_available', title: 'Resources are available at this time'};
68         }
69         return icon;
70     }
71
72     fetchRelevantResources = (resourceTypeId: number, owningLibraries: number[], selectedAttributes: number[]): Observable<IdlObject> => {
73         const where = {
74             type: resourceTypeId,
75             owner: owningLibraries,
76         };
77
78         if (selectedAttributes.length) {
79             where['id'] = {'in':
80                 {'from': 'bram', 'select': {'bram': ['resource']},
81                 'where': {'value':  selectedAttributes}}};
82         }
83         return this.pcrud.search('brsrc', where, {
84             order_by: 'barcode ASC',
85             flesh: 1,
86             flesh_fields: {'brsrc': ['attr_maps']},
87         });
88     }
89
90     momentizeDateRange = (range: DateRange, timezone: string): {startTime: moment.Moment, endTime: moment.Moment} => {
91         return {
92             startTime: moment.tz([
93                 range.fromDate.year,
94                 range.fromDate.month - 1,
95                 range.fromDate.day],
96                 timezone),
97             endTime: moment.tz([
98                 range.toDate.year,
99                 range.toDate.month - 1,
100                 range.toDate.day + 1],
101                 timezone)
102         };
103     }
104     momentizeDay = (date: Date, start: NgbTimeStruct, end: NgbTimeStruct, timezone: string): {startTime: moment.Moment, endTime: moment.Moment} => {
105         return {
106             startTime: moment.tz([
107                 date.getFullYear(),
108                 date.getMonth(),
109                 date.getDate(),
110                 start.hour,
111                 start.minute],
112                 timezone),
113             endTime: moment.tz([
114                 date.getFullYear(),
115                 date.getMonth(),
116                 date.getDate(),
117                 end.hour,
118                 end.minute],
119                 timezone)
120         };
121     }
122
123     createBasicSchedule = (range: {startTime: moment.Moment, endTime: moment.Moment}, granularity: number): ScheduleRow[] => {
124         const currentTime = range.startTime.clone();
125         const schedule = [];
126         while (currentTime < range.endTime) {
127             schedule.push({'time': currentTime.clone()});
128             currentTime.add(granularity, 'minutes');
129         }
130         return schedule;
131     }
132
133     fetchReservations = (range: {startTime: moment.Moment, endTime: moment.Moment}, resourceIds: number[]): Observable<IdlObject> => {
134         return this.pcrud.search('bresv', {
135             '-or': {'target_resource': resourceIds, 'current_resource': resourceIds},
136             'end_time': {'>': range.startTime.toISOString()},
137             'start_time': {'<': range.endTime.toISOString()},
138             'return_time': null,
139             'cancel_time': null },
140             {'flesh': 1, 'flesh_fields': {'bresv': ['current_resource', 'usr']}});
141     }
142
143     addReservationToSchedule = (reservation: IdlObject, schedule: ScheduleRow[], granularity: number, timezone: string): ScheduleRow[] => {
144         for (let index = 0; index < schedule.length; index++) {
145             const start = schedule[index].time;
146             const end = (index + 1 < schedule.length) ?
147                 schedule[index + 1].time :
148                 schedule[index].time.clone().add(granularity, 'minutes');
149             if ((moment.tz(reservation.start_time(), timezone).isBefore(end)) &&
150                 (moment.tz(reservation.end_time(), timezone).isAfter(start))) {
151                 if (!schedule[index]['patrons']) schedule[index].patrons = {};
152                 if (!schedule[index].patrons[reservation.current_resource().barcode()]) {
153                     schedule[index].patrons[reservation.current_resource().barcode()] = [];
154                 }
155                 if (schedule[index].patrons[reservation.current_resource().barcode()]
156                     .findIndex(patron => patron.patronId === reservation.usr().id()) === -1) {
157                     schedule[index].patrons[reservation.current_resource().barcode()].push(
158                         {'patronLabel': reservation.usr().usrname(),
159                         'patronId': reservation.usr().id(),
160                         'reservationId': reservation.id()});
161                 }
162             }
163
164         }
165         return schedule;
166
167     }
168
169     // Evergreen uses its own day of week style, where dow_0 = Monday and dow_6 = Sunday
170     private evergreenStyleDow = (original: Date): string => {
171         const daysInAWeek = 7;
172         const offset = 6;
173         return 'dow_' + (original.getDay() + offset) % daysInAWeek;
174     }
175
176
177 }
178