@bash-app/bash-common 30.331.0 → 30.334.0
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/__tests__/causeTags.test.d.ts +2 -0
- package/dist/__tests__/causeTags.test.d.ts.map +1 -0
- package/dist/__tests__/causeTags.test.js +94 -0
- package/dist/__tests__/causeTags.test.js.map +1 -0
- package/dist/__tests__/definitionsHelpers.test.js +6 -2
- package/dist/__tests__/definitionsHelpers.test.js.map +1 -1
- package/dist/__tests__/hostCrmGrowthPack.test.js +12 -2
- package/dist/__tests__/hostCrmGrowthPack.test.js.map +1 -1
- package/dist/__tests__/hostPromoter.test.d.ts +2 -0
- package/dist/__tests__/hostPromoter.test.d.ts.map +1 -0
- package/dist/__tests__/hostPromoter.test.js +79 -0
- package/dist/__tests__/hostPromoter.test.js.map +1 -0
- package/dist/causeTags.d.ts +42 -0
- package/dist/causeTags.d.ts.map +1 -0
- package/dist/causeTags.js +173 -0
- package/dist/causeTags.js.map +1 -0
- package/dist/definitions.d.ts +38 -5
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +47 -7
- package/dist/definitions.js.map +1 -1
- package/dist/hostCrmAutomation.d.ts +12 -2
- package/dist/hostCrmAutomation.d.ts.map +1 -1
- package/dist/hostCrmAutomation.js +16 -2
- package/dist/hostCrmAutomation.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/sms/smsTemplates.d.ts +6 -1
- package/dist/sms/smsTemplates.d.ts.map +1 -1
- package/dist/sms/smsTemplates.js +1 -0
- package/dist/sms/smsTemplates.js.map +1 -1
- package/dist/utils/__tests__/coatCheckUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/coatCheckUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/coatCheckUtils.test.js +76 -0
- package/dist/utils/__tests__/coatCheckUtils.test.js.map +1 -0
- package/dist/utils/coatCheckUtils.d.ts +30 -0
- package/dist/utils/coatCheckUtils.d.ts.map +1 -0
- package/dist/utils/coatCheckUtils.js +85 -0
- package/dist/utils/coatCheckUtils.js.map +1 -0
- package/dist/utils/slugUtils.d.ts +3 -4
- package/dist/utils/slugUtils.d.ts.map +1 -1
- package/dist/utils/slugUtils.js +3 -4
- package/dist/utils/slugUtils.js.map +1 -1
- package/package.json +1 -1
- package/prisma/COMPREHENSIVE-MIGRATION-README.md +1 -1
- package/prisma/schema.prisma +431 -3
- package/src/__tests__/causeTags.test.ts +143 -0
- package/src/__tests__/definitionsHelpers.test.ts +11 -1
- package/src/__tests__/hostCrmGrowthPack.test.ts +14 -1
- package/src/__tests__/hostPromoter.test.ts +133 -0
- package/src/causeTags.ts +243 -0
- package/src/definitions.ts +77 -14
- package/src/hostCrmAutomation.ts +25 -2
- package/src/index.ts +2 -0
- package/src/sms/smsTemplates.ts +6 -0
- package/src/utils/__tests__/coatCheckUtils.test.ts +110 -0
- package/src/utils/coatCheckUtils.ts +134 -0
- package/src/utils/slugUtils.ts +3 -4
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canTransitionCoatCheckStatus,
|
|
3
|
+
clampCoatCheckPurchaseQty,
|
|
4
|
+
coatCheckScanOutcomeForStatus,
|
|
5
|
+
encodeCoatCheckQrPayload,
|
|
6
|
+
parseCoatCheckQrPayload,
|
|
7
|
+
remainingCoatCheckSlots,
|
|
8
|
+
} from "../coatCheckUtils";
|
|
9
|
+
|
|
10
|
+
describe("coatCheckUtils", () => {
|
|
11
|
+
describe("encode/parse QR payload", () => {
|
|
12
|
+
it("round-trips claim QR fields", () => {
|
|
13
|
+
const raw = encodeCoatCheckQrPayload({
|
|
14
|
+
eventId: "evt1",
|
|
15
|
+
entitlementId: "ent1",
|
|
16
|
+
qrToken: "tok-abc",
|
|
17
|
+
});
|
|
18
|
+
const parsed = parseCoatCheckQrPayload(raw);
|
|
19
|
+
expect(parsed).toMatchObject({
|
|
20
|
+
type: "coat_check",
|
|
21
|
+
eventId: "evt1",
|
|
22
|
+
entitlementId: "ent1",
|
|
23
|
+
qrToken: "tok-abc",
|
|
24
|
+
});
|
|
25
|
+
expect(typeof parsed?.timestamp).toBe("number");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("rejects ticket-style payloads", () => {
|
|
29
|
+
expect(
|
|
30
|
+
parseCoatCheckQrPayload(
|
|
31
|
+
JSON.stringify({ eventId: "e", ticketId: "t", qrToken: "x" })
|
|
32
|
+
)
|
|
33
|
+
).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("rejects invalid JSON", () => {
|
|
37
|
+
expect(parseCoatCheckQrPayload("not-json")).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("state machine", () => {
|
|
42
|
+
it("allows Paid → CheckedIn → Claimed", () => {
|
|
43
|
+
expect(canTransitionCoatCheckStatus("Paid", "CheckedIn")).toBe(true);
|
|
44
|
+
expect(canTransitionCoatCheckStatus("CheckedIn", "Claimed")).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("rejects illegal transitions", () => {
|
|
48
|
+
expect(canTransitionCoatCheckStatus("Paid", "Claimed")).toBe(false);
|
|
49
|
+
expect(canTransitionCoatCheckStatus("Claimed", "CheckedIn")).toBe(false);
|
|
50
|
+
expect(canTransitionCoatCheckStatus("CheckedIn", "Paid")).toBe(false);
|
|
51
|
+
expect(canTransitionCoatCheckStatus("Paid", "Paid")).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("allows refund/cancel from Paid or CheckedIn", () => {
|
|
55
|
+
expect(canTransitionCoatCheckStatus("Paid", "Refunded")).toBe(true);
|
|
56
|
+
expect(canTransitionCoatCheckStatus("CheckedIn", "Cancelled")).toBe(true);
|
|
57
|
+
expect(canTransitionCoatCheckStatus("Claimed", "Refunded")).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("maps status to scan outcomes", () => {
|
|
61
|
+
expect(coatCheckScanOutcomeForStatus("Paid")).toBe("ready_for_dropoff");
|
|
62
|
+
expect(coatCheckScanOutcomeForStatus("CheckedIn")).toBe("ready_for_claim");
|
|
63
|
+
expect(coatCheckScanOutcomeForStatus("Claimed")).toBe("already_claimed");
|
|
64
|
+
expect(coatCheckScanOutcomeForStatus("Refunded")).toBe(
|
|
65
|
+
"cancelled_or_refunded"
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("inventory helpers", () => {
|
|
71
|
+
it("treats null capacity as unlimited remaining", () => {
|
|
72
|
+
expect(
|
|
73
|
+
remainingCoatCheckSlots({ capacity: null, occupiedCount: 50 })
|
|
74
|
+
).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("computes remaining slots", () => {
|
|
78
|
+
expect(
|
|
79
|
+
remainingCoatCheckSlots({ capacity: 100, occupiedCount: 40 })
|
|
80
|
+
).toBe(60);
|
|
81
|
+
expect(
|
|
82
|
+
remainingCoatCheckSlots({ capacity: 10, occupiedCount: 12 })
|
|
83
|
+
).toBe(0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("clamps purchase qty by max and remaining", () => {
|
|
87
|
+
expect(
|
|
88
|
+
clampCoatCheckPurchaseQty({
|
|
89
|
+
requested: 5,
|
|
90
|
+
maxPerPurchase: 2,
|
|
91
|
+
remainingSlots: 10,
|
|
92
|
+
})
|
|
93
|
+
).toBe(2);
|
|
94
|
+
expect(
|
|
95
|
+
clampCoatCheckPurchaseQty({
|
|
96
|
+
requested: 5,
|
|
97
|
+
maxPerPurchase: 10,
|
|
98
|
+
remainingSlots: 1,
|
|
99
|
+
})
|
|
100
|
+
).toBe(1);
|
|
101
|
+
expect(
|
|
102
|
+
clampCoatCheckPurchaseQty({
|
|
103
|
+
requested: -3,
|
|
104
|
+
maxPerPurchase: 2,
|
|
105
|
+
remainingSlots: null,
|
|
106
|
+
})
|
|
107
|
+
).toBe(0);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COAT_CHECK_ENTITLEMENT_STATUSES,
|
|
3
|
+
COAT_CHECK_QR_TYPE,
|
|
4
|
+
COAT_CHECK_SCAN_OUTCOMES,
|
|
5
|
+
COAT_CHECK_SLOT_OCCUPYING_STATUSES,
|
|
6
|
+
type CoatCheckEntitlementStatusName,
|
|
7
|
+
type CoatCheckScanOutcome,
|
|
8
|
+
} from "../definitions.js";
|
|
9
|
+
|
|
10
|
+
export type CoatCheckQrPayload = {
|
|
11
|
+
type: typeof COAT_CHECK_QR_TYPE;
|
|
12
|
+
eventId: string;
|
|
13
|
+
entitlementId: string;
|
|
14
|
+
qrToken: string;
|
|
15
|
+
timestamp: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function encodeCoatCheckQrPayload(args: {
|
|
19
|
+
eventId: string;
|
|
20
|
+
entitlementId: string;
|
|
21
|
+
qrToken: string;
|
|
22
|
+
}): string {
|
|
23
|
+
const payload: CoatCheckQrPayload = {
|
|
24
|
+
type: COAT_CHECK_QR_TYPE,
|
|
25
|
+
eventId: args.eventId,
|
|
26
|
+
entitlementId: args.entitlementId,
|
|
27
|
+
qrToken: args.qrToken,
|
|
28
|
+
timestamp: Date.now(),
|
|
29
|
+
};
|
|
30
|
+
return JSON.stringify(payload);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function parseCoatCheckQrPayload(
|
|
34
|
+
raw: string
|
|
35
|
+
): CoatCheckQrPayload | null {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(raw) as Partial<CoatCheckQrPayload>;
|
|
38
|
+
if (
|
|
39
|
+
parsed?.type !== COAT_CHECK_QR_TYPE ||
|
|
40
|
+
typeof parsed.eventId !== "string" ||
|
|
41
|
+
typeof parsed.entitlementId !== "string" ||
|
|
42
|
+
typeof parsed.qrToken !== "string"
|
|
43
|
+
) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
type: COAT_CHECK_QR_TYPE,
|
|
48
|
+
eventId: parsed.eventId,
|
|
49
|
+
entitlementId: parsed.entitlementId,
|
|
50
|
+
qrToken: parsed.qrToken,
|
|
51
|
+
timestamp:
|
|
52
|
+
typeof parsed.timestamp === "number" ? parsed.timestamp : Date.now(),
|
|
53
|
+
};
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function coatCheckScanOutcomeForStatus(
|
|
60
|
+
status: CoatCheckEntitlementStatusName
|
|
61
|
+
): CoatCheckScanOutcome {
|
|
62
|
+
switch (status) {
|
|
63
|
+
case "Paid":
|
|
64
|
+
return "ready_for_dropoff";
|
|
65
|
+
case "CheckedIn":
|
|
66
|
+
return "ready_for_claim";
|
|
67
|
+
case "Claimed":
|
|
68
|
+
return "already_claimed";
|
|
69
|
+
case "Refunded":
|
|
70
|
+
case "Cancelled":
|
|
71
|
+
return "cancelled_or_refunded";
|
|
72
|
+
default:
|
|
73
|
+
return "invalid_or_expired";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function canTransitionCoatCheckStatus(
|
|
78
|
+
from: CoatCheckEntitlementStatusName,
|
|
79
|
+
to: CoatCheckEntitlementStatusName
|
|
80
|
+
): boolean {
|
|
81
|
+
if (from === to) return false;
|
|
82
|
+
if (from === "Paid" && to === "CheckedIn") return true;
|
|
83
|
+
if (from === "CheckedIn" && to === "Claimed") return true;
|
|
84
|
+
if (
|
|
85
|
+
(from === "Paid" || from === "CheckedIn") &&
|
|
86
|
+
(to === "Refunded" || to === "Cancelled")
|
|
87
|
+
) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function isCoatCheckSlotOccupyingStatus(
|
|
94
|
+
status: string
|
|
95
|
+
): status is CoatCheckEntitlementStatusName {
|
|
96
|
+
return (COAT_CHECK_SLOT_OCCUPYING_STATUSES as readonly string[]).includes(
|
|
97
|
+
status
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function isCoatCheckEntitlementStatus(
|
|
102
|
+
status: string
|
|
103
|
+
): status is CoatCheckEntitlementStatusName {
|
|
104
|
+
return (COAT_CHECK_ENTITLEMENT_STATUSES as readonly string[]).includes(status);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function assertCoatCheckScanOutcome(
|
|
108
|
+
outcome: string
|
|
109
|
+
): outcome is CoatCheckScanOutcome {
|
|
110
|
+
return (COAT_CHECK_SCAN_OUTCOMES as readonly string[]).includes(outcome);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Remaining slots; null capacity = unlimited (Infinity for math). */
|
|
114
|
+
export function remainingCoatCheckSlots(args: {
|
|
115
|
+
capacity: number | null | undefined;
|
|
116
|
+
occupiedCount: number;
|
|
117
|
+
}): number | null {
|
|
118
|
+
if (args.capacity == null) return null;
|
|
119
|
+
return Math.max(0, args.capacity - args.occupiedCount);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function clampCoatCheckPurchaseQty(args: {
|
|
123
|
+
requested: number;
|
|
124
|
+
maxPerPurchase: number;
|
|
125
|
+
remainingSlots: number | null;
|
|
126
|
+
}): number {
|
|
127
|
+
const maxPer = Math.max(1, args.maxPerPurchase || 1);
|
|
128
|
+
let qty = Math.max(0, Math.floor(args.requested));
|
|
129
|
+
qty = Math.min(qty, maxPer);
|
|
130
|
+
if (args.remainingSlots != null) {
|
|
131
|
+
qty = Math.min(qty, args.remainingSlots);
|
|
132
|
+
}
|
|
133
|
+
return qty;
|
|
134
|
+
}
|
package/src/utils/slugUtils.ts
CHANGED
|
@@ -53,10 +53,9 @@ export function getPublicBashDetailPath(
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* link works for people who aren't signed in yet.
|
|
56
|
+
* Path to Your Memories (private vault) for a bash — nested under bash detail
|
|
57
|
+
* (`/bash/{id}-{slug}/memories`). Requires sign-in + eligibility; not a public
|
|
58
|
+
* unauthenticated recap page.
|
|
60
59
|
*/
|
|
61
60
|
export function getPublicBashMemoriesPath(
|
|
62
61
|
bashEventId: string,
|