@bash-app/bash-common 30.375.0 → 30.377.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.
Files changed (36) hide show
  1. package/dist/definitions.d.ts +7 -0
  2. package/dist/definitions.d.ts.map +1 -1
  3. package/dist/definitions.js.map +1 -1
  4. package/dist/extendedSchemas.d.ts +57 -1
  5. package/dist/extendedSchemas.d.ts.map +1 -1
  6. package/dist/extendedSchemas.js +10 -0
  7. package/dist/extendedSchemas.js.map +1 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/utils/__tests__/ticketPrintPostageUtils.test.d.ts +2 -0
  13. package/dist/utils/__tests__/ticketPrintPostageUtils.test.d.ts.map +1 -0
  14. package/dist/utils/__tests__/ticketPrintPostageUtils.test.js +70 -0
  15. package/dist/utils/__tests__/ticketPrintPostageUtils.test.js.map +1 -0
  16. package/dist/utils/lobFlyerUtils.d.ts +2 -0
  17. package/dist/utils/lobFlyerUtils.d.ts.map +1 -1
  18. package/dist/utils/lobFlyerUtils.js +8 -0
  19. package/dist/utils/lobFlyerUtils.js.map +1 -1
  20. package/dist/utils/qrCodeUtils.d.ts +17 -0
  21. package/dist/utils/qrCodeUtils.d.ts.map +1 -1
  22. package/dist/utils/qrCodeUtils.js +20 -0
  23. package/dist/utils/qrCodeUtils.js.map +1 -1
  24. package/dist/utils/ticketPrintPostageUtils.d.ts +27 -0
  25. package/dist/utils/ticketPrintPostageUtils.d.ts.map +1 -0
  26. package/dist/utils/ticketPrintPostageUtils.js +65 -0
  27. package/dist/utils/ticketPrintPostageUtils.js.map +1 -0
  28. package/package.json +1 -1
  29. package/prisma/schema.prisma +94 -0
  30. package/src/definitions.ts +8 -0
  31. package/src/extendedSchemas.ts +64 -0
  32. package/src/index.ts +1 -0
  33. package/src/utils/__tests__/ticketPrintPostageUtils.test.ts +95 -0
  34. package/src/utils/lobFlyerUtils.ts +7 -0
  35. package/src/utils/qrCodeUtils.ts +26 -0
  36. package/src/utils/ticketPrintPostageUtils.ts +81 -0
