@pinecall/pinecall-booking-sdk 1.0.1
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/LICENSE +21 -0
- package/README.md +340 -0
- package/package.json +47 -0
- package/src/client.js +121 -0
- package/src/constants.js +41 -0
- package/src/errors.js +78 -0
- package/src/http.js +156 -0
- package/src/index.js +20 -0
- package/src/resources/availability.js +44 -0
- package/src/resources/bookings.js +126 -0
- package/src/resources/configurations.js +46 -0
- package/src/resources/resources.js +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Pinecall
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# @pinecall/pinecall-booking-sdk
|
|
4
|
+
|
|
5
|
+
**Official JavaScript SDK for the Pinecall Booking API**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@pinecall/pinecall-booking-sdk)
|
|
8
|
+
[](https://nodejs.org/)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
|
|
11
|
+
[Installation](#installation) •
|
|
12
|
+
[Quick Start](#quick-start) •
|
|
13
|
+
[API Reference](#api-reference) •
|
|
14
|
+
[Examples](#examples) •
|
|
15
|
+
[License](#license)
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @pinecall/pinecall-booking-sdk
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
import { Pinecall } from '@pinecall/pinecall-booking-sdk';
|
|
33
|
+
|
|
34
|
+
const client = new Pinecall('pk_your_api_key');
|
|
35
|
+
|
|
36
|
+
// Create a restaurant booking system
|
|
37
|
+
const config = await client.configurations.create({
|
|
38
|
+
name: 'My Restaurant',
|
|
39
|
+
businessType: 'restaurant',
|
|
40
|
+
resourceDefinition: {
|
|
41
|
+
name: 'table',
|
|
42
|
+
pluralName: 'tables',
|
|
43
|
+
attributes: [
|
|
44
|
+
{ key: 'seats', type: 'number', required: true }
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
businessRules: {
|
|
48
|
+
minBookingTime: '1h',
|
|
49
|
+
maxBookingTime: '3h',
|
|
50
|
+
bufferTime: '15m'
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Create tables
|
|
55
|
+
await client.resources.bulkCreate({
|
|
56
|
+
configurationId: config.configuration._id,
|
|
57
|
+
resources: [
|
|
58
|
+
{ name: 'Table 1', attributes: { seats: 2 } },
|
|
59
|
+
{ name: 'Table 2', attributes: { seats: 4 } },
|
|
60
|
+
{ name: 'Table 3', attributes: { seats: 6 } }
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Check availability
|
|
65
|
+
const availability = await client.availability.query({
|
|
66
|
+
configurationId: config.configuration._id,
|
|
67
|
+
startDate: '2025-01-20',
|
|
68
|
+
endDate: '2025-01-20'
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Create a booking
|
|
72
|
+
const booking = await client.bookings.create({
|
|
73
|
+
configurationId: config.configuration._id,
|
|
74
|
+
resourceId: availability.resources[0]._id,
|
|
75
|
+
startTime: '2025-01-20T19:00:00Z',
|
|
76
|
+
endTime: '2025-01-20T21:00:00Z',
|
|
77
|
+
customer: {
|
|
78
|
+
name: 'John Doe',
|
|
79
|
+
email: 'john@example.com',
|
|
80
|
+
phone: '+1234567890'
|
|
81
|
+
},
|
|
82
|
+
attributes: {
|
|
83
|
+
partySize: 4
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Confirm the booking
|
|
88
|
+
await client.bookings.confirm(booking.booking._id);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## API Reference
|
|
94
|
+
|
|
95
|
+
### Client Initialization
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
import { Pinecall } from '@pinecall/pinecall-booking-sdk';
|
|
99
|
+
|
|
100
|
+
// Simple
|
|
101
|
+
const client = new Pinecall('pk_your_api_key');
|
|
102
|
+
|
|
103
|
+
// With options
|
|
104
|
+
const client = new Pinecall('pk_your_api_key', {
|
|
105
|
+
baseUrl: 'https://api.example.com', // Custom API URL
|
|
106
|
+
timeout: 30000, // Request timeout (ms)
|
|
107
|
+
maxRetries: 3 // Retry attempts
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Configurations
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
// List all configurations
|
|
115
|
+
const configs = await client.configurations.list();
|
|
116
|
+
|
|
117
|
+
// Get a specific configuration
|
|
118
|
+
const config = await client.configurations.get('config_id');
|
|
119
|
+
|
|
120
|
+
// Create a configuration
|
|
121
|
+
const config = await client.configurations.create({
|
|
122
|
+
name: 'My Business',
|
|
123
|
+
businessType: 'restaurant',
|
|
124
|
+
resourceDefinition: { ... },
|
|
125
|
+
bookingDefinition: { ... },
|
|
126
|
+
businessRules: { ... },
|
|
127
|
+
defaultSchedule: [ ... ]
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Update a configuration
|
|
131
|
+
await client.configurations.update('config_id', { name: 'New Name' });
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Resources
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
// List resources
|
|
138
|
+
const resources = await client.resources.list('config_id');
|
|
139
|
+
|
|
140
|
+
// List with filters
|
|
141
|
+
const tables = await client.resources.list('config_id', {
|
|
142
|
+
seats: { $gte: 4 }
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Get a resource
|
|
146
|
+
const resource = await client.resources.get('resource_id');
|
|
147
|
+
|
|
148
|
+
// Create a resource
|
|
149
|
+
const resource = await client.resources.create({
|
|
150
|
+
configurationId: 'config_id',
|
|
151
|
+
name: 'Table 5',
|
|
152
|
+
attributes: { seats: 4, location: 'terrace' }
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Bulk create resources
|
|
156
|
+
const result = await client.resources.bulkCreate({
|
|
157
|
+
configurationId: 'config_id',
|
|
158
|
+
resources: [
|
|
159
|
+
{ name: 'Table 1', attributes: { seats: 2 } },
|
|
160
|
+
{ name: 'Table 2', attributes: { seats: 4 } }
|
|
161
|
+
]
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Update a resource
|
|
165
|
+
await client.resources.update('resource_id', { name: 'VIP Table' });
|
|
166
|
+
|
|
167
|
+
// Delete a resource
|
|
168
|
+
await client.resources.delete('resource_id');
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Availability
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
// Query available slots
|
|
175
|
+
const availability = await client.availability.query({
|
|
176
|
+
configurationId: 'config_id',
|
|
177
|
+
startDate: '2025-01-20',
|
|
178
|
+
endDate: '2025-01-20'
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Query with filters
|
|
182
|
+
const availability = await client.availability.query({
|
|
183
|
+
configurationId: 'config_id',
|
|
184
|
+
startDate: '2025-01-20',
|
|
185
|
+
endDate: '2025-01-20',
|
|
186
|
+
duration: 60,
|
|
187
|
+
resourceFilters: { seats: { $gte: 4 } }
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Check specific slot
|
|
191
|
+
const isAvailable = await client.availability.check(
|
|
192
|
+
'resource_id',
|
|
193
|
+
'2025-01-20T19:00:00Z',
|
|
194
|
+
'2025-01-20T21:00:00Z'
|
|
195
|
+
);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Bookings
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
// Create a booking
|
|
202
|
+
const booking = await client.bookings.create({
|
|
203
|
+
configurationId: 'config_id',
|
|
204
|
+
resourceId: 'resource_id',
|
|
205
|
+
startTime: '2025-01-20T19:00:00Z',
|
|
206
|
+
endTime: '2025-01-20T21:00:00Z',
|
|
207
|
+
customer: {
|
|
208
|
+
name: 'John Doe',
|
|
209
|
+
email: 'john@example.com'
|
|
210
|
+
},
|
|
211
|
+
attributes: {
|
|
212
|
+
partySize: 4
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Get a booking
|
|
217
|
+
const booking = await client.bookings.get('booking_id');
|
|
218
|
+
|
|
219
|
+
// List bookings
|
|
220
|
+
const bookings = await client.bookings.list({
|
|
221
|
+
configurationId: 'config_id',
|
|
222
|
+
startDate: '2025-01-01',
|
|
223
|
+
endDate: '2025-01-31'
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// List with filters
|
|
227
|
+
const bookings = await client.bookings.list({
|
|
228
|
+
configurationId: 'config_id',
|
|
229
|
+
startDate: '2025-01-01',
|
|
230
|
+
endDate: '2025-01-31',
|
|
231
|
+
status: 'confirmed',
|
|
232
|
+
resourceId: 'resource_id'
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Confirm a booking
|
|
236
|
+
await client.bookings.confirm('booking_id');
|
|
237
|
+
|
|
238
|
+
// Cancel a booking
|
|
239
|
+
await client.bookings.cancel('booking_id', 'Customer request');
|
|
240
|
+
|
|
241
|
+
// Reschedule a booking
|
|
242
|
+
await client.bookings.reschedule(
|
|
243
|
+
'booking_id',
|
|
244
|
+
'2025-01-21T19:00:00Z',
|
|
245
|
+
'2025-01-21T21:00:00Z'
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Mark as completed
|
|
249
|
+
await client.bookings.complete('booking_id');
|
|
250
|
+
|
|
251
|
+
// Mark as no-show
|
|
252
|
+
await client.bookings.noShow('booking_id');
|
|
253
|
+
|
|
254
|
+
// Get statistics
|
|
255
|
+
const stats = await client.bookings.stats('config_id', '2025-01-01', '2025-01-31');
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Static Helpers
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
import { Pinecall } from '@pinecall/pinecall-booking-sdk';
|
|
262
|
+
|
|
263
|
+
// Get date N days in the future (YYYY-MM-DD)
|
|
264
|
+
Pinecall.futureDate(7); // '2025-01-27'
|
|
265
|
+
|
|
266
|
+
// Get datetime N days in the future at specific time
|
|
267
|
+
Pinecall.futureDateTime(7, 19, 0); // '2025-01-27T19:00:00.000Z'
|
|
268
|
+
|
|
269
|
+
// Format date for API
|
|
270
|
+
Pinecall.formatDate(new Date()); // '2025-01-20'
|
|
271
|
+
|
|
272
|
+
// Format datetime for API
|
|
273
|
+
Pinecall.formatDateTime(new Date()); // '2025-01-20T15:30:00.000Z'
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Error Handling
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
import { Pinecall, PinecallError, ValidationError, NotFoundError, ConflictError } from '@pinecall/pinecall-booking-sdk';
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const booking = await client.bookings.create({ ... });
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (error instanceof ValidationError) {
|
|
287
|
+
console.log('Invalid data:', error.message);
|
|
288
|
+
} else if (error instanceof NotFoundError) {
|
|
289
|
+
console.log('Resource not found:', error.message);
|
|
290
|
+
} else if (error instanceof ConflictError) {
|
|
291
|
+
console.log('Booking conflict:', error.message);
|
|
292
|
+
} else if (error instanceof PinecallError) {
|
|
293
|
+
console.log('API error:', error.message, error.statusCode);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Constants
|
|
301
|
+
|
|
302
|
+
```javascript
|
|
303
|
+
import { BOOKING_STATUS, BUSINESS_TYPES } from '@pinecall/pinecall-booking-sdk';
|
|
304
|
+
|
|
305
|
+
BOOKING_STATUS.PENDING // 'pending'
|
|
306
|
+
BOOKING_STATUS.CONFIRMED // 'confirmed'
|
|
307
|
+
BOOKING_STATUS.CANCELLED // 'cancelled'
|
|
308
|
+
BOOKING_STATUS.COMPLETED // 'completed'
|
|
309
|
+
BOOKING_STATUS.NO_SHOW // 'no_show'
|
|
310
|
+
|
|
311
|
+
BUSINESS_TYPES.RESTAURANT // 'restaurant'
|
|
312
|
+
BUSINESS_TYPES.SPA // 'spa'
|
|
313
|
+
BUSINESS_TYPES.HOTEL // 'hotel'
|
|
314
|
+
BUSINESS_TYPES.CLINIC // 'clinic'
|
|
315
|
+
BUSINESS_TYPES.SPORTS // 'sports'
|
|
316
|
+
BUSINESS_TYPES.COWORKING // 'coworking'
|
|
317
|
+
BUSINESS_TYPES.CUSTOM // 'custom'
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Examples
|
|
323
|
+
|
|
324
|
+
See the [examples](./examples) directory for complete working examples:
|
|
325
|
+
|
|
326
|
+
- [Restaurant Booking](./examples/basic.js) - Tables, time slots, reservations
|
|
327
|
+
- [Spa Appointments](./examples/spa.js) - Therapists, treatments, scheduling
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Requirements
|
|
332
|
+
|
|
333
|
+
- Node.js >= 18.0.0
|
|
334
|
+
- ES Modules support
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## License
|
|
339
|
+
|
|
340
|
+
MIT © [Pinecall](https://pinecall.io)
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pinecall/pinecall-booking-sdk",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Simple, elegant SDK for Pinecall Booking API. Zero dependencies.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "src/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./src/index.js",
|
|
11
|
+
"require": "./src/index.cjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node --test test/*.test.js",
|
|
16
|
+
"test:watch": "node --test --watch test/*.test.js",
|
|
17
|
+
"example": "node examples/basic.js"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"booking",
|
|
21
|
+
"reservations",
|
|
22
|
+
"scheduling",
|
|
23
|
+
"api",
|
|
24
|
+
"sdk",
|
|
25
|
+
"pinecall",
|
|
26
|
+
"restaurant",
|
|
27
|
+
"appointments"
|
|
28
|
+
],
|
|
29
|
+
"author": "Pinecall <support@pinecall.com>",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/pinecall/pinecall-booking-sdk.git"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/pinecall/pinecall-booking-sdk/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://pinecall.io/docs/pinecall-booking-sdk",
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18.0.0"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"src",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
]
|
|
47
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { HttpClient } from './http.js';
|
|
2
|
+
import { Configurations } from './resources/configurations.js';
|
|
3
|
+
import { Resources } from './resources/resources.js';
|
|
4
|
+
import { Availability } from './resources/availability.js';
|
|
5
|
+
import { Bookings } from './resources/bookings.js';
|
|
6
|
+
import { ValidationError } from './errors.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Pinecall Booking SDK Client
|
|
10
|
+
*
|
|
11
|
+
* A simple, elegant client for the Pinecall Booking API.
|
|
12
|
+
* Inspired by Stripe's SDK design.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```js
|
|
16
|
+
* import { Pinecall } from '@pinecall/booking-sdk';
|
|
17
|
+
*
|
|
18
|
+
* const client = new Pinecall('pk_your_api_key');
|
|
19
|
+
*
|
|
20
|
+
* // Or with options
|
|
21
|
+
* const client = new Pinecall('pk_your_api_key', {
|
|
22
|
+
* baseUrl: 'https://api.pinecall.com',
|
|
23
|
+
* timeout: 30000
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class Pinecall {
|
|
28
|
+
/** @type {Configurations} */
|
|
29
|
+
configurations;
|
|
30
|
+
|
|
31
|
+
/** @type {Resources} */
|
|
32
|
+
resources;
|
|
33
|
+
|
|
34
|
+
/** @type {Availability} */
|
|
35
|
+
availability;
|
|
36
|
+
|
|
37
|
+
/** @type {Bookings} */
|
|
38
|
+
bookings;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a new Pinecall client
|
|
42
|
+
* @param {string} apiKey - Your Pinecall API key
|
|
43
|
+
* @param {Object} [options]
|
|
44
|
+
* @param {string} [options.baseUrl='https://api.pinecall.com'] - API base URL
|
|
45
|
+
* @param {number} [options.timeout=30000] - Request timeout in milliseconds
|
|
46
|
+
* @param {number} [options.maxRetries=3] - Maximum retry attempts
|
|
47
|
+
*/
|
|
48
|
+
constructor(apiKey, options = {}) {
|
|
49
|
+
if (!apiKey || typeof apiKey !== 'string') {
|
|
50
|
+
throw new ValidationError('API key is required');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const http = new HttpClient({
|
|
54
|
+
apiKey,
|
|
55
|
+
baseUrl: options.baseUrl || 'https://api.pinecall.com',
|
|
56
|
+
timeout: options.timeout || 30000,
|
|
57
|
+
maxRetries: options.maxRetries || 3
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Initialize resource managers
|
|
61
|
+
this.configurations = new Configurations(http);
|
|
62
|
+
this.resources = new Resources(http);
|
|
63
|
+
this.availability = new Availability(http);
|
|
64
|
+
this.bookings = new Bookings(http);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ═══════════════════════════════════════════════════════════════
|
|
68
|
+
// STATIC HELPERS
|
|
69
|
+
// ═══════════════════════════════════════════════════════════════
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get a date string for N days in the future
|
|
73
|
+
* @param {number} [days=1] - Days ahead
|
|
74
|
+
* @returns {string} Date string (YYYY-MM-DD)
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* Pinecall.futureDate(7) // '2025-01-27'
|
|
78
|
+
*/
|
|
79
|
+
static futureDate(days = 1) {
|
|
80
|
+
const date = new Date();
|
|
81
|
+
date.setDate(date.getDate() + days);
|
|
82
|
+
return date.toISOString().split('T')[0];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get a datetime string for N days in the future at specific time
|
|
87
|
+
* @param {number} [days=1] - Days ahead
|
|
88
|
+
* @param {number} [hour=12] - Hour (0-23)
|
|
89
|
+
* @param {number} [minute=0] - Minute (0-59)
|
|
90
|
+
* @returns {string} ISO 8601 datetime string
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* Pinecall.futureDateTime(7, 19, 0) // '2025-01-27T19:00:00.000Z'
|
|
94
|
+
*/
|
|
95
|
+
static futureDateTime(days = 1, hour = 12, minute = 0) {
|
|
96
|
+
const date = new Date();
|
|
97
|
+
date.setDate(date.getDate() + days);
|
|
98
|
+
date.setUTCHours(hour, minute, 0, 0);
|
|
99
|
+
return date.toISOString();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Format a Date object to API date format
|
|
104
|
+
* @param {Date|string} date - Date to format
|
|
105
|
+
* @returns {string} Date string (YYYY-MM-DD)
|
|
106
|
+
*/
|
|
107
|
+
static formatDate(date) {
|
|
108
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
109
|
+
return d.toISOString().split('T')[0];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Format a Date object to API datetime format
|
|
114
|
+
* @param {Date|string} date - Date to format
|
|
115
|
+
* @returns {string} ISO 8601 datetime string
|
|
116
|
+
*/
|
|
117
|
+
static formatDateTime(date) {
|
|
118
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
119
|
+
return d.toISOString();
|
|
120
|
+
}
|
|
121
|
+
}
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Booking status constants
|
|
3
|
+
* @readonly
|
|
4
|
+
* @enum {string}
|
|
5
|
+
*/
|
|
6
|
+
export const BOOKING_STATUS = Object.freeze({
|
|
7
|
+
PENDING: 'pending',
|
|
8
|
+
CONFIRMED: 'confirmed',
|
|
9
|
+
CANCELLED: 'cancelled',
|
|
10
|
+
COMPLETED: 'completed',
|
|
11
|
+
NO_SHOW: 'no_show'
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Common business types
|
|
16
|
+
* @readonly
|
|
17
|
+
* @enum {string}
|
|
18
|
+
*/
|
|
19
|
+
export const BUSINESS_TYPES = Object.freeze({
|
|
20
|
+
RESTAURANT: 'restaurant',
|
|
21
|
+
SPA: 'spa',
|
|
22
|
+
SALON: 'salon',
|
|
23
|
+
HOTEL: 'hotel',
|
|
24
|
+
SPORTS: 'sports',
|
|
25
|
+
COWORKING: 'coworking',
|
|
26
|
+
MEDICAL: 'medical',
|
|
27
|
+
CUSTOM: 'custom'
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Booking actions
|
|
32
|
+
* @readonly
|
|
33
|
+
* @enum {string}
|
|
34
|
+
*/
|
|
35
|
+
export const BOOKING_ACTIONS = Object.freeze({
|
|
36
|
+
CONFIRM: 'confirm',
|
|
37
|
+
CANCEL: 'cancel',
|
|
38
|
+
COMPLETE: 'complete',
|
|
39
|
+
NO_SHOW: 'no_show',
|
|
40
|
+
RESCHEDULE: 'reschedule'
|
|
41
|
+
});
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for Pinecall SDK
|
|
3
|
+
*/
|
|
4
|
+
export class PinecallError extends Error {
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} message - Error message
|
|
7
|
+
* @param {string} [code] - Error code
|
|
8
|
+
* @param {number} [status] - HTTP status code
|
|
9
|
+
* @param {Object} [details] - Additional error details
|
|
10
|
+
*/
|
|
11
|
+
constructor(message, code = 'PINECALL_ERROR', status = 500, details = {}) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'PinecallError';
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.status = status;
|
|
16
|
+
this.details = details;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
toJSON() {
|
|
20
|
+
return {
|
|
21
|
+
name: this.name,
|
|
22
|
+
message: this.message,
|
|
23
|
+
code: this.code,
|
|
24
|
+
status: this.status,
|
|
25
|
+
details: this.details
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validation error (400)
|
|
32
|
+
*/
|
|
33
|
+
export class ValidationError extends PinecallError {
|
|
34
|
+
constructor(message, details = {}) {
|
|
35
|
+
super(message, 'VALIDATION_ERROR', 400, details);
|
|
36
|
+
this.name = 'ValidationError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Not found error (404)
|
|
42
|
+
*/
|
|
43
|
+
export class NotFoundError extends PinecallError {
|
|
44
|
+
constructor(message = 'Resource not found', details = {}) {
|
|
45
|
+
super(message, 'NOT_FOUND', 404, details);
|
|
46
|
+
this.name = 'NotFoundError';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Conflict error (409) - e.g., double booking
|
|
52
|
+
*/
|
|
53
|
+
export class ConflictError extends PinecallError {
|
|
54
|
+
constructor(message = 'Resource conflict', details = {}) {
|
|
55
|
+
super(message, 'CONFLICT', 409, details);
|
|
56
|
+
this.name = 'ConflictError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Authentication error (401)
|
|
62
|
+
*/
|
|
63
|
+
export class AuthenticationError extends PinecallError {
|
|
64
|
+
constructor(message = 'Invalid API key') {
|
|
65
|
+
super(message, 'UNAUTHORIZED', 401);
|
|
66
|
+
this.name = 'AuthenticationError';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Rate limit error (429)
|
|
72
|
+
*/
|
|
73
|
+
export class RateLimitError extends PinecallError {
|
|
74
|
+
constructor(message = 'Rate limit exceeded', retryAfter = 60) {
|
|
75
|
+
super(message, 'RATE_LIMIT', 429, { retryAfter });
|
|
76
|
+
this.name = 'RateLimitError';
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/http.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { PinecallError, AuthenticationError, NotFoundError, ValidationError, RateLimitError } from './errors.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HTTP client with retry logic and error handling
|
|
5
|
+
*/
|
|
6
|
+
export class HttpClient {
|
|
7
|
+
#apiKey;
|
|
8
|
+
#baseUrl;
|
|
9
|
+
#timeout;
|
|
10
|
+
#maxRetries;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {Object} config
|
|
14
|
+
* @param {string} config.apiKey - API key
|
|
15
|
+
* @param {string} [config.baseUrl] - Base URL
|
|
16
|
+
* @param {number} [config.timeout] - Request timeout in ms
|
|
17
|
+
* @param {number} [config.maxRetries] - Max retry attempts
|
|
18
|
+
*/
|
|
19
|
+
constructor({ apiKey, baseUrl = 'https://api.pinecall.com', timeout = 30000, maxRetries = 3 }) {
|
|
20
|
+
this.#apiKey = apiKey;
|
|
21
|
+
this.#baseUrl = baseUrl.replace(/\/$/, '');
|
|
22
|
+
this.#timeout = timeout;
|
|
23
|
+
this.#maxRetries = maxRetries;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Make HTTP request with automatic retry
|
|
28
|
+
* @param {string} method - HTTP method
|
|
29
|
+
* @param {string} path - API path
|
|
30
|
+
* @param {Object} [options] - Request options
|
|
31
|
+
* @returns {Promise<any>}
|
|
32
|
+
*/
|
|
33
|
+
async request(method, path, options = {}) {
|
|
34
|
+
const url = `${this.#baseUrl}${path}`;
|
|
35
|
+
const { body, query, headers: customHeaders = {} } = options;
|
|
36
|
+
|
|
37
|
+
// Build URL with query params
|
|
38
|
+
const urlWithQuery = query
|
|
39
|
+
? `${url}?${new URLSearchParams(this.#flattenQuery(query))}`
|
|
40
|
+
: url;
|
|
41
|
+
|
|
42
|
+
const headers = {
|
|
43
|
+
'Content-Type': 'application/json',
|
|
44
|
+
'X-API-Key': this.#apiKey,
|
|
45
|
+
'User-Agent': '@pinecall/booking-sdk/1.0.0',
|
|
46
|
+
...customHeaders
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const fetchOptions = {
|
|
50
|
+
method,
|
|
51
|
+
headers,
|
|
52
|
+
...(body && { body: JSON.stringify(body) })
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return this.#executeWithRetry(urlWithQuery, fetchOptions);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Execute request with retry logic
|
|
60
|
+
*/
|
|
61
|
+
async #executeWithRetry(url, options, attempt = 1) {
|
|
62
|
+
const controller = new AbortController();
|
|
63
|
+
const timeoutId = setTimeout(() => controller.abort(), this.#timeout);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(url, { ...options, signal: controller.signal });
|
|
67
|
+
clearTimeout(timeoutId);
|
|
68
|
+
|
|
69
|
+
const data = await response.json().catch(() => ({}));
|
|
70
|
+
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw this.#createError(response.status, data);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return data;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
clearTimeout(timeoutId);
|
|
78
|
+
|
|
79
|
+
// Retry on network errors or 5xx
|
|
80
|
+
if (this.#shouldRetry(error, attempt)) {
|
|
81
|
+
const delay = this.#getRetryDelay(attempt);
|
|
82
|
+
await this.#sleep(delay);
|
|
83
|
+
return this.#executeWithRetry(url, options, attempt + 1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (error.name === 'AbortError') {
|
|
87
|
+
throw new PinecallError(`Request timeout after ${this.#timeout}ms`, 'TIMEOUT', 408);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
throw error instanceof PinecallError ? error : new PinecallError(error.message);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create appropriate error from response
|
|
96
|
+
*/
|
|
97
|
+
#createError(status, data) {
|
|
98
|
+
const message = data.error || data.message || 'Unknown error';
|
|
99
|
+
|
|
100
|
+
switch (status) {
|
|
101
|
+
case 400: return new ValidationError(message, data.details);
|
|
102
|
+
case 401: return new AuthenticationError(message);
|
|
103
|
+
case 404: return new NotFoundError(message);
|
|
104
|
+
case 429: return new RateLimitError(message, data.retryAfter);
|
|
105
|
+
default: return new PinecallError(message, data.code || 'API_ERROR', status, data);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#shouldRetry(error, attempt) {
|
|
110
|
+
if (attempt >= this.#maxRetries) return false;
|
|
111
|
+
if (error instanceof RateLimitError) return true;
|
|
112
|
+
if (error.status >= 500) return true;
|
|
113
|
+
if (error.name === 'AbortError') return true;
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#getRetryDelay(attempt) {
|
|
118
|
+
// Exponential backoff: 1s, 2s, 4s...
|
|
119
|
+
return Math.min(1000 * Math.pow(2, attempt - 1), 10000);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#sleep(ms) {
|
|
123
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#flattenQuery(query, prefix = '') {
|
|
127
|
+
const params = {};
|
|
128
|
+
|
|
129
|
+
for (const [key, value] of Object.entries(query)) {
|
|
130
|
+
if (value === undefined || value === null) continue;
|
|
131
|
+
|
|
132
|
+
const paramKey = prefix ? `${prefix}_${key}` : key;
|
|
133
|
+
|
|
134
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
135
|
+
// For attribute filters, stringify the object
|
|
136
|
+
if (key.startsWith('attr') || prefix === 'attr') {
|
|
137
|
+
params[paramKey] = JSON.stringify(value);
|
|
138
|
+
} else {
|
|
139
|
+
Object.assign(params, this.#flattenQuery(value, paramKey));
|
|
140
|
+
}
|
|
141
|
+
} else if (Array.isArray(value)) {
|
|
142
|
+
params[paramKey] = value.join(',');
|
|
143
|
+
} else {
|
|
144
|
+
params[paramKey] = String(value);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return params;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Convenience methods
|
|
152
|
+
get(path, query) { return this.request('GET', path, { query }); }
|
|
153
|
+
post(path, body) { return this.request('POST', path, { body }); }
|
|
154
|
+
put(path, body) { return this.request('PUT', path, { body }); }
|
|
155
|
+
delete(path, body) { return this.request('DELETE', path, { body }); }
|
|
156
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pinecall/booking-sdk
|
|
3
|
+
*
|
|
4
|
+
* Simple, elegant SDK for Pinecall Booking API.
|
|
5
|
+
* Zero dependencies. Full JSDoc support.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```js
|
|
9
|
+
* import { Pinecall } from '@pinecall/booking-sdk';
|
|
10
|
+
*
|
|
11
|
+
* const client = new Pinecall('pk_your_api_key');
|
|
12
|
+
* const config = await client.configurations.create({ name: 'My Restaurant' });
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @module @pinecall/booking-sdk
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export { Pinecall } from './client.js';
|
|
19
|
+
export { PinecallError, ValidationError, NotFoundError, ConflictError } from './errors.js';
|
|
20
|
+
export { BOOKING_STATUS, BUSINESS_TYPES } from './constants.js';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Availability - Query available time slots
|
|
3
|
+
*/
|
|
4
|
+
export class Availability {
|
|
5
|
+
#http;
|
|
6
|
+
|
|
7
|
+
constructor(http) {
|
|
8
|
+
this.#http = http;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Query available slots
|
|
13
|
+
* @param {Object} params
|
|
14
|
+
* @returns {Promise<{summary: Object, resources: Array, availableSlots: Array}>}
|
|
15
|
+
*/
|
|
16
|
+
query(params) {
|
|
17
|
+
const query = {
|
|
18
|
+
configId: params.configurationId,
|
|
19
|
+
startDate: params.startDate,
|
|
20
|
+
endDate: params.endDate
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (params.duration) {
|
|
24
|
+
query.duration = params.duration;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (params.resourceFilters) {
|
|
28
|
+
for (const [key, value] of Object.entries(params.resourceFilters)) {
|
|
29
|
+
query[`attr_${key}`] = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return this.#http.get('/api/bookings/availability', query);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if a specific slot is available
|
|
38
|
+
* @param {Object} params
|
|
39
|
+
* @returns {Promise<{available: boolean, reason?: string}>}
|
|
40
|
+
*/
|
|
41
|
+
check(params) {
|
|
42
|
+
return this.#http.post('/api/bookings/availability', params);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { BOOKING_ACTIONS } from '../constants.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bookings - Create and manage reservations
|
|
5
|
+
*/
|
|
6
|
+
export class Bookings {
|
|
7
|
+
#http;
|
|
8
|
+
|
|
9
|
+
constructor(http) {
|
|
10
|
+
this.#http = http;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* List bookings
|
|
15
|
+
* @param {Object} params
|
|
16
|
+
* @returns {Promise<{bookings: Array}>}
|
|
17
|
+
*/
|
|
18
|
+
list(params) {
|
|
19
|
+
const query = {
|
|
20
|
+
configId: params.configurationId,
|
|
21
|
+
startDate: params.startDate,
|
|
22
|
+
endDate: params.endDate
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (params.status) {
|
|
26
|
+
query.status = Array.isArray(params.status) ? params.status.join(',') : params.status;
|
|
27
|
+
}
|
|
28
|
+
if (params.resourceId) query.resourceId = params.resourceId;
|
|
29
|
+
if (params.search) query.search = params.search;
|
|
30
|
+
|
|
31
|
+
return this.#http.get('/api/bookings/manage', query);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get a booking by ID
|
|
36
|
+
* @param {string} id - Booking ID
|
|
37
|
+
* @returns {Promise<{booking: Object}>}
|
|
38
|
+
*/
|
|
39
|
+
get(id) {
|
|
40
|
+
return this.#http.get('/api/bookings/manage', { id });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create a new booking
|
|
45
|
+
* @param {Object} params
|
|
46
|
+
* @returns {Promise<{booking: Object}>}
|
|
47
|
+
*/
|
|
48
|
+
create(params) {
|
|
49
|
+
return this.#http.post('/api/bookings/create', params);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Confirm a booking
|
|
54
|
+
* @param {string} id - Booking ID
|
|
55
|
+
* @param {string} [reason] - Reason
|
|
56
|
+
* @returns {Promise<{booking: Object}>}
|
|
57
|
+
*/
|
|
58
|
+
confirm(id, reason) {
|
|
59
|
+
return this.#action(id, BOOKING_ACTIONS.CONFIRM, { reason });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Cancel a booking
|
|
64
|
+
* @param {string} id - Booking ID
|
|
65
|
+
* @param {string} [reason] - Reason
|
|
66
|
+
* @returns {Promise<{booking: Object}>}
|
|
67
|
+
*/
|
|
68
|
+
cancel(id, reason) {
|
|
69
|
+
return this.#action(id, BOOKING_ACTIONS.CANCEL, { reason });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Mark booking as completed
|
|
74
|
+
* @param {string} id - Booking ID
|
|
75
|
+
* @param {string} [reason] - Reason
|
|
76
|
+
* @returns {Promise<{booking: Object}>}
|
|
77
|
+
*/
|
|
78
|
+
complete(id, reason) {
|
|
79
|
+
return this.#action(id, BOOKING_ACTIONS.COMPLETE, { reason });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Mark booking as no-show
|
|
84
|
+
* @param {string} id - Booking ID
|
|
85
|
+
* @param {string} [reason] - Reason
|
|
86
|
+
* @returns {Promise<{booking: Object}>}
|
|
87
|
+
*/
|
|
88
|
+
noShow(id, reason) {
|
|
89
|
+
return this.#action(id, BOOKING_ACTIONS.NO_SHOW, { reason });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Reschedule a booking
|
|
94
|
+
* @param {string} id - Booking ID
|
|
95
|
+
* @param {Object} params
|
|
96
|
+
* @returns {Promise<{booking: Object}>}
|
|
97
|
+
*/
|
|
98
|
+
reschedule(id, params) {
|
|
99
|
+
return this.#action(id, BOOKING_ACTIONS.RESCHEDULE, {
|
|
100
|
+
newStartTime: params.newStartTime,
|
|
101
|
+
newEndTime: params.newEndTime
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get booking statistics
|
|
107
|
+
* @param {string} configId - Configuration ID
|
|
108
|
+
* @param {Object} [params]
|
|
109
|
+
* @returns {Promise<{stats: Object}>}
|
|
110
|
+
*/
|
|
111
|
+
stats(configId, params = {}) {
|
|
112
|
+
return this.#http.get('/api/bookings/manage', {
|
|
113
|
+
configId,
|
|
114
|
+
stats: 'true',
|
|
115
|
+
...params
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#action(bookingId, action, params = {}) {
|
|
120
|
+
return this.#http.post('/api/bookings/manage', {
|
|
121
|
+
bookingId,
|
|
122
|
+
action,
|
|
123
|
+
...params
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration resource - Manage booking system configurations
|
|
3
|
+
*/
|
|
4
|
+
export class Configurations {
|
|
5
|
+
#http;
|
|
6
|
+
|
|
7
|
+
constructor(http) {
|
|
8
|
+
this.#http = http;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* List all configurations
|
|
13
|
+
* @returns {Promise<{configurations: Array}>}
|
|
14
|
+
*/
|
|
15
|
+
list() {
|
|
16
|
+
return this.#http.get('/api/bookings/configure');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get a configuration by ID
|
|
21
|
+
* @param {string} id - Configuration ID
|
|
22
|
+
* @returns {Promise<{configuration: Object}>}
|
|
23
|
+
*/
|
|
24
|
+
get(id) {
|
|
25
|
+
return this.#http.get('/api/bookings/configure', { id });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create a new configuration
|
|
30
|
+
* @param {Object} params
|
|
31
|
+
* @returns {Promise<{configuration: Object}>}
|
|
32
|
+
*/
|
|
33
|
+
create(params) {
|
|
34
|
+
return this.#http.post('/api/bookings/configure', params);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Update a configuration
|
|
39
|
+
* @param {string} id - Configuration ID
|
|
40
|
+
* @param {Object} params - Fields to update
|
|
41
|
+
* @returns {Promise<{configuration: Object}>}
|
|
42
|
+
*/
|
|
43
|
+
update(id, params) {
|
|
44
|
+
return this.#http.put('/api/bookings/configure', { configurationId: id, ...params });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resources - Manage bookable entities (tables, rooms, vehicles, etc.)
|
|
3
|
+
*/
|
|
4
|
+
export class Resources {
|
|
5
|
+
#http;
|
|
6
|
+
|
|
7
|
+
constructor(http) {
|
|
8
|
+
this.#http = http;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* List resources for a configuration
|
|
13
|
+
* @param {string} configId - Configuration ID
|
|
14
|
+
* @param {Object} [filters] - Attribute filters
|
|
15
|
+
* @returns {Promise<{resources: Array, count: number}>}
|
|
16
|
+
*/
|
|
17
|
+
list(configId, filters = {}) {
|
|
18
|
+
const query = { configId };
|
|
19
|
+
|
|
20
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
21
|
+
query[`attr_${key}`] = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return this.#http.get('/api/bookings/resources', query);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get a resource by ID
|
|
29
|
+
* @param {string} id - Resource ID
|
|
30
|
+
* @returns {Promise<{resource: Object}>}
|
|
31
|
+
*/
|
|
32
|
+
get(id) {
|
|
33
|
+
return this.#http.get('/api/bookings/resources', { id });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a single resource
|
|
38
|
+
* @param {Object} params
|
|
39
|
+
* @returns {Promise<{resource: Object}>}
|
|
40
|
+
*/
|
|
41
|
+
create(params) {
|
|
42
|
+
return this.#http.post('/api/bookings/resources', params);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Bulk create resources
|
|
47
|
+
* @param {Object} params
|
|
48
|
+
* @returns {Promise<{created: number, resources: Array}>}
|
|
49
|
+
*/
|
|
50
|
+
bulkCreate(params) {
|
|
51
|
+
return this.#http.post('/api/bookings/resources', params);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Update a resource
|
|
56
|
+
* @param {string} id - Resource ID
|
|
57
|
+
* @param {Object} params - Fields to update
|
|
58
|
+
* @returns {Promise<{resource: Object}>}
|
|
59
|
+
*/
|
|
60
|
+
update(id, params) {
|
|
61
|
+
return this.#http.put('/api/bookings/resources', { resourceId: id, ...params });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Delete a resource
|
|
66
|
+
* @param {string} id - Resource ID
|
|
67
|
+
* @returns {Promise<{success: boolean}>}
|
|
68
|
+
*/
|
|
69
|
+
delete(id) {
|
|
70
|
+
return this.#http.delete('/api/bookings/resources', { resourceId: id });
|
|
71
|
+
}
|
|
72
|
+
}
|