@voyant-travel/bookings 0.119.3
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 +201 -0
- package/README.md +84 -0
- package/dist/action-ledger-capabilities.d.ts +306 -0
- package/dist/action-ledger-capabilities.d.ts.map +1 -0
- package/dist/action-ledger-capabilities.js +92 -0
- package/dist/action-ledger-drift-remediation.d.ts +30 -0
- package/dist/action-ledger-drift-remediation.d.ts.map +1 -0
- package/dist/action-ledger-drift-remediation.js +85 -0
- package/dist/action-ledger-drift.d.ts +29 -0
- package/dist/action-ledger-drift.d.ts.map +1 -0
- package/dist/action-ledger-drift.js +222 -0
- package/dist/availability-ref.d.ts +418 -0
- package/dist/availability-ref.d.ts.map +1 -0
- package/dist/availability-ref.js +28 -0
- package/dist/checkout-capability.d.ts +30 -0
- package/dist/checkout-capability.d.ts.map +1 -0
- package/dist/checkout-capability.js +112 -0
- package/dist/extensions/suppliers.d.ts +3 -0
- package/dist/extensions/suppliers.d.ts.map +1 -0
- package/dist/extensions/suppliers.js +109 -0
- package/dist/extras/product-extra-ref.d.ts +343 -0
- package/dist/extras/product-extra-ref.d.ts.map +1 -0
- package/dist/extras/product-extra-ref.js +31 -0
- package/dist/extras/routes.d.ts +515 -0
- package/dist/extras/routes.d.ts.map +1 -0
- package/dist/extras/routes.js +60 -0
- package/dist/extras/schema.d.ts +660 -0
- package/dist/extras/schema.d.ts.map +1 -0
- package/dist/extras/schema.js +102 -0
- package/dist/extras/service-manifest.d.ts +213 -0
- package/dist/extras/service-manifest.d.ts.map +1 -0
- package/dist/extras/service-manifest.js +329 -0
- package/dist/extras/service.d.ts +341 -0
- package/dist/extras/service.d.ts.map +1 -0
- package/dist/extras/service.js +52 -0
- package/dist/extras/validation.d.ts +223 -0
- package/dist/extras/validation.d.ts.map +1 -0
- package/dist/extras/validation.js +101 -0
- package/dist/extras.d.ts +12 -0
- package/dist/extras.d.ts.map +1 -0
- package/dist/extras.js +12 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/markets-ref.d.ts +151 -0
- package/dist/markets-ref.d.ts.map +1 -0
- package/dist/markets-ref.js +19 -0
- package/dist/pii-redaction.d.ts +88 -0
- package/dist/pii-redaction.d.ts.map +1 -0
- package/dist/pii-redaction.js +121 -0
- package/dist/pii.d.ts +63 -0
- package/dist/pii.d.ts.map +1 -0
- package/dist/pii.js +242 -0
- package/dist/pricing-assignment/age.d.ts +33 -0
- package/dist/pricing-assignment/age.d.ts.map +1 -0
- package/dist/pricing-assignment/age.js +109 -0
- package/dist/pricing-assignment/draft.d.ts +69 -0
- package/dist/pricing-assignment/draft.d.ts.map +1 -0
- package/dist/pricing-assignment/draft.js +284 -0
- package/dist/pricing-assignment/types.d.ts +113 -0
- package/dist/pricing-assignment/types.d.ts.map +1 -0
- package/dist/pricing-assignment/types.js +30 -0
- package/dist/pricing-assignment/unit-helpers.d.ts +11 -0
- package/dist/pricing-assignment/unit-helpers.d.ts.map +1 -0
- package/dist/pricing-assignment/unit-helpers.js +19 -0
- package/dist/pricing-assignment/verify.d.ts +67 -0
- package/dist/pricing-assignment/verify.d.ts.map +1 -0
- package/dist/pricing-assignment/verify.js +121 -0
- package/dist/pricing-assignment.d.ts +5 -0
- package/dist/pricing-assignment.d.ts.map +1 -0
- package/dist/pricing-assignment.js +4 -0
- package/dist/pricing-ref.d.ts +1116 -0
- package/dist/pricing-ref.d.ts.map +1 -0
- package/dist/pricing-ref.js +77 -0
- package/dist/products-ref.d.ts +1472 -0
- package/dist/products-ref.d.ts.map +1 -0
- package/dist/products-ref.js +110 -0
- package/dist/requirements/index.d.ts +19 -0
- package/dist/requirements/index.d.ts.map +1 -0
- package/dist/requirements/index.js +24 -0
- package/dist/requirements/routes-public.d.ts +112 -0
- package/dist/requirements/routes-public.d.ts.map +1 -0
- package/dist/requirements/routes-public.js +15 -0
- package/dist/requirements/routes.d.ts +1211 -0
- package/dist/requirements/routes.d.ts.map +1 -0
- package/dist/requirements/routes.js +221 -0
- package/dist/requirements/schema.d.ts +1504 -0
- package/dist/requirements/schema.d.ts.map +1 -0
- package/dist/requirements/schema.js +260 -0
- package/dist/requirements/service-answers.d.ts +71 -0
- package/dist/requirements/service-answers.d.ts.map +1 -0
- package/dist/requirements/service-answers.js +47 -0
- package/dist/requirements/service-public.d.ts +35 -0
- package/dist/requirements/service-public.d.ts.map +1 -0
- package/dist/requirements/service-public.js +56 -0
- package/dist/requirements/service-questions.d.ts +391 -0
- package/dist/requirements/service-questions.d.ts.map +1 -0
- package/dist/requirements/service-questions.js +319 -0
- package/dist/requirements/service-shared.d.ts +37 -0
- package/dist/requirements/service-shared.d.ts.map +1 -0
- package/dist/requirements/service-shared.js +4 -0
- package/dist/requirements/service.d.ts +47 -0
- package/dist/requirements/service.d.ts.map +1 -0
- package/dist/requirements/service.js +46 -0
- package/dist/requirements/validation.d.ts +649 -0
- package/dist/requirements/validation.d.ts.map +1 -0
- package/dist/requirements/validation.js +221 -0
- package/dist/reservation-plans.d.ts +78 -0
- package/dist/reservation-plans.d.ts.map +1 -0
- package/dist/reservation-plans.js +102 -0
- package/dist/route-runtime.d.ts +87 -0
- package/dist/route-runtime.d.ts.map +1 -0
- package/dist/route-runtime.js +26 -0
- package/dist/routes-admin.d.ts +2798 -0
- package/dist/routes-admin.d.ts.map +1 -0
- package/dist/routes-admin.js +2065 -0
- package/dist/routes-groups.d.ts +405 -0
- package/dist/routes-groups.d.ts.map +1 -0
- package/dist/routes-groups.js +62 -0
- package/dist/routes-public.d.ts +685 -0
- package/dist/routes-public.d.ts.map +1 -0
- package/dist/routes-public.js +257 -0
- package/dist/routes-shared.d.ts +60 -0
- package/dist/routes-shared.d.ts.map +1 -0
- package/dist/routes-shared.js +10 -0
- package/dist/routes.d.ts +2 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +1 -0
- package/dist/schema/travel-details.d.ts +404 -0
- package/dist/schema/travel-details.d.ts.map +1 -0
- package/dist/schema/travel-details.js +158 -0
- package/dist/schema-core.d.ts +1248 -0
- package/dist/schema-core.d.ts.map +1 -0
- package/dist/schema-core.js +120 -0
- package/dist/schema-groups.d.ts +261 -0
- package/dist/schema-groups.d.ts.map +1 -0
- package/dist/schema-groups.js +38 -0
- package/dist/schema-items.d.ts +1363 -0
- package/dist/schema-items.d.ts.map +1 -0
- package/dist/schema-items.js +160 -0
- package/dist/schema-operations.d.ts +800 -0
- package/dist/schema-operations.d.ts.map +1 -0
- package/dist/schema-operations.js +95 -0
- package/dist/schema-origin.d.ts +328 -0
- package/dist/schema-origin.d.ts.map +1 -0
- package/dist/schema-origin.js +45 -0
- package/dist/schema-relations.d.ts +87 -0
- package/dist/schema-relations.d.ts.map +1 -0
- package/dist/schema-relations.js +132 -0
- package/dist/schema-shared.d.ts +20 -0
- package/dist/schema-shared.d.ts.map +1 -0
- package/dist/schema-shared.js +150 -0
- package/dist/schema-staff.d.ts +267 -0
- package/dist/schema-staff.d.ts.map +1 -0
- package/dist/schema-staff.js +31 -0
- package/dist/schema.d.ts +12 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +11 -0
- package/dist/service-core.d.ts +6233 -0
- package/dist/service-core.d.ts.map +1 -0
- package/dist/service-core.js +3776 -0
- package/dist/service-groups.d.ts +65 -0
- package/dist/service-groups.d.ts.map +1 -0
- package/dist/service-groups.js +195 -0
- package/dist/service-origin.d.ts +55 -0
- package/dist/service-origin.d.ts.map +1 -0
- package/dist/service-origin.js +135 -0
- package/dist/service-public-core.d.ts +907 -0
- package/dist/service-public-core.d.ts.map +1 -0
- package/dist/service-public-core.js +1182 -0
- package/dist/service-public.d.ts +2 -0
- package/dist/service-public.d.ts.map +1 -0
- package/dist/service-public.js +1 -0
- package/dist/service.d.ts +2 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +1 -0
- package/dist/state-machine.d.ts +38 -0
- package/dist/state-machine.d.ts.map +1 -0
- package/dist/state-machine.js +48 -0
- package/dist/status-dispatch.d.ts +32 -0
- package/dist/status-dispatch.d.ts.map +1 -0
- package/dist/status-dispatch.js +56 -0
- package/dist/tasks/expire-stale-holds.d.ts +13 -0
- package/dist/tasks/expire-stale-holds.d.ts.map +1 -0
- package/dist/tasks/expire-stale-holds.js +7 -0
- package/dist/tasks/index.d.ts +2 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +1 -0
- package/dist/transactions-ref.d.ts +3189 -0
- package/dist/transactions-ref.d.ts.map +1 -0
- package/dist/transactions-ref.js +207 -0
- package/dist/validation-public.d.ts +2 -0
- package/dist/validation-public.d.ts.map +1 -0
- package/dist/validation-public.js +1 -0
- package/dist/validation-shared.d.ts +2 -0
- package/dist/validation-shared.d.ts.map +1 -0
- package/dist/validation-shared.js +1 -0
- package/dist/validation.d.ts +2 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +1 -0
- package/dist/workflows/refund-booking.d.ts +87 -0
- package/dist/workflows/refund-booking.d.ts.map +1 -0
- package/dist/workflows/refund-booking.js +216 -0
- package/package.json +159 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AnyDrizzleDb } from "@voyant-travel/db";
|
|
2
|
+
import type { BookingActionLedgerDriftCheck, CheckBookingActionLedgerDriftInput, CheckBookingActionLedgerDriftResult } from "./action-ledger-drift.js";
|
|
3
|
+
export interface BookingActionLedgerDriftRemediationItem {
|
|
4
|
+
check: BookingActionLedgerDriftCheck;
|
|
5
|
+
missingCount: number;
|
|
6
|
+
sampleTargetIds: string[];
|
|
7
|
+
sampleTruncated: boolean;
|
|
8
|
+
recommendedBackfillActionName: string;
|
|
9
|
+
targetType: string;
|
|
10
|
+
targetIdKind: string;
|
|
11
|
+
mode: "dry_run";
|
|
12
|
+
note: string;
|
|
13
|
+
}
|
|
14
|
+
export interface BookingActionLedgerDriftRemediationPlan {
|
|
15
|
+
mode: "dry_run";
|
|
16
|
+
generatedAt: string;
|
|
17
|
+
createdAtFrom: string | null;
|
|
18
|
+
sampleLimit: number | null;
|
|
19
|
+
totalMissingCount: number;
|
|
20
|
+
items: BookingActionLedgerDriftRemediationItem[];
|
|
21
|
+
}
|
|
22
|
+
export interface BuildBookingActionLedgerDriftRemediationPlanInput {
|
|
23
|
+
drift: CheckBookingActionLedgerDriftResult;
|
|
24
|
+
createdAtFrom?: CheckBookingActionLedgerDriftInput["createdAtFrom"];
|
|
25
|
+
sampleLimit?: number | null;
|
|
26
|
+
generatedAt?: Date | string;
|
|
27
|
+
}
|
|
28
|
+
export declare function planBookingActionLedgerDriftRemediation(db: AnyDrizzleDb, input?: CheckBookingActionLedgerDriftInput): Promise<BookingActionLedgerDriftRemediationPlan>;
|
|
29
|
+
export declare function buildBookingActionLedgerDriftRemediationPlan({ drift, createdAtFrom, sampleLimit, generatedAt, }: BuildBookingActionLedgerDriftRemediationPlanInput): BookingActionLedgerDriftRemediationPlan;
|
|
30
|
+
//# sourceMappingURL=action-ledger-drift-remediation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-ledger-drift-remediation.d.ts","sourceRoot":"","sources":["../src/action-ledger-drift-remediation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EACV,6BAA6B,EAC7B,kCAAkC,EAClC,mCAAmC,EACpC,MAAM,0BAA0B,CAAA;AAgDjC,MAAM,WAAW,uCAAuC;IACtD,KAAK,EAAE,6BAA6B,CAAA;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,eAAe,EAAE,OAAO,CAAA;IACxB,6BAA6B,EAAE,MAAM,CAAA;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,uCAAuC;IACtD,IAAI,EAAE,SAAS,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,iBAAiB,EAAE,MAAM,CAAA;IACzB,KAAK,EAAE,uCAAuC,EAAE,CAAA;CACjD;AAED,MAAM,WAAW,iDAAiD;IAChE,KAAK,EAAE,mCAAmC,CAAA;IAC1C,aAAa,CAAC,EAAE,kCAAkC,CAAC,eAAe,CAAC,CAAA;IACnE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;CAC5B;AAED,wBAAsB,uCAAuC,CAC3D,EAAE,EAAE,YAAY,EAChB,KAAK,GAAE,kCAAuC,GAC7C,OAAO,CAAC,uCAAuC,CAAC,CAOlD;AAED,wBAAgB,4CAA4C,CAAC,EAC3D,KAAK,EACL,aAAa,EACb,WAAW,EACX,WAAwB,GACzB,EAAE,iDAAiD,GAAG,uCAAuC,CA+B7F"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { checkBookingActionLedgerDrift } from "./action-ledger-drift.js";
|
|
2
|
+
const BACKFILL_ACTION_BY_CHECK = {
|
|
3
|
+
booking_confirmed: {
|
|
4
|
+
actionName: "booking.status.confirm",
|
|
5
|
+
targetType: "booking",
|
|
6
|
+
targetIdKind: "booking_id",
|
|
7
|
+
},
|
|
8
|
+
booking_expired: {
|
|
9
|
+
actionName: "booking.status.expire",
|
|
10
|
+
targetType: "booking",
|
|
11
|
+
targetIdKind: "booking_id",
|
|
12
|
+
},
|
|
13
|
+
booking_cancelled: {
|
|
14
|
+
actionName: "booking.status.cancel",
|
|
15
|
+
targetType: "booking",
|
|
16
|
+
targetIdKind: "booking_id",
|
|
17
|
+
},
|
|
18
|
+
booking_completed: {
|
|
19
|
+
actionName: "booking.status.complete",
|
|
20
|
+
targetType: "booking",
|
|
21
|
+
targetIdKind: "booking_id",
|
|
22
|
+
},
|
|
23
|
+
booking_item: {
|
|
24
|
+
actionName: "booking.item.create",
|
|
25
|
+
targetType: "booking_item",
|
|
26
|
+
targetIdKind: "booking_item_id",
|
|
27
|
+
},
|
|
28
|
+
booking_traveler: {
|
|
29
|
+
actionName: "booking.traveler.create",
|
|
30
|
+
targetType: "booking_traveler",
|
|
31
|
+
targetIdKind: "booking_traveler_id",
|
|
32
|
+
},
|
|
33
|
+
booking_traveler_travel_details: {
|
|
34
|
+
actionName: "booking.traveler_travel_details.update",
|
|
35
|
+
targetType: "booking_traveler",
|
|
36
|
+
targetIdKind: "booking_traveler_id",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export async function planBookingActionLedgerDriftRemediation(db, input = {}) {
|
|
40
|
+
const drift = await checkBookingActionLedgerDrift(db, input);
|
|
41
|
+
return buildBookingActionLedgerDriftRemediationPlan({
|
|
42
|
+
drift,
|
|
43
|
+
createdAtFrom: input.createdAtFrom,
|
|
44
|
+
sampleLimit: input.sampleLimit,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export function buildBookingActionLedgerDriftRemediationPlan({ drift, createdAtFrom, sampleLimit, generatedAt = new Date(), }) {
|
|
48
|
+
const generatedAtDate = generatedAt instanceof Date ? generatedAt : new Date(generatedAt);
|
|
49
|
+
if (Number.isNaN(generatedAtDate.getTime())) {
|
|
50
|
+
throw new Error("generatedAt must be a valid date");
|
|
51
|
+
}
|
|
52
|
+
const items = drift.rows
|
|
53
|
+
.filter((row) => row.missingCount > 0)
|
|
54
|
+
.map((row) => {
|
|
55
|
+
const action = BACKFILL_ACTION_BY_CHECK[row.check];
|
|
56
|
+
return {
|
|
57
|
+
check: row.check,
|
|
58
|
+
missingCount: row.missingCount,
|
|
59
|
+
sampleTargetIds: row.sampleIds,
|
|
60
|
+
sampleTruncated: row.missingCount > row.sampleIds.length,
|
|
61
|
+
recommendedBackfillActionName: action.actionName,
|
|
62
|
+
targetType: action.targetType,
|
|
63
|
+
targetIdKind: action.targetIdKind,
|
|
64
|
+
mode: "dry_run",
|
|
65
|
+
note: "Dry run only. Review source rows and choose an explicit historical actor before writing backfill ledger entries.",
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
mode: "dry_run",
|
|
70
|
+
generatedAt: generatedAtDate.toISOString(),
|
|
71
|
+
createdAtFrom: normalizeNullableDate(createdAtFrom),
|
|
72
|
+
sampleLimit: sampleLimit ?? null,
|
|
73
|
+
totalMissingCount: items.reduce((sum, item) => sum + item.missingCount, 0),
|
|
74
|
+
items,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function normalizeNullableDate(value) {
|
|
78
|
+
if (!value)
|
|
79
|
+
return null;
|
|
80
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
81
|
+
if (Number.isNaN(date.getTime())) {
|
|
82
|
+
throw new Error("createdAtFrom must be a valid date");
|
|
83
|
+
}
|
|
84
|
+
return date.toISOString();
|
|
85
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AnyDrizzleDb } from "@voyant-travel/db";
|
|
2
|
+
import { type SQL } from "drizzle-orm";
|
|
3
|
+
export type BookingActionLedgerDriftCheck = "booking_confirmed" | "booking_expired" | "booking_cancelled" | "booking_completed" | "booking_item" | "booking_traveler" | "booking_traveler_travel_details";
|
|
4
|
+
export interface CheckBookingActionLedgerDriftInput {
|
|
5
|
+
createdAtFrom?: Date | string | null;
|
|
6
|
+
sampleLimit?: number | null;
|
|
7
|
+
}
|
|
8
|
+
export interface BookingActionLedgerDriftRow {
|
|
9
|
+
check: BookingActionLedgerDriftCheck;
|
|
10
|
+
missingCount: number;
|
|
11
|
+
sampleIds: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface CheckBookingActionLedgerDriftResult {
|
|
14
|
+
ok: boolean;
|
|
15
|
+
rows: BookingActionLedgerDriftRow[];
|
|
16
|
+
}
|
|
17
|
+
interface BookingActionLedgerDriftQueryRow extends Record<string, unknown> {
|
|
18
|
+
check: BookingActionLedgerDriftCheck;
|
|
19
|
+
missing_count: number | string;
|
|
20
|
+
sample_ids: string[] | null;
|
|
21
|
+
}
|
|
22
|
+
export declare function buildBookingActionLedgerDriftQueries(input?: CheckBookingActionLedgerDriftInput): Record<BookingActionLedgerDriftCheck, SQL<BookingActionLedgerDriftQueryRow>>;
|
|
23
|
+
export declare function checkBookingActionLedgerDrift(db: AnyDrizzleDb, input?: CheckBookingActionLedgerDriftInput): Promise<CheckBookingActionLedgerDriftResult>;
|
|
24
|
+
declare function normalizeRow(row: BookingActionLedgerDriftQueryRow): BookingActionLedgerDriftRow;
|
|
25
|
+
export declare const __test__: {
|
|
26
|
+
normalizeRow: typeof normalizeRow;
|
|
27
|
+
};
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=action-ledger-drift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-ledger-drift.d.ts","sourceRoot":"","sources":["../src/action-ledger-drift.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,KAAK,GAAG,EAAwB,MAAM,aAAa,CAAA;AAsB5D,MAAM,MAAM,6BAA6B,GACrC,mBAAmB,GACnB,iBAAiB,GACjB,mBAAmB,GACnB,mBAAmB,GACnB,cAAc,GACd,kBAAkB,GAClB,iCAAiC,CAAA;AAErC,MAAM,WAAW,kCAAkC;IACjD,aAAa,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IACpC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,6BAA6B,CAAA;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,mCAAmC;IAClD,EAAE,EAAE,OAAO,CAAA;IACX,IAAI,EAAE,2BAA2B,EAAE,CAAA;CACpC;AAED,UAAU,gCAAiC,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACxE,KAAK,EAAE,6BAA6B,CAAA;IACpC,aAAa,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CAC5B;AAED,wBAAgB,oCAAoC,CAClD,KAAK,GAAE,kCAAuC,GAC7C,MAAM,CAAC,6BAA6B,EAAE,GAAG,CAAC,gCAAgC,CAAC,CAAC,CA8H9E;AAED,wBAAsB,6BAA6B,CACjD,EAAE,EAAE,YAAY,EAChB,KAAK,GAAE,kCAAuC,GAC7C,OAAO,CAAC,mCAAmC,CAAC,CAmB9C;AA+DD,iBAAS,YAAY,CAAC,GAAG,EAAE,gCAAgC,GAAG,2BAA2B,CAMxF;AAED,eAAO,MAAM,QAAQ;;CAEpB,CAAA"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { actionLedgerEntries } from "@voyant-travel/action-ledger/schema";
|
|
2
|
+
import { sql } from "drizzle-orm";
|
|
3
|
+
import { bookingTravelerTravelDetails } from "./schema/travel-details.js";
|
|
4
|
+
import { bookings, bookingTravelers } from "./schema-core.js";
|
|
5
|
+
import { bookingItems } from "./schema-items.js";
|
|
6
|
+
const DEFAULT_SAMPLE_LIMIT = 20;
|
|
7
|
+
const MAX_SAMPLE_LIMIT = 100;
|
|
8
|
+
const BOOKING_CONFIRM_ACTION_NAME = "booking.status.confirm";
|
|
9
|
+
const BOOKING_EXPIRE_ACTION_NAME = "booking.status.expire";
|
|
10
|
+
const BOOKING_CANCEL_ACTION_NAME = "booking.status.cancel";
|
|
11
|
+
const BOOKING_COMPLETE_ACTION_NAME = "booking.status.complete";
|
|
12
|
+
const BOOKING_TRAVELER_CREATE_ACTION_NAMES = [
|
|
13
|
+
"booking.traveler.create",
|
|
14
|
+
"booking.traveler_with_travel_details.create",
|
|
15
|
+
];
|
|
16
|
+
const BOOKING_TRAVELER_TRAVEL_DETAILS_ACTION_NAMES = [
|
|
17
|
+
"booking.traveler_with_travel_details.create",
|
|
18
|
+
"booking.traveler_travel_details.update",
|
|
19
|
+
];
|
|
20
|
+
const BOOKING_ITEM_CREATE_ACTION_NAME = "booking.item.create";
|
|
21
|
+
export function buildBookingActionLedgerDriftQueries(input = {}) {
|
|
22
|
+
const sampleLimit = normalizeSampleLimit(input.sampleLimit);
|
|
23
|
+
return {
|
|
24
|
+
booking_confirmed: buildBookingStatusDriftQuery({
|
|
25
|
+
check: "booking_confirmed",
|
|
26
|
+
actionName: BOOKING_CONFIRM_ACTION_NAME,
|
|
27
|
+
timestampColumn: bookings.confirmedAt,
|
|
28
|
+
input,
|
|
29
|
+
sampleLimit,
|
|
30
|
+
}),
|
|
31
|
+
booking_expired: buildBookingStatusDriftQuery({
|
|
32
|
+
check: "booking_expired",
|
|
33
|
+
actionName: BOOKING_EXPIRE_ACTION_NAME,
|
|
34
|
+
timestampColumn: bookings.expiredAt,
|
|
35
|
+
input,
|
|
36
|
+
sampleLimit,
|
|
37
|
+
}),
|
|
38
|
+
booking_cancelled: buildBookingStatusDriftQuery({
|
|
39
|
+
check: "booking_cancelled",
|
|
40
|
+
actionName: BOOKING_CANCEL_ACTION_NAME,
|
|
41
|
+
timestampColumn: bookings.cancelledAt,
|
|
42
|
+
input,
|
|
43
|
+
sampleLimit,
|
|
44
|
+
}),
|
|
45
|
+
booking_completed: buildBookingStatusDriftQuery({
|
|
46
|
+
check: "booking_completed",
|
|
47
|
+
actionName: BOOKING_COMPLETE_ACTION_NAME,
|
|
48
|
+
timestampColumn: bookings.completedAt,
|
|
49
|
+
input,
|
|
50
|
+
sampleLimit,
|
|
51
|
+
}),
|
|
52
|
+
booking_item: sql `
|
|
53
|
+
SELECT
|
|
54
|
+
'booking_item' AS check,
|
|
55
|
+
count(*)::int AS missing_count,
|
|
56
|
+
coalesce(
|
|
57
|
+
array_agg(candidate_id ORDER BY created_at DESC, candidate_id DESC)
|
|
58
|
+
FILTER (WHERE sample_ordinal <= ${sampleLimit}),
|
|
59
|
+
ARRAY[]::text[]
|
|
60
|
+
) AS sample_ids
|
|
61
|
+
FROM (
|
|
62
|
+
SELECT
|
|
63
|
+
${bookingItems.id} AS candidate_id,
|
|
64
|
+
${bookingItems.createdAt} AS created_at,
|
|
65
|
+
row_number() OVER (
|
|
66
|
+
ORDER BY ${bookingItems.createdAt} DESC, ${bookingItems.id} DESC
|
|
67
|
+
) AS sample_ordinal
|
|
68
|
+
FROM ${bookingItems}
|
|
69
|
+
WHERE 1 = 1
|
|
70
|
+
${buildCreatedAtCondition(bookingItems.createdAt, input.createdAtFrom)}
|
|
71
|
+
AND NOT EXISTS (
|
|
72
|
+
SELECT 1
|
|
73
|
+
FROM ${actionLedgerEntries}
|
|
74
|
+
WHERE ${actionLedgerEntries.actionName} = ${BOOKING_ITEM_CREATE_ACTION_NAME}
|
|
75
|
+
AND ${actionLedgerEntries.targetType} = ${"booking_item"}
|
|
76
|
+
AND ${actionLedgerEntries.targetId} = ${bookingItems.id}
|
|
77
|
+
)
|
|
78
|
+
) missing
|
|
79
|
+
`,
|
|
80
|
+
booking_traveler: sql `
|
|
81
|
+
SELECT
|
|
82
|
+
'booking_traveler' AS check,
|
|
83
|
+
count(*)::int AS missing_count,
|
|
84
|
+
coalesce(
|
|
85
|
+
array_agg(candidate_id ORDER BY created_at DESC, candidate_id DESC)
|
|
86
|
+
FILTER (WHERE sample_ordinal <= ${sampleLimit}),
|
|
87
|
+
ARRAY[]::text[]
|
|
88
|
+
) AS sample_ids
|
|
89
|
+
FROM (
|
|
90
|
+
SELECT
|
|
91
|
+
${bookingTravelers.id} AS candidate_id,
|
|
92
|
+
${bookingTravelers.createdAt} AS created_at,
|
|
93
|
+
row_number() OVER (
|
|
94
|
+
ORDER BY ${bookingTravelers.createdAt} DESC, ${bookingTravelers.id} DESC
|
|
95
|
+
) AS sample_ordinal
|
|
96
|
+
FROM ${bookingTravelers}
|
|
97
|
+
WHERE 1 = 1
|
|
98
|
+
${buildCreatedAtCondition(bookingTravelers.createdAt, input.createdAtFrom)}
|
|
99
|
+
AND NOT EXISTS (
|
|
100
|
+
SELECT 1
|
|
101
|
+
FROM ${actionLedgerEntries}
|
|
102
|
+
WHERE ${actionLedgerEntries.actionName} IN (${sql.join(
|
|
103
|
+
// agent-quality: raw-sql reviewed -- owner: bookings; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
104
|
+
BOOKING_TRAVELER_CREATE_ACTION_NAMES.map((actionName) => sql `${actionName}`), sql `, `)})
|
|
105
|
+
AND ${actionLedgerEntries.targetType} = ${"booking_traveler"}
|
|
106
|
+
AND ${actionLedgerEntries.targetId} = ${bookingTravelers.id}
|
|
107
|
+
)
|
|
108
|
+
) missing
|
|
109
|
+
`,
|
|
110
|
+
booking_traveler_travel_details: sql `
|
|
111
|
+
SELECT
|
|
112
|
+
'booking_traveler_travel_details' AS check,
|
|
113
|
+
count(*)::int AS missing_count,
|
|
114
|
+
coalesce(
|
|
115
|
+
array_agg(candidate_id ORDER BY created_at DESC, candidate_id DESC)
|
|
116
|
+
FILTER (WHERE sample_ordinal <= ${sampleLimit}),
|
|
117
|
+
ARRAY[]::text[]
|
|
118
|
+
) AS sample_ids
|
|
119
|
+
FROM (
|
|
120
|
+
SELECT
|
|
121
|
+
${bookingTravelerTravelDetails.travelerId} AS candidate_id,
|
|
122
|
+
${bookingTravelerTravelDetails.createdAt} AS created_at,
|
|
123
|
+
row_number() OVER (
|
|
124
|
+
ORDER BY ${bookingTravelerTravelDetails.createdAt} DESC,
|
|
125
|
+
${bookingTravelerTravelDetails.travelerId} DESC
|
|
126
|
+
) AS sample_ordinal
|
|
127
|
+
FROM ${bookingTravelerTravelDetails}
|
|
128
|
+
WHERE 1 = 1
|
|
129
|
+
${buildCreatedAtCondition(bookingTravelerTravelDetails.createdAt, input.createdAtFrom)}
|
|
130
|
+
AND NOT EXISTS (
|
|
131
|
+
SELECT 1
|
|
132
|
+
FROM ${actionLedgerEntries}
|
|
133
|
+
WHERE ${actionLedgerEntries.actionName} IN (${sql.join(
|
|
134
|
+
// agent-quality: raw-sql reviewed -- owner: bookings; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
135
|
+
BOOKING_TRAVELER_TRAVEL_DETAILS_ACTION_NAMES.map((actionName) => sql `${actionName}`), sql `, `)})
|
|
136
|
+
AND ${actionLedgerEntries.targetType} = ${"booking_traveler"}
|
|
137
|
+
AND ${actionLedgerEntries.targetId} = ${bookingTravelerTravelDetails.travelerId}
|
|
138
|
+
)
|
|
139
|
+
) missing
|
|
140
|
+
`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export async function checkBookingActionLedgerDrift(db, input = {}) {
|
|
144
|
+
const queries = buildBookingActionLedgerDriftQueries(input);
|
|
145
|
+
const results = await Promise.all([
|
|
146
|
+
db.execute(queries.booking_confirmed),
|
|
147
|
+
db.execute(queries.booking_expired),
|
|
148
|
+
db.execute(queries.booking_cancelled),
|
|
149
|
+
db.execute(queries.booking_completed),
|
|
150
|
+
db.execute(queries.booking_item),
|
|
151
|
+
db.execute(queries.booking_traveler),
|
|
152
|
+
db.execute(queries.booking_traveler_travel_details),
|
|
153
|
+
]);
|
|
154
|
+
const rows = results
|
|
155
|
+
.flatMap((result) => extractRows(result))
|
|
156
|
+
.map((row) => normalizeRow(row));
|
|
157
|
+
return {
|
|
158
|
+
ok: rows.every((row) => row.missingCount === 0),
|
|
159
|
+
rows,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function buildBookingStatusDriftQuery(input) {
|
|
163
|
+
return sql `
|
|
164
|
+
SELECT
|
|
165
|
+
${input.check} AS check,
|
|
166
|
+
count(*)::int AS missing_count,
|
|
167
|
+
coalesce(
|
|
168
|
+
array_agg(candidate_id ORDER BY created_at DESC, candidate_id DESC)
|
|
169
|
+
FILTER (WHERE sample_ordinal <= ${input.sampleLimit}),
|
|
170
|
+
ARRAY[]::text[]
|
|
171
|
+
) AS sample_ids
|
|
172
|
+
FROM (
|
|
173
|
+
SELECT
|
|
174
|
+
${bookings.id} AS candidate_id,
|
|
175
|
+
${input.timestampColumn} AS created_at,
|
|
176
|
+
row_number() OVER (
|
|
177
|
+
ORDER BY ${input.timestampColumn} DESC, ${bookings.id} DESC
|
|
178
|
+
) AS sample_ordinal
|
|
179
|
+
FROM ${bookings}
|
|
180
|
+
WHERE ${input.timestampColumn} IS NOT NULL
|
|
181
|
+
${buildCreatedAtCondition(input.timestampColumn, input.input.createdAtFrom)}
|
|
182
|
+
AND NOT EXISTS (
|
|
183
|
+
SELECT 1
|
|
184
|
+
FROM ${actionLedgerEntries}
|
|
185
|
+
WHERE ${actionLedgerEntries.actionName} = ${input.actionName}
|
|
186
|
+
AND ${actionLedgerEntries.targetType} = ${"booking"}
|
|
187
|
+
AND ${actionLedgerEntries.targetId} = ${bookings.id}
|
|
188
|
+
)
|
|
189
|
+
) missing
|
|
190
|
+
`;
|
|
191
|
+
}
|
|
192
|
+
function normalizeSampleLimit(limit) {
|
|
193
|
+
if (!limit)
|
|
194
|
+
return DEFAULT_SAMPLE_LIMIT;
|
|
195
|
+
return Math.min(Math.max(Math.trunc(limit), 1), MAX_SAMPLE_LIMIT);
|
|
196
|
+
}
|
|
197
|
+
function buildCreatedAtCondition(column, value) {
|
|
198
|
+
if (!value)
|
|
199
|
+
return sql ``;
|
|
200
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
201
|
+
if (Number.isNaN(date.getTime())) {
|
|
202
|
+
throw new Error("createdAtFrom must be a valid date");
|
|
203
|
+
}
|
|
204
|
+
// agent-quality: raw-sql reviewed -- owner: bookings; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
205
|
+
return sql `AND ${column} >= ${date}`;
|
|
206
|
+
}
|
|
207
|
+
function extractRows(result) {
|
|
208
|
+
if (Array.isArray(result))
|
|
209
|
+
return result;
|
|
210
|
+
const maybeRows = result.rows;
|
|
211
|
+
return Array.isArray(maybeRows) ? maybeRows : [];
|
|
212
|
+
}
|
|
213
|
+
function normalizeRow(row) {
|
|
214
|
+
return {
|
|
215
|
+
check: row.check,
|
|
216
|
+
missingCount: Number(row.missing_count),
|
|
217
|
+
sampleIds: row.sample_ids ?? [],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
export const __test__ = {
|
|
221
|
+
normalizeRow,
|
|
222
|
+
};
|