@@ -78,6 +78,10 @@ import {
78
78
  TicketTier,
79
79
  TicketTransfer,
80
80
  TicketTransferInvite,
81
+ TicketDeliverySettings,
82
+ TicketDeliveryMethod,
83
+ TicketPrintOrder,
84
+ TicketPrintOrderStatus,
81
85
  User,
82
86
  UserPreferences,
83
87
  UserStats,
@@ -1729,6 +1733,8 @@ export type TicketFlexible = Ticket & {
1729
1733
  transfers?: TicketTransfer[];
1730
1734
  metadata?: TicketMetadata[];
1731
1735
  transferInvite?: TicketTransferInviteTeaser | null;
1736
+ /** Physical delivery (mail / venue will-call) status, when the ticket opted in at checkout. */
1737
+ printOrder?: TicketPrintOrder | null;
1732
1738
  };
1733
1739
 
1734
1740
  /**
@@ -2114,6 +2120,64 @@ export interface FlyerAnalyticsSummary {
2114
2120
 
2115
2121
  export type PostalCarrierRouteExt = PostalCarrierRoute;
2116
2122
 
2123
+ // --- Physical ticket delivery (mail / venue will-call via Lob) ---
2124
+
2125
+ /** Delivery method a buyer picks at checkout when the tier allows physical delivery. */
2126
+ export interface TicketDeliverySelection {
2127
+ deliveryMethod: TicketDeliveryMethod;
2128
+ }
2129
+
2130
+ export const TICKET_PRINT_ORDER_WITH_TICKET_INCLUDE = {
2131
+ ticket: {
2132
+ select: {
2133
+ id: true,
2134
+ fullName: true,
2135
+ email: true,
2136
+ ticketTier: { select: { id: true, title: true } },
2137
+ },
2138
+ },
2139
+ } satisfies Prisma.TicketPrintOrderInclude;
2140
+
2141
+ export type TicketPrintOrderExt = Prisma.TicketPrintOrderGetPayload<{
2142
+ include: typeof TICKET_PRINT_ORDER_WITH_TICKET_INCLUDE;
2143
+ }>;
2144
+
2145
+ /** Host-facing form for enabling/configuring physical delivery on an event. */
2146
+ export interface TicketDeliverySettingsFormData {
2147
+ physicalMailEnabled: boolean;
2148
+ willCallEnabled: boolean;
2149
+ lobPostcardSize: TicketDeliverySettings["lobPostcardSize"];
2150
+ willCallAddressName?: string;
2151
+ willCallAddressLine1?: string;
2152
+ willCallAddressLine2?: string;
2153
+ willCallAddressCity?: string;
2154
+ willCallAddressState?: string;
2155
+ willCallAddressZip?: string;
2156
+ }
2157
+
2158
+ /** Response from the host "send queued will-call tickets to venue" action. */
2159
+ export interface WillCallBatchSubmitResult {
2160
+ willCallBatchId: string;
2161
+ submittedCount: number;
2162
+ failedCount: number;
2163
+ }
2164
+
2165
+ /** Summary counts for the host Tickets tab "Print & Mail" panel. */
2166
+ export interface TicketPrintOrderSummary {
2167
+ deliveryMethod: TicketDeliveryMethod;
2168
+ status: TicketPrintOrderStatus;
2169
+ count: number;
2170
+ }
2171
+
2172
+ /** Public-safe checkout-time read of an event's physical delivery settings. */
2173
+ export interface TicketDeliveryAvailability {
2174
+ physicalMailEnabled: boolean;
2175
+ willCallEnabled: boolean;
2176
+ lobPostcardSize: TicketDeliverySettings["lobPostcardSize"];
2177
+ /** Buyer-facing surcharge (integer cents) for one physical ticket piece. */
2178
+ surchargeCentsPerTicket: number;
2179
+ }
2180
+
2117
2181
  // === Talent Agency selects/types ============================================
2118
2182
 
2119
2183
  /// Public-safe roster entry for an agency's artist (agency detail page + cards).
package/src/index.ts CHANGED
@@ -416,6 +416,7 @@ export * from "./utils/ticketCheckInDisplay.js";
416
416
  export * from "./utils/coatCheckUtils.js";
417
417
  export * from "./utils/ticketTierSchedule.js";
418
418
  export * from "./utils/lobFlyerUtils.js";
419
+ export * from "./utils/ticketPrintPostageUtils.js";
419
420
  export * from "./utils/generalDateTimeUtils.js";
420
421
  export * from "./utils/locationVisibility.js";
421
422
  export * from "./utils/luxonUtils.js";
@@ -0,0 +1,95 @@
1
+ import { describe, expect, it } from "@jest/globals";
2
+ import { TicketDeliveryMethod, TicketPrintOrderStatus } from "@prisma/client";
3
+
4
+ import {
5
+ ticketDeliveryStatusLabel,
6
+ ticketPrintSurchargeCentsForQuantity,
7
+ ticketPrintSurchargeCentsPerPiece,
8
+ } from "../ticketPrintPostageUtils.js";
9
+
10
+ describe("ticketPrintPostageUtils", () => {
11
+ describe("ticketPrintSurchargeCentsPerPiece", () => {
12
+ it("matches the per-piece quote for quantity 1", () => {
13
+ expect(ticketPrintSurchargeCentsPerPiece("LOB_4X6")).toBe(
14
+ ticketPrintSurchargeCentsForQuantity("LOB_4X6", 1)
15
+ );
16
+ });
17
+ });
18
+
19
+ describe("ticketPrintSurchargeCentsForQuantity", () => {
20
+ it("is zero for quantity < 1", () => {
21
+ expect(ticketPrintSurchargeCentsForQuantity("LOB_4X6", 0)).toBe(0);
22
+ expect(ticketPrintSurchargeCentsForQuantity("LOB_4X6", -1)).toBe(0);
23
+ });
24
+
25
+ it("scales with quantity", () => {
26
+ const one = ticketPrintSurchargeCentsForQuantity("LOB_6X9", 1);
27
+ const five = ticketPrintSurchargeCentsForQuantity("LOB_6X9", 5);
28
+ expect(five).toBeGreaterThan(one);
29
+ });
30
+ });
31
+
32
+ describe("ticketDeliveryStatusLabel", () => {
33
+ it("returns null for Digital (no print order)", () => {
34
+ expect(ticketDeliveryStatusLabel(null)).toBeNull();
35
+ expect(ticketDeliveryStatusLabel(undefined)).toBeNull();
36
+ });
37
+
38
+ it("returns a cancelled label regardless of delivery method (before submit)", () => {
39
+ expect(
40
+ ticketDeliveryStatusLabel({
41
+ deliveryMethod: TicketDeliveryMethod.PhysicalMail,
42
+ status: TicketPrintOrderStatus.CancelledBeforeSubmit,
43
+ })
44
+ ).toBe("Physical ticket cancelled");
45
+ });
46
+
47
+ it("returns a cancelled label (after submit)", () => {
48
+ expect(
49
+ ticketDeliveryStatusLabel({
50
+ deliveryMethod: TicketDeliveryMethod.VenueWillCall,
51
+ status: TicketPrintOrderStatus.CancelledAfterSubmit,
52
+ })
53
+ ).toBe("Physical ticket cancelled");
54
+ });
55
+
56
+ it("returns a failure label", () => {
57
+ expect(
58
+ ticketDeliveryStatusLabel({
59
+ deliveryMethod: TicketDeliveryMethod.PhysicalMail,
60
+ status: TicketPrintOrderStatus.Failed,
61
+ })
62
+ ).toBe("Physical ticket delivery failed — contact the host");
63
+ });
64
+
65
+ it.each([
66
+ [TicketPrintOrderStatus.Queued, "Pick up at venue: will-call (printing soon)"],
67
+ [TicketPrintOrderStatus.Submitted, "Pick up at venue: will-call"],
68
+ [TicketPrintOrderStatus.Printed, "Pick up at venue: will-call"],
69
+ [TicketPrintOrderStatus.InTransit, "Pick up at venue: will-call"],
70
+ [TicketPrintOrderStatus.Delivered, "Pick up at venue: will-call (ready)"],
71
+ ])("VenueWillCall %s -> %s", (status, expected) => {
72
+ expect(
73
+ ticketDeliveryStatusLabel({
74
+ deliveryMethod: TicketDeliveryMethod.VenueWillCall,
75
+ status,
76
+ })
77
+ ).toBe(expected);
78
+ });
79
+
80
+ it.each([
81
+ [TicketPrintOrderStatus.Queued, "Mailed ticket: printing"],
82
+ [TicketPrintOrderStatus.Submitted, "Mailed ticket: printing"],
83
+ [TicketPrintOrderStatus.Printed, "Mailed ticket: printed"],
84
+ [TicketPrintOrderStatus.InTransit, "Mailed ticket: in transit"],
85
+ [TicketPrintOrderStatus.Delivered, "Mailed ticket: delivered"],
86
+ ])("PhysicalMail %s -> %s", (status, expected) => {
87
+ expect(
88
+ ticketDeliveryStatusLabel({
89
+ deliveryMethod: TicketDeliveryMethod.PhysicalMail,
90
+ status,
91
+ })
92
+ ).toBe(expected);
93
+ });
94
+ });
95
+ });
@@ -13,6 +13,13 @@
13
13
 
14
14
  export type LobPostcardSizeKey = "LOB_4X6" | "LOB_6X9" | "LOB_6X11";
15
15
 
16
+ /** Narrows the Prisma `LobPostcardSize` enum value to the string-literal key used by these utils. */
17
+ export function lobPostcardSizeEnumToKey(size: LobPostcardSizeKey | string): LobPostcardSizeKey {
18
+ if (size === "LOB_6X9") return "LOB_6X9";
19
+ if (size === "LOB_6X11") return "LOB_6X11";
20
+ return "LOB_4X6";
21
+ }
22
+
16
23
  /** Trim width × height in inches (portrait), matching Lob postcard products. */
17
24
  export const LOB_POSTCARD_TRIM_IN: Record<
18
25
  LobPostcardSizeKey,
@@ -24,3 +24,29 @@ export async function createQrCodeImage(qrData: string): Promise<string> {
24
24
  errorCorrectionLevel: 'high',
25
25
  });
