@htlkg/data 0.0.19 → 0.0.21

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.
Files changed (39) hide show
  1. package/dist/client/index.d.ts +257 -1
  2. package/dist/client/index.js +59 -1
  3. package/dist/client/index.js.map +1 -1
  4. package/dist/common-DSxswsZ3.d.ts +40 -0
  5. package/dist/hooks/index.d.ts +162 -10
  6. package/dist/hooks/index.js +191 -33
  7. package/dist/hooks/index.js.map +1 -1
  8. package/dist/index.d.ts +9 -5
  9. package/dist/index.js +789 -13
  10. package/dist/index.js.map +1 -1
  11. package/dist/mutations/index.d.ts +342 -4
  12. package/dist/mutations/index.js +486 -0
  13. package/dist/mutations/index.js.map +1 -1
  14. package/dist/{productInstances-BA3cNsYc.d.ts → productInstances-BpQv1oLS.d.ts} +2 -40
  15. package/dist/queries/index.d.ts +113 -2
  16. package/dist/queries/index.js +192 -1
  17. package/dist/queries/index.js.map +1 -1
  18. package/dist/reservations-C0FNm__0.d.ts +154 -0
  19. package/dist/reservations-CdDfkcZ_.d.ts +172 -0
  20. package/package.json +14 -13
  21. package/src/client/index.ts +18 -0
  22. package/src/client/reservations.ts +336 -0
  23. package/src/hooks/createDataHook.test.ts +534 -0
  24. package/src/hooks/createDataHook.ts +20 -13
  25. package/src/hooks/index.ts +2 -0
  26. package/src/hooks/useContacts.test.ts +159 -0
  27. package/src/hooks/useContacts.ts +176 -0
  28. package/src/hooks/useReservations.ts +145 -0
  29. package/src/mutations/contacts.test.ts +604 -0
  30. package/src/mutations/contacts.ts +554 -0
  31. package/src/mutations/index.ts +32 -0
  32. package/src/mutations/productInstances/productInstances.test.ts +3 -3
  33. package/src/mutations/reservations.test.ts +459 -0
  34. package/src/mutations/reservations.ts +452 -0
  35. package/src/queries/contacts.test.ts +505 -0
  36. package/src/queries/contacts.ts +237 -0
  37. package/src/queries/index.ts +21 -0
  38. package/src/queries/reservations.test.ts +374 -0
  39. package/src/queries/reservations.ts +247 -0
