@vulog/aima-booking 1.1.87 → 1.1.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -104,6 +104,47 @@ type SATBookingRequest = BaseBookingRequest & {
104
104
  trip: any | null;
105
105
  warning: string | null;
106
106
  };
107
+ type GeoInfo = {
108
+ name: string;
109
+ coordinates: {
110
+ latitude: number;
111
+ longitude: number;
112
+ };
113
+ geoProperties: {
114
+ [key: string]: any;
115
+ };
116
+ };
117
+ type Service = {
118
+ id: string;
119
+ models: {
120
+ id: number;
121
+ vehicles: string[];
122
+ }[];
123
+ };
124
+ type ServiceInfo = {
125
+ services: Service[];
126
+ };
127
+ type Days = 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY';
128
+ type DayOpeningHours = {
129
+ id: number;
130
+ closed: boolean;
131
+ openAt: string;
132
+ closeAt: string;
133
+ };
134
+ type Timetable = Record<Days, DayOpeningHours[]>;
135
+ type OpeningHours = {
136
+ alwaysOpen: boolean;
137
+ timetable: Timetable;
138
+ };
139
+ type Station = Partial<GeoInfo> & Partial<ServiceInfo> & {
140
+ id: string;
141
+ zoneId: string;
142
+ poiId: string;
143
+ modificationDate: string;
144
+ fleetId: string;
145
+ openingHours?: OpeningHours;
146
+ [key: string]: any;
147
+ };
107
148
 
108
149
  declare const BookingRequestStatusSchema: z.ZodEnum<["ALERT", "UPCOMING", "ONGOING", "COMPLETED", "CANCELLED", "PENDING_APPROVAL"]>;
109
150
  type BookingRequestStatus = z.infer<typeof BookingRequestStatusSchema>;
@@ -146,49 +187,12 @@ declare const getBookingRequestById: (client: Client, id: string) => Promise<Boo
146
187
  declare const getBookingRequestByTrip: (client: Client, tripId: string) => Promise<BookingRequest>;
147
188
  declare const getSubscriptionBookingRequestById: (client: Client, id: string) => Promise<BookingRequest>;
148
189
 
149
- declare const IncludeSchema: z.ZodEnum<["INFO", "OPEN_HOUR", "SERVICES"]>;
150
- type Include = z.infer<typeof IncludeSchema>;
151
- type Days = 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY';
152
- type DayOpeningHours = {
153
- id: number;
154
- closed: boolean;
155
- openAt: string;
156
- closeAt: string;
157
- };
158
- type Timetable = Record<Days, DayOpeningHours[]>;
159
- type OpeningHours = {
160
- alwaysOpen: boolean;
161
- timetable: Timetable;
162
- };
163
- type GeoInfo = {
164
- name: string;
165
- coordinates: {
166
- latitude: number;
167
- longitude: number;
168
- };
169
- geoProperties: {
170
- [key: string]: any;
171
- };
172
- };
173
- type Service = {
174
- id: string;
175
- models: {
176
- id: number;
177
- vehicles: string[];
178
- }[];
179
- };
180
- type ServiceInfo = {
181
- services: Service[];
182
- };
183
- type Station = Partial<GeoInfo> & Partial<ServiceInfo> & {
184
- id: string;
185
- zoneId: string;
186
- poiId: string;
187
- modificationDate: string;
188
- fleetId: string;
189
- openingHours?: OpeningHours;
190
- [key: string]: any;
191
- };
190
+ declare const IncludeSchema$1: z.ZodEnum<["INFO", "OPEN_HOUR", "SERVICES"]>;
191
+ type Include = z.infer<typeof IncludeSchema$1>;
192
192
  declare const getStations: (client: Client, includes?: Include[]) => Promise<Station[]>;
193
193
 
194
- export { type BookingRequest, type BookingRequestFilters, type BookingRequestStatus, type DayOpeningHours, type Days, type Include, type OpeningHours, type SATBookingRequestStatus, type ServiceType, type Station, type Timetable, getBookingRequestById, getBookingRequestByTrip, getBookingRequests, getSATBookingRequests, getScheduleBookingRequests, getStations, getSubscriptionBookingRequestById, getSubscriptionBookingRequests };
194
+ declare const IncludeSchema: z.ZodEnum<["INFO", "SERVICES"]>;
195
+ type IncludeStation = z.infer<typeof IncludeSchema>;
196
+ declare const getStationById: (client: Client, id: string, includes?: IncludeStation[]) => Promise<Station | null>;
197
+
198
+ export { type BaseBookingRequest, type BookingRequest, type BookingRequestFilters, type BookingRequestStatus, type CustomPrice, type DayOpeningHours, type Days, type GeoInfo, type Include, type IncludeStation, type OpeningHours, type PaymentReceipts, type SATBookingRequest, type SATBookingRequestStatus, type Service, type ServiceInfo, type ServiceType, type Station, type Timetable, getBookingRequestById, getBookingRequestByTrip, getBookingRequests, getSATBookingRequests, getScheduleBookingRequests, getStationById, getStations, getSubscriptionBookingRequestById, getSubscriptionBookingRequests };
package/dist/index.d.ts CHANGED
@@ -104,6 +104,47 @@ type SATBookingRequest = BaseBookingRequest & {
104
104
  trip: any | null;
105
105
  warning: string | null;
106
106
  };
107
+ type GeoInfo = {
108
+ name: string;
109
+ coordinates: {
110
+ latitude: number;
111
+ longitude: number;
112
+ };
113
+ geoProperties: {
114
+ [key: string]: any;
115
+ };
116
+ };
117
+ type Service = {
118
+ id: string;
119
+ models: {
120
+ id: number;
121
+ vehicles: string[];
122
+ }[];
123
+ };
124
+ type ServiceInfo = {
125
+ services: Service[];
126
+ };
127
+ type Days = 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY';
128
+ type DayOpeningHours = {
129
+ id: number;
130
+ closed: boolean;
131
+ openAt: string;
132
+ closeAt: string;
133
+ };
134
+ type Timetable = Record<Days, DayOpeningHours[]>;
135
+ type OpeningHours = {
136
+ alwaysOpen: boolean;
137
+ timetable: Timetable;
138
+ };
139
+ type Station = Partial<GeoInfo> & Partial<ServiceInfo> & {
140
+ id: string;
141
+ zoneId: string;
142
+ poiId: string;
143
+ modificationDate: string;
144
+ fleetId: string;
145
+ openingHours?: OpeningHours;
146
+ [key: string]: any;
147
+ };
107
148
 