26
26
  }
27
+
28
+ /**
29
+ * Door-scan admission QR payload for a ticket — the same shape the buyer's
30
+ * "show at door" QR encodes (bash-app `MyTicketInlineQr.tsx`, built inline as
31
+ * `checkInData` for `encodeTicketQrPayload`) and what the API's door-verify
32
+ * parser reads (`parseTicketQrScanPayload` in `ticketApi.ts`, which requires
33
+ * string `eventId`/`ticketId` and reads an optional `qrToken`).
34
+ *
35
+ * Server-side callers (e.g. printed ticket artwork) MUST use this so a
36
+ * physical ticket's QR scans identically to the in-app one — no new
37
+ * verification path, no schema changes to `qrToken` handling.
38
+ */
39
+ export function encodeTicketDoorQrPayload(args: {
40
+ eventId: string;
41
+ ticketId: string;
42
+ ownerUserId?: string | null;
43
+ qrToken?: string | null;
44
+ }): string {
45
+ return JSON.stringify({
46
+ eventId: args.eventId,
47
+ userId: args.ownerUserId ?? "",
48
+ ticketId: args.ticketId,
49
+ qrToken: args.qrToken ?? undefined,
50
+ timestamp: new Date().toISOString(),
51
+ });
52
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Buyer-facing surcharge for physical ticket delivery (mail-to-buyer or venue
3
+ * will-call), both fulfilled as individually-addressed Lob postcards — one
4
+ * ticket = one piece, same product used by Flyer Blast's addressed mail path.
5
+ *
6
+ * Reuses the Lob per-piece cost table from `lobFlyerUtils.ts` rather than
7
+ * duplicating pricing — same "verify before production" caveat applies here.
8
+ */
9
+
10
+ import { TicketDeliveryMethod, TicketPrintOrderStatus } from "@prisma/client";
11
+
12
+ import {
13
+ quoteLobAddressedPostageCents,
14
+ type LobPostcardSizeKey,
15
+ } from "./lobFlyerUtils.js";
16
+
17
+ /** Surcharge for a single printed/mailed ticket piece (integer cents). */
18
+ export function ticketPrintSurchargeCentsPerPiece(size: LobPostcardSizeKey): number {
19
+ return quoteLobAddressedPostageCents(size, 1);
20
+ }
21
+
22
+ /**
23
+ * Total buyer-facing surcharge (integer cents) for `quantity` physical ticket
24
+ * pieces of the given size — used as the Stripe checkout line item amount.
25
+ */
26
+ export function ticketPrintSurchargeCentsForQuantity(
27
+ size: LobPostcardSizeKey,
28
+ quantity: number
29
+ ): number {
30
+ if (quantity < 1) return 0;
31
+ return quoteLobAddressedPostageCents(size, quantity);
32
+ }
33
+
34
+ /**
35
+ * Buyer-facing badge copy for a ticket's physical delivery status — shared by
36
+ * My Tickets and the ticket details view so both surfaces stay in sync.
37
+ * `null`/`undefined` (Digital) means no badge should render.
38
+ */
39
+ export function ticketDeliveryStatusLabel(
40
+ printOrder:
41
+ | { deliveryMethod: TicketDeliveryMethod; status: TicketPrintOrderStatus }
42
+ | null
43
+ | undefined
44
+ ): string | null {
45
+ if (!printOrder) return null;
46
+ const { deliveryMethod, status } = printOrder;
47
+
48
+ if (
49
+ status === TicketPrintOrderStatus.CancelledBeforeSubmit ||
50
+ status === TicketPrintOrderStatus.CancelledAfterSubmit
51
+ ) {
52
+ return "Physical ticket cancelled";
53
+ }
54
+ if (status === TicketPrintOrderStatus.Failed) {
55
+ return "Physical ticket delivery failed — contact the host";
56
+ }
57
+ if (deliveryMethod === TicketDeliveryMethod.VenueWillCall) {
58
+ switch (status) {
59
+ case TicketPrintOrderStatus.Queued:
60
+ return "Pick up at venue: will-call (printing soon)";
61
+ case TicketPrintOrderStatus.Delivered:
62
+ return "Pick up at venue: will-call (ready)";
63
+ default:
64
+ return "Pick up at venue: will-call";
65
+ }
66
+ }
67
+ // PhysicalMail
68
+ switch (status) {
69
+ case TicketPrintOrderStatus.Queued:
70
+ case TicketPrintOrderStatus.Submitted:
71
+ return "Mailed ticket: printing";
72
+ case TicketPrintOrderStatus.Printed:
73
+ return "Mailed ticket: printed";
74
+ case TicketPrintOrderStatus.InTransit:
75
+ return "Mailed ticket: in transit";
76
+ case TicketPrintOrderStatus.Delivered:
77
+ return "Mailed ticket: delivered";
78
+ default:
79
+ return "Mailed ticket";
80
+ }
81
+ }