e7d76f40de
New event_ticket_settings.tickets_mailed column (default false, since no event mails physical tickets today). When false, the redemption confirmation email tells the guest their tickets await pickup at the Abendkasse under their name instead. Read directly by sendRedemptionConfirmation from event_ticket_settings, so both send paths (redeem flow, admin resend) pick it up without either caller changing. Also fixes docker/init/03-tickets-schema.sql, found missing the SOURCE line for migration 003 while adding 004 - local tickets dev databases have been silently missing confirmation_email_status. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
320 lines
8.5 KiB
TypeScript
320 lines
8.5 KiB
TypeScript
/**
|
|
* @swagger
|
|
* components:
|
|
* schemas:
|
|
* VoucherStatus:
|
|
* type: string
|
|
* enum: [UNUSED, REDEEMED, VOID]
|
|
* RedemptionStatus:
|
|
* type: string
|
|
* enum: [ACTIVE, UNDONE]
|
|
* EligibleEvent:
|
|
* type: object
|
|
* required: [eventId, name, startDateTime, location, redemptionDeadline, deadlinePassed, isFull, collectAddress, requireAddress]
|
|
* properties:
|
|
* eventId:
|
|
* type: integer
|
|
* example: 42
|
|
* name:
|
|
* type: string
|
|
* example: "Adventskonzert 2026"
|
|
* startDateTime:
|
|
* type: string
|
|
* format: date-time
|
|
* location:
|
|
* type: string
|
|
* redemptionDeadline:
|
|
* type: string
|
|
* format: date-time
|
|
* nullable: true
|
|
* description: null when the event has no redemption deadline set
|
|
* deadlinePassed:
|
|
* type: boolean
|
|
* isFull:
|
|
* type: boolean
|
|
* spotsRemaining:
|
|
* type: integer
|
|
* nullable: true
|
|
* description: null when the event has no capacity cap set (uncapped)
|
|
* collectAddress:
|
|
* type: boolean
|
|
* requireAddress:
|
|
* type: boolean
|
|
* VoucherValidation:
|
|
* type: object
|
|
* required: [code, status, maxGuests, eligibleEvents]
|
|
* properties:
|
|
* code:
|
|
* type: string
|
|
* example: "K7F3M9QX"
|
|
* status:
|
|
* $ref: '#/components/schemas/VoucherStatus'
|
|
* maxGuests:
|
|
* type: integer
|
|
* example: 2
|
|
* prefillName:
|
|
* type: string
|
|
* nullable: true
|
|
* prefillEmail:
|
|
* type: string
|
|
* nullable: true
|
|
* eligibleEvents:
|
|
* type: array
|
|
* items:
|
|
* $ref: '#/components/schemas/EligibleEvent'
|
|
* RedeemGuest:
|
|
* type: object
|
|
* required: [name]
|
|
* properties:
|
|
* name:
|
|
* type: string
|
|
* example: "Erika Mustermann"
|
|
* RedeemRequest:
|
|
* type: object
|
|
* required: [eventId, contactName, contactEmail, guests]
|
|
* properties:
|
|
* eventId:
|
|
* type: integer
|
|
* contactName:
|
|
* type: string
|
|
* contactEmail:
|
|
* type: string
|
|
* contactAddress:
|
|
* type: string
|
|
* nullable: true
|
|
* guests:
|
|
* type: array
|
|
* items:
|
|
* $ref: '#/components/schemas/RedeemGuest'
|
|
* RedemptionSummary:
|
|
* type: object
|
|
* required: [redemptionId, code, eventId, status, contactName, contactEmail, guestCount, guests, redeemedAt]
|
|
* properties:
|
|
* redemptionId:
|
|
* type: integer
|
|
* code:
|
|
* type: string
|
|
* eventId:
|
|
* type: integer
|
|
* status:
|
|
* $ref: '#/components/schemas/RedemptionStatus'
|
|
* contactName:
|
|
* type: string
|
|
* contactEmail:
|
|
* type: string
|
|
* contactAddress:
|
|
* type: string
|
|
* nullable: true
|
|
* guestCount:
|
|
* type: integer
|
|
* guests:
|
|
* type: array
|
|
* items:
|
|
* type: string
|
|
* redeemedAt:
|
|
* type: string
|
|
* format: date-time
|
|
* confirmationEmailStatus:
|
|
* type: string
|
|
* enum: [SENT, FAILED]
|
|
* nullable: true
|
|
* description: Outcome of the redemption confirmation email. null until the send resolves.
|
|
* VoucherCode:
|
|
* type: object
|
|
* required: [code, status, maxGuests, createdByEmail, createdAt, eligibleEventIds]
|
|
* properties:
|
|
* code:
|
|
* type: string
|
|
* status:
|
|
* $ref: '#/components/schemas/VoucherStatus'
|
|
* maxGuests:
|
|
* type: integer
|
|
* prefillName:
|
|
* type: string
|
|
* nullable: true
|
|
* prefillEmail:
|
|
* type: string
|
|
* nullable: true
|
|
* batchId:
|
|
* type: string
|
|
* nullable: true
|
|
* createdByEmail:
|
|
* type: string
|
|
* createdAt:
|
|
* type: string
|
|
* format: date-time
|
|
* eligibleEventIds:
|
|
* type: array
|
|
* items:
|
|
* type: integer
|
|
* EventTicketSettings:
|
|
* type: object
|
|
* required: [eventId, collectAddress, requireAddress, ticketsMailed]
|
|
* properties:
|
|
* eventId:
|
|
* type: integer
|
|
* capacity:
|
|
* type: integer
|
|
* nullable: true
|
|
* redemptionDeadline:
|
|
* type: string
|
|
* format: date-time
|
|
* nullable: true
|
|
* collectAddress:
|
|
* type: boolean
|
|
* requireAddress:
|
|
* type: boolean
|
|
* description: Only meaningful when collectAddress is true.
|
|
* ticketsMailed:
|
|
* type: boolean
|
|
* description: When false, the redemption confirmation email tells the guest their tickets await pickup at the Abendkasse instead.
|
|
* EventStats:
|
|
* type: object
|
|
* required: [eventId, collectAddress, requireAddress, ticketsMailed, guestsUsed, unusedCodes, redeemedCodes, voidCodes]
|
|
* properties:
|
|
* eventId:
|
|
* type: integer
|
|
* capacity:
|
|
* type: integer
|
|
* nullable: true
|
|
* redemptionDeadline:
|
|
* type: string
|
|
* format: date-time
|
|
* nullable: true
|
|
* collectAddress:
|
|
* type: boolean
|
|
* requireAddress:
|
|
* type: boolean
|
|
* ticketsMailed:
|
|
* type: boolean
|
|
* guestsUsed:
|
|
* type: integer
|
|
* spotsRemaining:
|
|
* type: integer
|
|
* nullable: true
|
|
* unusedCodes:
|
|
* type: integer
|
|
* redeemedCodes:
|
|
* type: integer
|
|
* voidCodes:
|
|
* type: integer
|
|
* AuditLogEntry:
|
|
* type: object
|
|
* required: [auditId, code, adminEmail, action, createdAt]
|
|
* properties:
|
|
* auditId:
|
|
* type: integer
|
|
* code:
|
|
* type: string
|
|
* redemptionId:
|
|
* type: integer
|
|
* nullable: true
|
|
* adminEmail:
|
|
* type: string
|
|
* action:
|
|
* type: string
|
|
* enum: [EDIT, VOID, UNDO]
|
|
* changeSummary:
|
|
* type: object
|
|
* nullable: true
|
|
* reason:
|
|
* type: string
|
|
* nullable: true
|
|
* createdAt:
|
|
* type: string
|
|
* format: date-time
|
|
*/
|
|
|
|
export type VoucherStatus = 'UNUSED' | 'REDEEMED' | 'VOID';
|
|
export type RedemptionStatus = 'ACTIVE' | 'UNDONE';
|
|
export type AuditAction = 'EDIT' | 'VOID' | 'UNDO';
|
|
|
|
export interface EligibleEvent {
|
|
eventId: number;
|
|
name: string;
|
|
startDateTime: Date;
|
|
location: string;
|
|
redemptionDeadline: Date | null;
|
|
deadlinePassed: boolean;
|
|
isFull: boolean;
|
|
spotsRemaining: number | null;
|
|
collectAddress: boolean;
|
|
requireAddress: boolean;
|
|
}
|
|
|
|
export interface VoucherValidation {
|
|
code: string;
|
|
status: VoucherStatus;
|
|
maxGuests: number;
|
|
prefillName: string | null;
|
|
prefillEmail: string | null;
|
|
eligibleEvents: EligibleEvent[];
|
|
}
|
|
|
|
export interface RedeemGuest {
|
|
name: string;
|
|
}
|
|
|
|
export interface RedeemRequest {
|
|
eventId: number;
|
|
contactName: string;
|
|
contactEmail: string;
|
|
contactAddress?: string | null;
|
|
guests: RedeemGuest[];
|
|
}
|
|
|
|
export interface RedemptionSummary {
|
|
redemptionId: number;
|
|
code: string;
|
|
eventId: number;
|
|
status: RedemptionStatus;
|
|
contactName: string;
|
|
contactEmail: string;
|
|
contactAddress: string | null;
|
|
guestCount: number;
|
|
guests: string[];
|
|
redeemedAt: Date;
|
|
// null until the post-redemption confirmation email send resolves.
|
|
confirmationEmailStatus: 'SENT' | 'FAILED' | null;
|
|
}
|
|
|
|
export interface VoucherCode {
|
|
code: string;
|
|
status: VoucherStatus;
|
|
maxGuests: number;
|
|
prefillName: string | null;
|
|
prefillEmail: string | null;
|
|
batchId: string | null;
|
|
createdByEmail: string;
|
|
createdAt: Date;
|
|
eligibleEventIds: number[];
|
|
}
|
|
|
|
export interface EventTicketSettings {
|
|
eventId: number;
|
|
capacity: number | null;
|
|
redemptionDeadline: Date | null;
|
|
collectAddress: boolean;
|
|
requireAddress: boolean;
|
|
ticketsMailed: boolean;
|
|
}
|
|
|
|
export interface EventStats extends EventTicketSettings {
|
|
guestsUsed: number;
|
|
spotsRemaining: number | null;
|
|
unusedCodes: number;
|
|
redeemedCodes: number;
|
|
voidCodes: number;
|
|
}
|
|
|
|
export interface AuditLogEntry {
|
|
auditId: number;
|
|
code: string;
|
|
redemptionId: number | null;
|
|
adminEmail: string;
|
|
action: AuditAction;
|
|
changeSummary: Record<string, unknown> | null;
|
|
reason: string | null;
|
|
createdAt: Date;
|
|
}
|