108
149
  declare const BookingRequestStatusSchema: z.ZodEnum<["ALERT", "UPCOMING", "ONGOING", "COMPLETED", "CANCELLED", "PENDING_APPROVAL"]>;
109
150
  type BookingRequestStatus = z.infer<typeof BookingRequestStatusSchema>;
@@ -146,49 +187,12 @@ declare const getBookingRequestById: (client: Client, id: string) => Promise<Boo
146
187
  declare const getBookingRequestByTrip: (client: Client, tripId: string) => Promise<BookingRequest>;
147
188
  declare const getSubscriptionBookingRequestById: (client: Client, id: string) => Promise<BookingRequest>;
148
189
 
149
- declare const IncludeSchema: z.ZodEnum<["INFO", "OPEN_HOUR", "SERVICES"]>;
150
- type Include = z.infer<typeof IncludeSchema>;
151
- type Days = 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY';
152
- type DayOpeningHours = {
153
- id: number;
154
- closed: boolean;
155
- openAt: string;
156
- closeAt: string;
157
- };
158
- type Timetable = Record<Days, DayOpeningHours[]>;
159
- type OpeningHours = {
160
- alwaysOpen: boolean;
161
- timetable: Timetable;
162
- };
163
- type GeoInfo = {
164
- name: string;
165
- coordinates: {
166
- latitude: number;
167
- longitude: number;
168
- };
169
- geoProperties: {
170
- [key: string]: any;
171
- };
172
- };
173
- type Service = {
174
- id: string;
175
- models: {
176
- id: number;
177
- vehicles: string[];
178
- }[];
179
- };
180
- type ServiceInfo = {
181
- services: Service[];
182
- };
183
- type Station = Partial<GeoInfo> & Partial<ServiceInfo> & {
184
- id: string;
185
- zoneId: string;
186
- poiId: string;
187
- modificationDate: string;
188
- fleetId: string;
189
- openingHours?: OpeningHours;
190
- [key: string]: any;
191
- };
190
+ declare const IncludeSchema$1: z.ZodEnum<["INFO", "OPEN_HOUR", "SERVICES"]>;
191
+ type Include = z.infer<typeof IncludeSchema$1>;
192
192
  declare const getStations: (client: Client, includes?: Include[]) => Promise<Station[]>;
193
193
 
194
- export { type BookingRequest, type BookingRequestFilters, type BookingRequestStatus, type DayOpeningHours, type Days, type Include, type OpeningHours, type SATBookingRequestStatus, type ServiceType, type Station, type Timetable, getBookingRequestById, getBookingRequestByTrip, getBookingRequests, getSATBookingRequests, getScheduleBookingRequests, getStations, getSubscriptionBookingRequestById, getSubscriptionBookingRequests };
194
+ declare const IncludeSchema: z.ZodEnum<["INFO", "SERVICES"]>;
195
+ type IncludeStation = z.infer<typeof IncludeSchema>;
196
+ declare const getStationById: (client: Client, id: string, includes?: IncludeStation[]) => Promise<Station | null>;
197
+
198
+ export { type BaseBookingRequest, type BookingRequest, type BookingRequestFilters, type BookingRequestStatus, type CustomPrice, type DayOpeningHours, type Days, type GeoInfo, type Include, type IncludeStation, type OpeningHours, type PaymentReceipts, type SATBookingRequest, type SATBookingRequestStatus, type Service, type ServiceInfo, type ServiceType, type Station, type Timetable, getBookingRequestById, getBookingRequestByTrip, getBookingRequests, getSATBookingRequests, getScheduleBookingRequests, getStationById, getStations, getSubscriptionBookingRequestById, getSubscriptionBookingRequests };
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  getBookingRequests: () => getBookingRequests,
26
26
  getSATBookingRequests: () => getSATBookingRequests,
27
27
  getScheduleBookingRequests: () => getScheduleBookingRequests,
28
+ getStationById: () => getStationById,
28
29
  getStations: () => getStations,
29
30
  getSubscriptionBookingRequestById: () => getSubscriptionBookingRequestById,
30
31
  getSubscriptionBookingRequests: () => getSubscriptionBookingRequests
@@ -304,6 +305,110 @@ var getStations = async (client, includes = []) => {
304
305
  }
305
306
  return stations;
306
307
  };