@@ -0,0 +1,452 @@
1
+ /**
2
+ * Reservation Mutation Functions
3
+ *
4
+ * Provides type-safe mutation functions for creating, updating, and deleting reservations.
5
+ * Includes date validation and status transition rules.
6
+ */
7
+
8
+ import {
9
+ checkRestoreEligibility,
10
+ DEFAULT_SOFT_DELETE_RETENTION_DAYS,
11
+ } from "../queries/systemSettings";
12
+ import type { CreateAuditFields, UpdateWithSoftDeleteFields } from "./common";
13
+
14
+ /**
15
+ * Reservation status type
16
+ */
17
+ export type ReservationStatus = "confirmed" | "checked_in" | "checked_out" | "cancelled" | "no_show";
18
+
19
+ /**
20
+ * Valid status transitions for reservations
21
+ * Maps current status -> allowed next statuses
22
+ */
23
+ const STATUS_TRANSITIONS: Record<ReservationStatus, ReservationStatus[]> = {
24
+ confirmed: ["checked_in", "cancelled", "no_show"],
25
+ checked_in: ["checked_out", "cancelled"],
26
+ checked_out: [], // Terminal state - no transitions allowed
27
+ cancelled: [], // Terminal state - no transitions allowed
28
+ no_show: [], // Terminal state - no transitions allowed
29
+ };
30
+
31
+ /**
32
+ * Validation error class for reservation operations
33
+ */
34
+ export class ReservationValidationError extends Error {
35
+ constructor(message: string) {
36
+ super(message);
37
+ this.name = "ReservationValidationError";
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Validate check-in and check-out dates
43
+ * @throws {ReservationValidationError} if dates are invalid
44
+ */
45
+ function validateDates(checkIn: string, checkOut: string): void {
46
+ const checkInDate = new Date(checkIn);
47
+ const checkOutDate = new Date(checkOut);
48
+
49
+ // Check if dates are valid
50
+ if (isNaN(checkInDate.getTime())) {
51
+ throw new ReservationValidationError("Invalid check-in date");
52
+ }
53
+
54
+ if (isNaN(checkOutDate.getTime())) {
55
+ throw new ReservationValidationError("Invalid check-out date");
56
+ }
57
+
58
+ // Check-out must be after check-in
59
+ if (checkOutDate <= checkInDate) {
60
+ throw new ReservationValidationError(
61
+ "Check-out date must be after check-in date"
62
+ );
63
+ }
64
+
65
+ // Calculate minimum stay (at least a few hours)
66
+ const minStayHours = 4;
67
+ const stayDuration = checkOutDate.getTime() - checkInDate.getTime();
68
+ const hoursDiff = stayDuration / (1000 * 60 * 60);
69
+
70
+ if (hoursDiff < minStayHours) {
71
+ throw new ReservationValidationError(
72
+ `Minimum stay is ${minStayHours} hours`
73
+ );
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Validate status transition
79
+ * @throws {ReservationValidationError} if transition is not allowed
80
+ */
81
+ function validateStatusTransition(
82
+ currentStatus: ReservationStatus,
83
+ newStatus: ReservationStatus
84
+ ): void {
85
+ const allowedTransitions = STATUS_TRANSITIONS[currentStatus];
86
+
87
+ if (!allowedTransitions.includes(newStatus)) {
88
+ throw new ReservationValidationError(
89
+ `Invalid status transition from '${currentStatus}' to '${newStatus}'. ` +
90
+ `Allowed transitions: ${allowedTransitions.length > 0 ? allowedTransitions.join(", ") : "none (terminal state)"}`
91
+ );
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Input type for creating a reservation
97
+ */
98
+ export interface CreateReservationInput extends CreateAuditFields {
99
+ brandId: string;
100
+ visitId: string;
101
+ confirmationCode: string;
102
+ checkIn: string;
103
+ checkOut: string;
104
+ status?: "confirmed" | "checked_in" | "checked_out" | "cancelled" | "no_show";
105
+ source?: string;
106
+ channel?: string;
107
+ roomType?: string;
108
+ room?: string;
109
+ totalAmount?: number;
110
+ currency?: string;
111
+ nights?: number;
112
+ }
113
+
114
+ /**
115
+ * Input type for updating a reservation
116
+ */
117
+ export interface UpdateReservationInput extends UpdateWithSoftDeleteFields {
118
+ id: string;
119
+ brandId?: string;
120
+ visitId?: string;
121
+ confirmationCode?: string;
122
+ checkIn?: string;
123
+ checkOut?: string;
124
+ status?: "confirmed" | "checked_in" | "checked_out" | "cancelled" | "no_show";
125
+ source?: string;
126
+ channel?: string;
127
+ roomType?: string;
128
+ room?: string;
129
+ totalAmount?: number;
130
+ currency?: string;
131
+ nights?: number;
132
+ }
133
+
134
+ /**
135
+ * Create a new reservation with date validation
136
+ *
137
+ * @throws {ReservationValidationError} if dates are invalid
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * import { createReservation } from '@htlkg/data/mutations';
142
+ * import { generateClient } from '@htlkg/data/client';
143
+ *
144
+ * const client = generateClient<Schema>();
145
+ * const reservation = await createReservation(client, {
146
+ * brandId: 'brand-123',
147
+ * visitId: 'visit-456',
148
+ * confirmationCode: 'ABC123',
149
+ * checkIn: '2024-01-01',
150
+ * checkOut: '2024-01-05',
151
+ * status: 'confirmed'
152
+ * });
153
+ * ```
154
+ */
155
+ export async function createReservation<TClient = any>(
156
+ client: TClient,
157
+ input: CreateReservationInput,
158
+ ): Promise<any | null> {
159
+ try {
160
+ // Validate dates before creating
161
+ validateDates(input.checkIn, input.checkOut);
162
+
163
+ // Calculate nights if not provided
164
+ if (!input.nights) {
165
+ const checkInDate = new Date(input.checkIn);
166
+ const checkOutDate = new Date(input.checkOut);
167
+ const diffTime = checkOutDate.getTime() - checkInDate.getTime();
168
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
169
+ input.nights = diffDays;
170
+ }
171
+
172
+ const { data, errors } = await (client as any).models.Reservation.create(input);
173
+
174
+ if (errors) {
175
+ console.error("[createReservation] GraphQL errors:", errors);
176
+ return null;
177
+ }
178
+
179
+ return data;
180
+ } catch (error) {
181
+ if (error instanceof ReservationValidationError) {
182
+ console.error("[createReservation] Validation error:", error.message);
183
+ throw error;
184
+ }
185
+ console.error("[createReservation] Error creating reservation:", error);
186
+ throw error;
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Update an existing reservation with validation
192
+ *
193
+ * @throws {ReservationValidationError} if dates or status transition is invalid
194
+ *
195
+ * @example
196
+ * ```typescript
197
+ * import { updateReservation } from '@htlkg/data/mutations';
198
+ * import { generateClient } from '@htlkg/data/client';
199
+ *
200
+ * const client = generateClient<Schema>();
201
+ * const reservation = await updateReservation(client, {
202
+ * id: 'reservation-123',
203
+ * status: 'checked_in',
204
+ * room: '201'
205
+ * });
206
+ * ```
207
+ */
208
+ export async function updateReservation<TClient = any>(
209
+ client: TClient,
210
+ input: UpdateReservationInput,
211
+ ): Promise<any | null> {
212
+ try {
213
+ // If both dates are being updated, validate them
214
+ if (input.checkIn && input.checkOut) {
215
+ validateDates(input.checkIn, input.checkOut);
216
+
217
+ // Recalculate nights if dates changed
218
+ const checkInDate = new Date(input.checkIn);
219
+ const checkOutDate = new Date(input.checkOut);
220
+ const diffTime = checkOutDate.getTime() - checkInDate.getTime();
221
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
222
+ input.nights = diffDays;
223
+ }
224
+
225
+ // If status is being updated, validate the transition
226
+ if (input.status) {
227
+ // Get current reservation to check current status
228
+ const { data: currentReservation, errors: getErrors } = await (
229
+ client as any
230
+ ).models.Reservation.get({ id: input.id });
231
+
232
+ if (getErrors || !currentReservation) {
233
+ console.error("[updateReservation] Error fetching current reservation:", getErrors);
234
+ return null;
235
+ }
236
+
237
+ const currentStatus = currentReservation.status as ReservationStatus;
238
+ validateStatusTransition(currentStatus, input.status as ReservationStatus);
239
+ }
240
+
241
+ const { data, errors } = await (client as any).models.Reservation.update(input);
242
+
243
+ if (errors) {
244
+ console.error("[updateReservation] GraphQL errors:", errors);
245
+ return null;
246
+ }
247
+
248
+ return data;
249
+ } catch (error) {
250
+ if (error instanceof ReservationValidationError) {
251
+ console.error("[updateReservation] Validation error:", error.message);
252
+ throw error;
253
+ }
254
+ console.error("[updateReservation] Error updating reservation:", error);
255
+ throw error;
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Soft delete a reservation (sets deletedAt/deletedBy instead of removing)
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * import { softDeleteReservation } from '@htlkg/data/mutations';
265
+ * import { generateClient } from '@htlkg/data/client';
266
+ *
267
+ * const client = generateClient<Schema>();
268
+ * await softDeleteReservation(client, 'reservation-123', 'admin@example.com');
269
+ * ```
270
+ */
271
+ export async function softDeleteReservation<TClient = any>(
272
+ client: TClient,
273
+ id: string,
274
+ deletedBy: string,
275
+ ): Promise<boolean> {
276
+ try {
277
+ const { errors } = await (client as any).models.Reservation.update({
278
+ id,
279
+ deletedAt: new Date().toISOString(),
280
+ deletedBy,
281
+ });
282
+
283
+ if (errors) {
284
+ console.error("[softDeleteReservation] GraphQL errors:", errors);
285
+ return false;
286
+ }
287
+
288
+ return true;
289
+ } catch (error) {
290
+ console.error("[softDeleteReservation] Error soft-deleting reservation:", error);
291
+ throw error;
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Restore a soft-deleted reservation
297
+ * Checks retention period before allowing restoration
298
+ *
299
+ * @example
300
+ * ```typescript
301
+ * import { restoreReservation } from '@htlkg/data/mutations';
302
+ * import { generateClient } from '@htlkg/data/client';
303
+ *
304
+ * const client = generateClient<Schema>();
305
+ * await restoreReservation(client, 'reservation-123');
306
+ * // Or with custom retention days:
307
+ * await restoreReservation(client, 'reservation-123', 60);
308
+ * ```
309
+ */
310
+ export async function restoreReservation<TClient = any>(
311
+ client: TClient,
312
+ id: string,
313
+ retentionDays: number = DEFAULT_SOFT_DELETE_RETENTION_DAYS,
314
+ ): Promise<{ success: boolean; error?: string }> {
315
+ try {
316
+ // First, get the reservation to check its deletedAt timestamp
317
+ const { data: reservation, errors: getErrors } = await (
318
+ client as any
319
+ ).models.Reservation.get({ id });
320
+
321
+ if (getErrors || !reservation) {
322
+ console.error("[restoreReservation] Error fetching reservation:", getErrors);
323
+ return { success: false, error: "Reservation not found" };
324
+ }
325
+
326
+ // Check if restoration is allowed based on retention period
327
+ const eligibility = checkRestoreEligibility(reservation.deletedAt, retentionDays);
328
+
329
+ if (!eligibility.canRestore) {
330
+ const errorMsg = `Cannot restore reservation. Retention period of ${retentionDays} days has expired. Item was deleted ${eligibility.daysExpired} days ago.`;
331
+ console.error("[restoreReservation]", errorMsg);
332
+ return { success: false, error: errorMsg };
333
+ }
334
+
335
+ const { errors } = await (client as any).models.Reservation.update({
336
+ id,
337
+ deletedAt: null,
338
+ deletedBy: null,
339
+ });
340
+
341
+ if (errors) {
342
+ console.error("[restoreReservation] GraphQL errors:", errors);
343
+ return { success: false, error: "Failed to restore reservation" };
344
+ }
345
+
346
+ return { success: true };
347
+ } catch (error) {
348
+ console.error("[restoreReservation] Error restoring reservation:", error);
349
+ throw error;
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Hard delete a reservation (permanently removes from database)
355
+ * Use with caution - prefer softDeleteReservation for recoverable deletion
356
+ *
357
+ * @example
358
+ * ```typescript
359
+ * import { deleteReservation } from '@htlkg/data/mutations';
360
+ * import { generateClient } from '@htlkg/data/client';
361
+ *
362
+ * const client = generateClient<Schema>();
363
+ * await deleteReservation(client, 'reservation-123');
364
+ * ```
365
+ */
366
+ export async function deleteReservation<TClient = any>(
367
+ client: TClient,
368
+ id: string,
369
+ ): Promise<boolean> {
370
+ try {
371
+ const { errors } = await (client as any).models.Reservation.delete({ id });
372
+
373
+ if (errors) {
374
+ console.error("[deleteReservation] GraphQL errors:", errors);
375
+ return false;
376
+ }
377
+
378
+ return true;
379
+ } catch (error) {
380
+ console.error("[deleteReservation] Error deleting reservation:", error);
381
+ throw error;
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Update reservation status with validation
387
+ *
388
+ * Validates that the status transition is allowed based on current status.
389
+ * Status transition rules:
390
+ * - confirmed → checked_in, cancelled, no_show
391
+ * - checked_in → checked_out, cancelled
392
+ * - checked_out → (terminal state, no transitions)
393
+ * - cancelled → (terminal state, no transitions)
394
+ * - no_show → (terminal state, no transitions)
395
+ *
396
+ * @throws {ReservationValidationError} if status transition is invalid
397
+ *
398
+ * @example
399
+ * ```typescript
400
+ * import { updateReservationStatus } from '@htlkg/data/mutations';
401
+ * import { generateClient } from '@htlkg/data/client';
402
+ *
403
+ * const client = generateClient<Schema>();
404
+ * const reservation = await updateReservationStatus(
405
+ * client,
406
+ * 'reservation-123',
407
+ * 'checked_in'
408
+ * );
409
+ * ```
410
+ */
411
+ export async function updateReservationStatus<TClient = any>(
412
+ client: TClient,
413
+ id: string,
414
+ newStatus: ReservationStatus,
415
+ ): Promise<any | null> {
416
+ try {
417
+ // Get current reservation to check current status
418
+ const { data: currentReservation, errors: getErrors } = await (
419
+ client as any
420
+ ).models.Reservation.get({ id });
421
+
422
+ if (getErrors || !currentReservation) {
423
+ console.error("[updateReservationStatus] Error fetching reservation:", getErrors);
424
+ return null;
425
+ }
426
+
427
+ const currentStatus = currentReservation.status as ReservationStatus;
428
+
429
+ // Validate status transition
430
+ validateStatusTransition(currentStatus, newStatus);
431
+
432
+ // Perform the update
433
+ const { data, errors } = await (client as any).models.Reservation.update({
434
+ id,
435
+ status: newStatus,
436
+ });
437
+
438
+ if (errors) {
439
+ console.error("[updateReservationStatus] GraphQL errors:", errors);
440
+ return null;
441
+ }
442
+
443
+ return data;
444
+ } catch (error) {
445
+ if (error instanceof ReservationValidationError) {
446
+ console.error("[updateReservationStatus] Validation error:", error.message);
447
+ throw error;
448
+ }
449
+ console.error("[updateReservationStatus] Error updating status:", error);
450
+ throw error;
451
+ }
452
+ }