308
+
309
+ // src/getStation.ts
310
+ var import_zod5 = require("zod");
311
+ var IncludeSchema2 = import_zod5.z.enum(["INFO", "SERVICES"]);
312
+ var IncludesSchema2 = import_zod5.z.array(IncludeSchema2);
313
+ var getStationById = async (client, id, includes = []) => {
314
+ const resultIncludes = IncludesSchema2.safeParse(includes);
315
+ if (!resultIncludes.success) {
316
+ throw new TypeError("Invalid includes", {
317
+ cause: resultIncludes.error.issues
318
+ });
319
+ }
320
+ const station = await client.get(`/boapi/proxy/user/scheduledBooking/fleets/${client.clientOptions.fleetId}/stations/${id}`).then(({ data, status }) => {
321
+ if (status === 200) {
322
+ return Object.keys(data).reduce((acc, key) => {
323
+ if (key === "stationTimetableDTO") {
324
+ if (data.stationTimetableDTO?.stationId) {
325
+ acc.openingHours = {
326
+ alwaysOpen: data.stationTimetableDTO.alwaysOpen,
327
+ timetable: Object.keys(data.stationTimetableDTO.timetable).reduce(
328
+ (timetable, val) => {
329
+ timetable[val] = data.stationTimetableDTO.timetable[val].map(
330
+ (day) => ({
331
+ id: day.id,
332
+ closed: day.closed,
333
+ openAt: day.openAt,
334
+ closeAt: day.closeAt
335
+ })
336
+ );
337
+ return timetable;
338
+ },
339
+ {}
340
+ )
341
+ };
342
+ }
343
+ return acc;
344
+ }
345
+ acc[key] = data[key];
346
+ return acc;
347
+ }, {});
348
+ }
349
+ if (status === 400) {
350
+ return null;
351
+ }
352
+ return null;
353
+ }).catch((error) => {
354
+ if (error.formattedError?.status === 400) {
355
+ return null;
356
+ }
357
+ throw error;
358
+ });
359
+ if (station && includes.includes("INFO")) {
360
+ const poi = await client.get(
361
+ `/boapi/proxy/geoloc/fleets/${client.clientOptions.fleetId}/poi/${station.poiId}`,
362
+ {
363
+ headers: { accept: "application/vnd.geo+json" }
364
+ }
365
+ ).then(
366
+ ({ data }) => data.features.reduce(
367
+ (max, current) => {
368
+ if (current.properties.Version > max.version) {
369
+ const {
370
+ geometry: {
371
+ coordinates: [longitude, latitude]
372
+ },
373
+ properties
374
+ } = current;
375
+ return {
376
+ version: current.properties.Version,
377
+ name: properties.name,
378
+ coordinates: { latitude, longitude },
379
+ geoProperties: properties
380
+ };
381
+ }
382
+ return max;
383
+ },
384
+ { version: -1 }
385
+ )
386
+ );
387
+ if (station.poiId && poi) {
388
+ poi.version = void 0;
389
+ Object.assign(station, poi);
390
+ station.name = station.geoProperties?.name;
391
+ }
392
+ }
393
+ if (station && includes.includes("SERVICES")) {
394
+ const services = await client.get(`/boapi/proxy/user/fleets/${client.clientOptions.fleetId}/stations/details?showTimetable=false`).then(
395
+ ({ data }) => data.stations.reduce((acc, service) => {
396
+ if (!acc[service.station.id]) {
397
+ acc[service.station.id] = { services: [] };
398
+ }
399
+ acc[service.station.id].services.push({
400
+ id: service.serviceId,
401
+ models: service.station.models
402
+ });
403
+ return acc;
404
+ }, {})
405
+ );
406
+ if (services[station.id]) {
407
+ Object.assign(station, services[station.id]);
408
+ }
409
+ }
410
+ return station;
411
+ };
307
412
  // Annotate the CommonJS export names for ESM import in node:
308
413
  0 && (module.exports = {
309
414
  getBookingRequestById,
@@ -311,6 +416,7 @@ var getStations = async (client, includes = []) => {
311
416
  getBookingRequests,
312
417
  getSATBookingRequests,
313
418
  getScheduleBookingRequests,
419
+ getStationById,
314
420
  getStations,
315
421
  getSubscriptionBookingRequestById,
316
422
  getSubscriptionBookingRequests
package/dist/index.mjs CHANGED
@@ -271,12 +271,117 @@ var getStations = async (client, includes = []) => {
271
271
  }
272
272
  return stations;
273
273
  };
274
+
275
+ // src/getStation.ts
276
+ import { z as z5 } from "zod";
277
+ var IncludeSchema2 = z5.enum(["INFO", "SERVICES"]);
278
+ var IncludesSchema2 = z5.array(IncludeSchema2);
279
+ var getStationById = async (client, id, includes = []) => {
280
+ const resultIncludes = IncludesSchema2.safeParse(includes);
281
+ if (!resultIncludes.success) {
282
+ throw new TypeError("Invalid includes", {
283
+ cause: resultIncludes.error.issues
284
+ });
285
+ }
286
+ const station = await client.get(`/boapi/proxy/user/scheduledBooking/fleets/${client.clientOptions.fleetId}/stations/${id}`).then(({ data, status }) => {
287
+ if (status === 200) {
288
+ return Object.keys(data).reduce((acc, key) => {
289
+ if (key === "stationTimetableDTO") {
290
+ if (data.stationTimetableDTO?.stationId) {
291
+ acc.openingHours = {
292
+ alwaysOpen: data.stationTimetableDTO.alwaysOpen,
293
+ timetable: Object.keys(data.stationTimetableDTO.timetable).reduce(
294
+ (timetable, val) => {
295
+ timetable[val] = data.stationTimetableDTO.timetable[val].map(
296
+ (day) => ({
297
+ id: day.id,
298
+ closed: day.closed,
299
+ openAt: day.openAt,
300
+ closeAt: day.closeAt
301
+ })
302
+ );
303
+ return timetable;
304
+ },
305
+ {}
306
+ )
307
+ };
308
+ }
309
+ return acc;
310
+ }
311
+ acc[key] = data[key];
312
+ return acc;
313
+ }, {});
314
+ }
315
+ if (status === 400) {
316
+ return null;
317
+ }
318
+ return null;
319
+ }).catch((error) => {
320
+ if (error.formattedError?.status === 400) {
321
+ return null;
322
+ }
323
+ throw error;
324
+ });
325
+ if (station && includes.includes("INFO")) {
326
+ const poi = await client.get(
327
+ `/boapi/proxy/geoloc/fleets/${client.clientOptions.fleetId}/poi/${station.poiId}`,
328
+ {
329
+ headers: { accept: "application/vnd.geo+json" }
330
+ }
331
+ ).then(
332
+ ({ data }) => data.features.reduce(
333
+ (max, current) => {
334
+ if (current.properties.Version > max.version) {
335
+ const {
336
+ geometry: {
337
+ coordinates: [longitude, latitude]
338
+ },
339
+ properties
340
+ } = current;
341
+ return {
342
+ version: current.properties.Version,
343
+ name: properties.name,
344
+ coordinates: { latitude, longitude },
345
+ geoProperties: properties
346
+ };
347
+ }
348
+ return max;
349
+ },
350
+ { version: -1 }
351
+ )
352
+ );
353
+ if (station.poiId && poi) {
354
+ poi.version = void 0;
355
+ Object.assign(station, poi);
356
+ station.name = station.geoProperties?.name;
357
+ }
358
+ }
359
+ if (station && includes.includes("SERVICES")) {
360
+ const services = await client.get(`/boapi/proxy/user/fleets/${client.clientOptions.fleetId}/stations/details?showTimetable=false`).then(
361
+ ({ data }) => data.stations.reduce((acc, service) => {
362
+ if (!acc[service.station.id]) {
363
+ acc[service.station.id] = { services: [] };
364
+ }
365
+ acc[service.station.id].services.push({
366
+ id: service.serviceId,
367
+ models: service.station.models
368
+ });
369
+ return acc;
370
+ }, {})
371
+ );
372
+ if (services[station.id]) {
373
+ Object.assign(station, services[station.id]);
374
+ }
375
+ }
376
+ return station;
377
+ };
274
378
  export {
275
379
  getBookingRequestById,
276
380
  getBookingRequestByTrip,
277
381
  getBookingRequests,
278
382
  getSATBookingRequests,
279
383
  getScheduleBookingRequests,
384
+ getStationById,
280
385
  getStations,
281
386
  getSubscriptionBookingRequestById,
282
387
  getSubscriptionBookingRequests
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vulog/aima-booking",
3
- "version": "1.1.87",
3
+ "version": "1.1.89",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -19,8 +19,8 @@
19
19
  "author": "Vulog",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@vulog/aima-client": "1.1.87",
23
- "@vulog/aima-core": "1.1.87"
22
+ "@vulog/aima-client": "1.1.89",
23
+ "@vulog/aima-core": "1.1.89"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "es-toolkit": "^1.39.9",
@@ -0,0 +1,411 @@
1
+ import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { Client } from '@vulog/aima-client';
3
+ import { getStationById } from './getStation';
4
+ import { features } from 'process';
5
+ import { DayOpeningHours, Service, Station } from './types';
6
+
7
+ const FLEET_ID = 'FLEET_ID';
8
+ const STATION_ID: string = 'd7d70752-8cf5-4352-a420-33170785976e';
9
+ const POI_ID = '9967b01d3a46469896888b458904669e'
10
+ const STATION = {
11
+ id: STATION_ID,
12
+ zoneId: 'f40b8255b4b24e7cb7e5f28553e79c7e',
13
+ taxZoneId: null,
14
+ poiId: POI_ID,
15
+ modificationDate: '2024-09-23T07:19:12Z',
16
+ fleetId: FLEET_ID,
17
+ openTime: '00:00:00',
18
+ closeTime: '23:59:00',
19
+ stationTimetableDTO: {
20
+ stationId: STATION_ID,
21
+ fleetId: FLEET_ID,
22
+ alwaysOpen: false,
23
+ timetable: {
24
+ MONDAY: [
25
+ {
26
+ id: 10863,
27
+ openAt: '00:00',
28
+ closeAt: '23:59',
29
+ closed: false,
30
+ weekDay: 'MONDAY',
31
+ }
32
+ ],
33
+ TUESDAY: [
34
+ {
35
+ id: 10866,
36
+ openAt: '00:00',
37
+ closeAt: '23:59',
38
+ closed: false,
39
+ weekDay: 'TUESDAY',
40
+ }
41
+ ],
42
+ WEDNESDAY: [
43
+ {
44
+ id: 10869,
45
+ openAt: '00:00',
46
+ closeAt: '23:59',
47
+ closed: false,
48
+ weekDay: 'WEDNESDAY',
49
+ }
50
+ ],
51
+ THURSDAY: [
52
+ {
53
+ id: 10872,
54
+ openAt: '00:00',
55
+ closeAt: '23:59',
56
+ closed: false,
57
+ weekDay: 'THURSDAY',
58
+ }
59
+ ],
60
+ FRIDAY: [
61
+ {
62
+ id: 10875,
63
+ openAt: '00:00',
64
+ closeAt: '23:59',
65
+ closed: false,
66
+ weekDay: 'FRIDAY',
67
+ }
68
+ ],
69
+ SATURDAY: [
70
+ {
71
+ id: 10878,
72
+ openAt: '00:00',
73
+ closeAt: '23:59',
74
+ closed: false,
75
+ weekDay: 'SATURDAY',
76
+ }
77
+ ],
78
+ SUNDAY: [
79
+ {
80
+ id: 10881,
81
+ openAt: '00:00',
82
+ closeAt: '23:59',
83
+ closed: false,
84
+ weekDay: 'SUNDAY',
85
+ }
86
+ ]
87
+ },
88
+ },
89
+ description: null,
90
+ maxAvailableSpots: 0,
91
+ };
92
+ const POI = {
93
+ type: 'Feature',
94
+ properties: {
95
+ name: 'Vulog HQ',
96
+ Version: 12,
97
+ type: 'scheduled_booking_station',
98
+ description: '',
99
+ address: 'av Simone Veil',
100
+ postal_code: '06000',
101
+ city: 'Nice',
102
+ capacity: 0
103
+ },
104
+ geometry: {
105
+ type: 'Point',
106
+ coordinates: [
107
+ -7.852491,
108
+ 42.282294
109
+ ]
110
+ }
111
+ };
112
+ const STATION_MODELS = {
113
+ station: {
114
+ id: STATION.id,
115
+ models: [
116
+ { id: 1950, vehicles: [ '52d8c237-03a8-4b2d-bc66-f52a5d5c5119' ] },
117
+ { id: 1981, vehicles: [ 'c20bc631-9943-444d-b025-b5cf78b68084' ] },
118
+ ],
119
+ openTime: STATION.openTime,
120
+ closeTime: STATION.closeTime,
121
+ maxAvailableSpots: 0
122
+ },
123
+ serviceId: '55160733-334d-40fb-b80f-bad7080f90de',
124
+ stationTimetableDTO: {
125
+ stationId: null,
126
+ fleetId: null,
127
+ alwaysOpen: false,
128
+ timetable: null
129
+ }
130
+ };
131
+
132
+ const expectStationTimeTableDay = (value: DayOpeningHours[] | undefined, expected: DayOpeningHours[]) => {
133
+ expect(value).not.toBeNull();
134
+ expect(value).not.toBeUndefined();
135
+ expect(value?.length).toEqual(expected.length);
136
+ if (value) {
137
+ expect(value[0]?.id).toEqual(expected[0].id);
138
+ expect(value[0].closed).toEqual(expected[0].closed);
139
+ expect(value[0].openAt).toEqual(expected[0].openAt);
140
+ expect(value[0].closeAt).toEqual(expected[0].closeAt);
141
+ }
142
+ }
143
+
144
+ const expectStation = (value: Station | null, expected: Station) => {
145
+
146
+ expect(value).not.toBeNull();
147
+ expect(value).not.toBeUndefined();
148
+ expect(value?.id).toEqual(expected.id);
149
+ expect(value?.zoneId).toEqual(expected.zoneId);
150
+ expect(value?.poiId).toEqual(expected.poiId);
151
+ expect(value?.modificationDate).toEqual(expected.modificationDate);
152
+ expect(value?.fleetId).toEqual(expected.fleetId);
153
+ expect(value?.openingHours).not.toBeNull();
154
+ expect(value?.openingHours?.alwaysOpen).toEqual(expected.stationTimetableDTO.alwaysOpen);
155
+ expect(value?.openingHours?.timetable).not.toBeNull();
156
+ expect(value?.openingHours?.timetable?.MONDAY).not.toBeNull();
157
+ expect(value?.openingHours?.timetable?.MONDAY.length).toEqual(expected.stationTimetableDTO.timetable.MONDAY.length);
158
+ expect(value?.openingHours?.timetable?.MONDAY[0].id).toEqual(expected.stationTimetableDTO.timetable.MONDAY[0].id);
159
+ expect(value?.openingHours?.timetable?.MONDAY[0].closed).toEqual(expected.stationTimetableDTO.timetable.MONDAY[0].closed);
160
+ expect(value?.openingHours?.timetable?.MONDAY[0].openAt).toEqual(expected.stationTimetableDTO.timetable.MONDAY[0].openAt);
161
+ expect(value?.openingHours?.timetable?.MONDAY[0].closeAt).toEqual(expected.stationTimetableDTO.timetable.MONDAY[0].closeAt);
162
+ expectStationTimeTableDay(value?.openingHours?.timetable?.TUESDAY, expected.stationTimetableDTO.timetable.TUESDAY);
163
+ expectStationTimeTableDay(value?.openingHours?.timetable?.WEDNESDAY, expected.stationTimetableDTO.timetable.WEDNESDAY);
164
+ expectStationTimeTableDay(value?.openingHours?.timetable?.THURSDAY, expected.stationTimetableDTO.timetable.THURSDAY);
165
+ expectStationTimeTableDay(value?.openingHours?.timetable?.FRIDAY, expected.stationTimetableDTO.timetable.FRIDAY);
166
+ expectStationTimeTableDay(value?.openingHours?.timetable?.SATURDAY, expected.stationTimetableDTO.timetable.SATURDAY);
167
+ expectStationTimeTableDay(value?.openingHours?.timetable?.SUNDAY, expected.stationTimetableDTO.timetable.SUNDAY);
168
+ }
169
+ const expectStationInfo = (value: Station | null, expectedPoi: any) => {
170
+ expect(value?.name).toEqual(expectedPoi.properties.name);
171
+ expect(value?.coordinates?.latitude).toEqual(expectedPoi.geometry.coordinates[1]);
172
+ expect(value?.coordinates?.longitude).toEqual(expectedPoi.geometry.coordinates[0]);
173
+ expect(value?.geoProperties?.Version).toEqual(expectedPoi.properties.Version);
174
+ expect(value?.geoProperties?.type).toEqual(expectedPoi.properties.type);
175
+ expect(value?.geoProperties?.description).toEqual(expectedPoi.properties.description);
176
+ expect(value?.geoProperties?.address).toEqual(expectedPoi.properties.address);
177
+ expect(value?.geoProperties?.postal_code).toEqual(expectedPoi.properties.postal_code);
178
+ expect(value?.geoProperties?.city).toEqual(expectedPoi.properties.city);
179
+ }
180
+ const expectStationServices = (value: Service[] | undefined, expectedStationModels: any) => {
181
+ expect(value).not.toBeUndefined();
182
+ expect(value?.length).toEqual(1);// 1 service for station ?
183
+ if (value) {
184
+ expect(value[0].id).toEqual(expectedStationModels.serviceId);
185
+ expect(value[0].models.length).toEqual(expectedStationModels.station.models.length);
186
+ expect(value[0].models[0].id).toEqual(expectedStationModels.station.models[0].id);
187
+ expect(value[0].models[0].vehicles.length).toEqual(expectedStationModels.station.models[0].vehicles.length);
188
+ expect(value[0].models[0].vehicles[0]).toEqual(expectedStationModels.station.models[0].vehicles[0]);
189
+ }
190
+ }
191
+
192
+ describe('getStationById', () => {
193
+ const getMock = vi.fn();
194
+ const client = {
195
+ get: getMock,
196
+ clientOptions: {
197
+ fleetId: 'FLEET_ID',
198
+ },
199
+ } as unknown as Client;
200
+
201
+ beforeEach(() => {
202
+ getMock.mockReset();
203
+ vi.useFakeTimers({ now: new Date('2025-01-12T13:35:50.123Z') });
204
+
205
+ getMock.mockImplementation((url: string): Promise<any> => {
206
+ if (url === `/boapi/proxy/user/scheduledBooking/fleets/${FLEET_ID}/stations/${STATION_ID}`) {
207
+ return Promise.resolve({
208
+ data: STATION,
209
+ status: 200,
210
+ statusText: 'OK',
211
+ headers: {},
212
+ config: {}
213
+ });
214
+ }
215
+ if (url.startsWith(`/boapi/proxy/user/scheduledBooking/fleets/${FLEET_ID}/stations/`)) {
216
+ return Promise.resolve({
217
+ data: {
218
+ status: 'BAD_REQUEST',
219
+ errorCode: 'BAD_REQUEST',
220
+ message: 'The provided input is not valid',
221
+ detail: null,
222
+ transactionId: '8c97dc753afc43098933a73bee34c327'
223
+ },
224
+ status: 400,
225
+ statusText: 'BAD REQUEST',
226
+ headers: {},
227
+ config: {}
228
+ });
229
+ }
230
+ if (url === `/boapi/proxy/geoloc/fleets/${FLEET_ID}/poi/${POI_ID}` ) {
231
+ return Promise.resolve({
232
+ data: {
233
+ type: 'FeatureCollection',
234
+ features: [
235
+ {
236
+ type: 'Feature',
237
+ properties: {
238
+ name: 'Vulog Silo',
239
+ Version: 1,
240
+ type: 'scheduled_booking_station',
241
+ description: '',
242
+ address: '42 av Simone Veil',
243
+ postal_code: '06200',
244
+ city: 'NICE',
245
+ capacity: 0
246
+ },
247
+ geometry: {
248
+ type: 'Point',
249
+ coordinates: [
250
+ -7.85794,
251
+ 42.28325
252
+ ]
253
+ }
254
+ },
255
+ // more than 2 ... but it's OK for this test
256
+ POI,
257
+ ]
258
+ },
259
+ status: 200,
260
+ statusText: 'OK',
261
+ headers: {},
262
+ config: {}
263
+ });
264
+ }
265
+ if (url.startsWith(`/boapi/proxy/geoloc/fleets/${FLEET_ID}/poi/`)) {
266
+ return Promise.resolve({
267
+ data: {
268
+ type: 'FeatureCollection',
269
+ features: []
270
+ },
271
+ status: 200,
272
+ statusText: 'OK',
273
+ headers: {},
274
+ config: {}
275
+ });
276
+ }
277
+ if (url.startsWith(`/boapi/proxy/user/fleets/${FLEET_ID}/stations/details`)) {
278
+ return Promise.resolve({
279
+ data: {
280
+ stations:[
281
+ {
282
+ station: {
283
+ id: '404b6ea4-2004-421c-9304-84796b018975',
284
+ models: [],
285
+ openTime: '00:00:00',
286
+ closeTime: '23:59:00',
287
+ maxAvailableSpots: 0
288
+ },
289
+ serviceId: '66a91a3a-12dc-40bc-8750-440f0ea14fee',
290
+ stationTimetableDTO: {
291
+ stationId: null,
292
+ fleetId: null,
293
+ alwaysOpen: false,
294
+ timetable: null
295
+ }
296
+ },
297
+ STATION_MODELS,
298
+ {
299
+ station: {
300
+ id: '95095a04-afb7-49e7-b039-5da9750c8911',
301
+ models: [],
302
+ openTime: '07:00:00',
303
+ closeTime: '19:00:00',
304
+ maxAvailableSpots: 0
305
+ },
306
+ serviceId: '5f53f2ab-5af0-4ef5-8138-6a5b7698397d',
307
+ stationTimetableDTO: {
308
+ stationId: null,
309
+ fleetId: null,
310
+ alwaysOpen: false,
311
+ timetable: null
312
+ }
313
+ },
314
+ ],
315
+ },
316
+ status: 200,
317
+ statusText: 'OK',
318
+ headers: {},
319
+ config: {}
320
+ });
321
+
322
+ }
323
+
324
+ // Pour les autres URLs, rejetez la promesse
325
+ return Promise.reject(new Error('404 Not Found'));
326
+ });
327
+ });
328
+
329
+ afterEach(() => {
330
+ vi.useRealTimers();
331
+ });
332
+
333
+ test('Fake includes', async () => {
334
+ const rejects = expect(() => getStationById(client, STATION_ID, ['FAKE'] as any)).rejects;
335
+ await rejects.toThrowError('Invalid includes');
336
+ await rejects.toSatisfy((error: any) => {
337
+ expect(error).toHaveProperty('cause');
338
+ expect(error.cause).toHaveLength(1);
339
+ expect(error.cause[0]).toHaveProperty('code');
340
+ expect(error.cause[0].code).toBe('invalid_enum_value');
341
+ return true;
342
+ });
343
+ });
344
+
345
+ test('station not found', async () => {
346
+ const station = await getStationById(client, 'FAKE_ID');
347
+ expect(getMock).toBeCalled();
348
+ expect(getMock).toBeCalledWith(`/boapi/proxy/user/scheduledBooking/fleets/${FLEET_ID}/stations/FAKE_ID`);
349
+ expect(station).toBeNull();
350
+ });
351
+
352
+ test('all OK', async () => {
353
+ const station: Station | null = await getStationById(client, STATION_ID, []);
354
+ expectStation(station, STATION);
355
+ expect(station?.name).toBeUndefined();
356
+ expect(station?.coordinates?.latitude).toBeUndefined();
357
+ expect(station?.coordinates?.longitude).toBeUndefined();
358
+ expect(station?.geoProperties?.Version).toBeUndefined();
359
+ expect(station?.geoProperties?.type).toBeUndefined();
360
+ expect(station?.geoProperties?.description).toBeUndefined();
361
+ expect(station?.geoProperties?.address).toBeUndefined();
362
+ expect(station?.geoProperties?.postal_code).toBeUndefined();
363
+ expect(station?.geoProperties?.city).toBeUndefined();
364
+ expect(station?.services).toBeUndefined();
365
+ });
366
+
367
+ test('all OK with INFO', async () => {
368
+ const station: Station | null = await getStationById(client, STATION_ID, ['INFO']);
369
+ expect(getMock).toBeCalledTimes(2);
370
+ expect(getMock).toBeCalledWith(`/boapi/proxy/user/scheduledBooking/fleets/${FLEET_ID}/stations/${STATION_ID}`);
371
+ expect(getMock).toBeCalledWith(
372
+ `/boapi/proxy/geoloc/fleets/${FLEET_ID}/poi/${POI_ID}`,
373
+ { headers: { accept: 'application/vnd.geo+json' } }
374
+ );
375
+ expectStation(station, STATION);
376
+ expectStationInfo(station, POI);
377
+ expect(station?.services).toBeUndefined();
378
+ });
379
+
380
+ test('all OK with SERVICES', async () => {
381
+ const station: Station | null = await getStationById(client, STATION_ID, ['SERVICES']);
382
+ expect(getMock).toBeCalledTimes(2);
383
+ expect(getMock).toBeCalledWith(`/boapi/proxy/user/scheduledBooking/fleets/${FLEET_ID}/stations/${STATION_ID}`);
384
+ expect(getMock).toBeCalledWith(`/boapi/proxy/user/fleets/${FLEET_ID}/stations/details?showTimetable=false`);
385
+ expectStation(station, STATION);
386
+ expect(station?.name).toBeUndefined();
387
+ expect(station?.coordinates?.latitude).toBeUndefined();
388
+ expect(station?.coordinates?.longitude).toBeUndefined();
389
+ expect(station?.geoProperties?.Version).toBeUndefined();
390
+ expect(station?.geoProperties?.type).toBeUndefined();
391
+ expect(station?.geoProperties?.description).toBeUndefined();
392
+ expect(station?.geoProperties?.address).toBeUndefined();
393
+ expect(station?.geoProperties?.postal_code).toBeUndefined();
394
+ expect(station?.geoProperties?.city).toBeUndefined();
395
+ expectStationServices(station?.services, STATION_MODELS);
396
+ });
397
+
398
+ test('all OK with INFO SERVICE', async () => {
399
+ const station: Station | null = await getStationById(client, STATION_ID, ['INFO', 'SERVICES']);
400
+ expect(getMock).toBeCalledTimes(3);
401
+ expect(getMock).toBeCalledWith(`/boapi/proxy/user/scheduledBooking/fleets/${FLEET_ID}/stations/${STATION_ID}`);
402
+ expect(getMock).toBeCalledWith(
403
+ `/boapi/proxy/geoloc/fleets/${FLEET_ID}/poi/${POI_ID}`,
404
+ { headers: { accept: 'application/vnd.geo+json' } }
405
+ );
406
+ expect(getMock).toBeCalledWith(`/boapi/proxy/user/fleets/${FLEET_ID}/stations/details?showTimetable=false`);
407
+ expectStation(station, STATION);
408
+ expectStationInfo(station, POI);
409
+ expectStationServices(station?.services, STATION_MODELS);
410
+ });
411
+ });
@@ -0,0 +1,128 @@
1
+ import { Client } from '@vulog/aima-client';
2
+ import { z } from 'zod';
3
+
4
+ import { Days, ServiceInfo, Station, Timetable } from './types';
5
+
6
+ const IncludeSchema = z.enum(['INFO', 'SERVICES']);
7
+ const IncludesSchema = z.array(IncludeSchema);
8
+ export type IncludeStation = z.infer<typeof IncludeSchema>;
9
+
10
+ export const getStationById = async (
11
+ client: Client,
12
+ id: string,
13
+ includes: IncludeStation[] = []
14
+ ): Promise<Station | null> => {
15
+ const resultIncludes = IncludesSchema.safeParse(includes);
16
+ if (!resultIncludes.success) {
17
+ throw new TypeError('Invalid includes', {
18
+ cause: resultIncludes.error.issues,
19
+ });
20
+ }
21
+
22
+ const station: Station | null = await client
23
+ .get<any>(`/boapi/proxy/user/scheduledBooking/fleets/${client.clientOptions.fleetId}/stations/${id}`)
24
+ .then(({ data, status }) => {
25
+ if (status === 200) {
26
+ return Object.keys(data).reduce<{ [key: string]: any }>((acc, key) => {
27
+ if (key === 'stationTimetableDTO') {
28
+ if (data.stationTimetableDTO?.stationId) {
29
+ acc.openingHours = {
30
+ alwaysOpen: data.stationTimetableDTO.alwaysOpen,
31
+ timetable: Object.keys(data.stationTimetableDTO.timetable).reduce<Timetable>(
32
+ (timetable, val) => {
33
+ // eslint-disable-next-line no-param-reassign
34
+ timetable[val as Days] = data.stationTimetableDTO.timetable[val].map(
35
+ (day: any) => ({
36
+ id: day.id,
37
+ closed: day.closed,
38
+ openAt: day.openAt,
39
+ closeAt: day.closeAt,
40
+ })
41
+ );
42
+ return timetable;
43
+ },
44
+ {} as Timetable
45
+ ),
46
+ };
47
+ }
48
+ return acc;
49
+ }
50
+ acc[key] = data[key];
51
+ return acc;
52
+ }, {}) as Station;
53
+ }
54
+ if (status === 400) {
55
+ return null;
56
+ }
57
+ return null;
58
+ })
59
+ .catch((error) => {
60
+ if (error.formattedError?.status === 400) {
61
+ return null;
62
+ }
63
+ throw error;
64
+ });
65
+
66
+ if (station && includes.includes('INFO')) {
67
+ const poi = await client
68
+ .get<{ features: any[] }>(
69
+ `/boapi/proxy/geoloc/fleets/${client.clientOptions.fleetId}/poi/${station.poiId}`,
70
+ {
71
+ headers: { accept: 'application/vnd.geo+json' },
72
+ }
73
+ )
74
+ .then(({ data }) =>
75
+ data.features.reduce(
76
+ (max, current) => {
77
+ if (current.properties.Version > max.version) {
78
+ const {
79
+ geometry: {
80
+ coordinates: [longitude, latitude],
81
+ },
82
+ properties,
83
+ } = current;
84
+ return {
85
+ version: current.properties.Version,
86
+ name: properties.name,
87
+ coordinates: { latitude, longitude },
88
+ geoProperties: properties,
89
+ };
90
+ }
91
+ return max;
92
+ },
93
+ { version: -1 }
94
+ )
95
+ );
96
+
97
+ if (station.poiId && poi) {
98
+ poi.version = undefined;
99
+ Object.assign(station, poi);
100
+ station.name = station.geoProperties?.name;
101
+ }
102
+ }
103
+
104
+ if (station && includes.includes('SERVICES')) {
105
+ const services = await client
106
+ .get<{
107
+ stations: any[];
108
+ }>(`/boapi/proxy/user/fleets/${client.clientOptions.fleetId}/stations/details?showTimetable=false`)
109
+ .then(({ data }) =>
110
+ data.stations.reduce<{ [key: string]: ServiceInfo }>((acc, service) => {
111
+ if (!acc[service.station.id]) {
112
+ acc[service.station.id] = { services: [] };
113
+ }
114
+ acc[service.station.id].services.push({
115
+ id: service.serviceId,
116
+ models: service.station.models,
117
+ });
118
+ return acc;
119
+ }, {})
120
+ );
121
+
122
+ if (services[station.id]) {
123
+ Object.assign(station, services[station.id]);
124
+ }
125
+ }
126
+
127
+ return station;
128
+ };
@@ -1,63 +1,12 @@
1
1
  import { Client } from '@vulog/aima-client';
2
2
  import { z } from 'zod';
3
3
 
4
+ import { Days, GeoInfo, ServiceInfo, Station, Timetable } from './types';
5
+
4
6
  const IncludeSchema = z.enum(['INFO', 'OPEN_HOUR', 'SERVICES']);
5
7
  const IncludesSchema = z.array(IncludeSchema);
6
8
  export type Include = z.infer<typeof IncludeSchema>;
7
9
 
8
- export type Days = 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY';
9
-
10
- export type DayOpeningHours = {
11
- id: number;
12
- closed: boolean;
13
- openAt: string;
14
- closeAt: string;
15
- };
16
-
17
- export type Timetable = Record<Days, DayOpeningHours[]>;
18
-
19
- export type OpeningHours = {
20
- alwaysOpen: boolean;
21
- timetable: Timetable;
22
- };
23
-
24
- type GeoInfo = {
25
- name: string;
26
- coordinates: {
27
- latitude: number;
28
- longitude: number;
29
- };
30
- geoProperties: { [key: string]: any };
31
- };
32
-
33
- type Service = {
34
- id: string;
35
- models: {
36
- id: number;
37
- vehicles: string[];
38
- }[];
39
- };
40
-
41
- type ServiceInfo = {
42
- services: Service[];
43
- };
44
-
45
- export type Station = Partial<GeoInfo> &
46
- Partial<ServiceInfo> & {
47
- id: string;
48
- zoneId: string;
49
- poiId: string;
50
- modificationDate: string;
51
- fleetId: string;
52
- openingHours?: OpeningHours;
53
- // description: {
54
- // fleetId: null;
55
- // stationId: null;
56
- // infos: [];
57
- // };
58
- [key: string]: any;
59
- };
60
-
61
10
  export const getStations = async (client: Client, includes: Include[] = []): Promise<Station[]> => {
62
11
  const resultIncludes = IncludesSchema.safeParse(includes);
63
12
  if (!resultIncludes.success) {
package/src/index.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export { getBookingRequests, getScheduleBookingRequests, getSubscriptionBookingRequests } from './getBookingRequests';
2
2
  export { getSATBookingRequests } from './getSATBookingRequests';
3
3
  export { getBookingRequestById, getBookingRequestByTrip, getSubscriptionBookingRequestById } from './getBookingRequest';
4
- export type { BookingRequest } from './types';
4
+ export * from './types';
5
5
  export type { BookingRequestStatus, ServiceType, BookingRequestFilters } from './getBookingRequests';
6
6
  export type { SATBookingRequestStatus } from './getSATBookingRequests';
7
7
  export { getStations } from './getStations';
8
- export type { Station, Include, Days, DayOpeningHours, Timetable, OpeningHours } from './getStations';
8
+ export type { Include } from './getStations';
9
+ export { getStationById } from './getStation';
10
+ export type { IncludeStation } from './getStation';
package/src/types.ts CHANGED
@@ -103,3 +103,57 @@ export type SATBookingRequest = BaseBookingRequest & {
103
103
  trip: any | null;
104
104
  warning: string | null;
105
105
  };
106
+
107
+ // Station types
108
+ export type GeoInfo = {
109
+ name: string;
110
+ coordinates: {
111
+ latitude: number;
112
+ longitude: number;
113
+ };
114
+ geoProperties: { [key: string]: any };
115
+ };
116
+
117
+ export type Service = {
118
+ id: string;
119
+ models: {
120
+ id: number;
121
+ vehicles: string[];
122
+ }[];
123
+ };
124
+
125
+ export type ServiceInfo = {
126
+ services: Service[];
127
+ };
128
+
129
+ export type Days = 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY';
130
+
131
+ export type DayOpeningHours = {
132
+ id: number;
133
+ closed: boolean;
134
+ openAt: string;
135
+ closeAt: string;
136
+ };
137
+
138
+ export type Timetable = Record<Days, DayOpeningHours[]>;
139
+
140
+ export type OpeningHours = {
141
+ alwaysOpen: boolean;
142
+ timetable: Timetable;
143
+ };
144
+
145
+ export type Station = Partial<GeoInfo> &
146
+ Partial<ServiceInfo> & {
147
+ id: string;
148
+ zoneId: string;
149
+ poiId: string;
150
+ modificationDate: string;
151
+ fleetId: string;
152
+ openingHours?: OpeningHours;
153
+ // description: {
154
+ // fleetId: null;
155
+ // stationId: null;
156
+ // infos: [];
157
+ // };
158
+ [key: string]: any;
159
+ };