PSA/shared/workflow/runtime/schemas/ticketEventSchemas.ts
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

307 lines
14 KiB
TypeScript

import { z } from 'zod';
import {
BaseDomainEventPayloadSchema,
changesSchema,
updatedFieldsSchema,
uuidSchema,
} from './commonEventPayloadSchemas';
/**
* Ticket event payloads for workflow runtime v2 simulation and validation.
*
* Notes:
* - Payloads follow PRD conventions: tenantId + occurredAt required; actor fields optional.
* - These are intentionally higher-level and stable; they can be expanded/versioned as needed.
*/
const ticketIdSchema = uuidSchema('Ticket ID');
const userIdSchema = uuidSchema('User ID');
const contactIdSchema = uuidSchema('Contact ID');
const messageIdSchema = uuidSchema('Message ID');
const noteIdSchema = uuidSchema('Note ID');
const timeEntryIdSchema = uuidSchema('Time Entry ID');
const assigneeTypeSchema = z.enum(['user', 'team']).describe('Assignee type');
export const ticketCreatedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
createdByUserId: userIdSchema.optional().describe('User who created the ticket'),
actorUserId: userIdSchema.optional().describe('Actor User ID (preferred)'),
createdAt: z.string().optional().describe('Created timestamp (ISO 8601)'),
updatedFields: updatedFieldsSchema,
changes: changesSchema,
}).describe('Payload for TICKET_CREATED');
export type TicketCreatedEventPayload = z.infer<typeof ticketCreatedEventPayloadSchema>;
export const ticketAssignedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
assignedToUserId: userIdSchema.optional().describe('Deprecated: user assigned to the ticket'),
assignedByUserId: userIdSchema.optional().describe('User who performed the assignment'),
previousAssigneeId: z.string().uuid().optional(),
previousAssigneeType: assigneeTypeSchema.optional(),
newAssigneeId: z.string().uuid().optional(),
newAssigneeType: assigneeTypeSchema.optional(),
assignedAt: z.string().optional().describe('Assigned timestamp (ISO 8601)'),
updatedFields: updatedFieldsSchema,
changes: changesSchema,
}).describe('Payload for TICKET_ASSIGNED');
export type TicketAssignedEventPayload = z.infer<typeof ticketAssignedEventPayloadSchema>;
export const ticketClosedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
closedByUserId: userIdSchema.optional().describe('User who closed the ticket'),
closedAt: z.string().optional().describe('Closed timestamp (ISO 8601)'),
reason: z.string().optional().describe('Optional close reason'),
updatedFields: updatedFieldsSchema,
changes: changesSchema,
}).describe('Payload for TICKET_CLOSED');
export type TicketClosedEventPayload = z.infer<typeof ticketClosedEventPayloadSchema>;
export const ticketUpdatedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
updatedByUserId: userIdSchema.optional().describe('User who updated the ticket'),
updatedFields: updatedFieldsSchema,
changes: changesSchema,
}).describe('Payload for TICKET_UPDATED');
export type TicketUpdatedEventPayload = z.infer<typeof ticketUpdatedEventPayloadSchema>;
export const ticketResponseStateChangedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
previousResponseState: z.string().optional().describe('Previous response state'),
newResponseState: z.string().optional().describe('New response state'),
}).describe('Payload for TICKET_RESPONSE_STATE_CHANGED');
export type TicketResponseStateChangedEventPayload = z.infer<
typeof ticketResponseStateChangedEventPayloadSchema
>;
export const ticketStatusChangedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
previousStatusId: z.string().min(1).describe('Previous status ID'),
newStatusId: z.string().min(1).describe('New status ID'),
reason: z.string().optional(),
changedAt: z.string().datetime().optional().describe('Timestamp when status changed (ISO 8601)'),
}).describe('Payload for TICKET_STATUS_CHANGED');
export type TicketStatusChangedEventPayload = z.infer<typeof ticketStatusChangedEventPayloadSchema>;
export const ticketPriorityChangedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
previousPriorityId: z.string().min(1).describe('Previous priority ID'),
newPriorityId: z.string().min(1).describe('New priority ID'),
reason: z.string().optional(),
changedAt: z.string().datetime().optional().describe('Timestamp when priority changed (ISO 8601)'),
}).describe('Payload for TICKET_PRIORITY_CHANGED');
export type TicketPriorityChangedEventPayload = z.infer<typeof ticketPriorityChangedEventPayloadSchema>;
export const ticketUnassignedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
previousAssigneeId: z.string().uuid().describe('Previous assignee ID'),
previousAssigneeType: assigneeTypeSchema.describe('Previous assignee type'),
newAssigneeId: z.string().uuid().optional().describe('New assignee ID (typically absent after unassignment)'),
newAssigneeType: assigneeTypeSchema.optional().describe('New assignee type (typically absent after unassignment)'),
unassignedAt: z.string().datetime().optional(),
reason: z.string().optional(),
}).describe('Payload for TICKET_UNASSIGNED');
export type TicketUnassignedEventPayload = z.infer<typeof ticketUnassignedEventPayloadSchema>;
export const ticketReopenedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
previousStatusId: z.string().min(1).describe('Previous status ID'),
newStatusId: z.string().min(1).describe('New status ID'),
reopenedAt: z.string().datetime().optional(),
reason: z.string().optional(),
}).describe('Payload for TICKET_REOPENED');
export type TicketReopenedEventPayload = z.infer<typeof ticketReopenedEventPayloadSchema>;
export const ticketMergedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
sourceTicketId: ticketIdSchema.describe('Source ticket ID'),
targetTicketId: ticketIdSchema.describe('Target ticket ID'),
mergedAt: z.string().datetime().optional(),
reason: z.string().optional(),
}).describe('Payload for TICKET_MERGED');
export type TicketMergedEventPayload = z.infer<typeof ticketMergedEventPayloadSchema>;
export const ticketSplitEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
originalTicketId: ticketIdSchema.describe('Original ticket ID'),
newTicketIds: z.array(ticketIdSchema).min(1).describe('New ticket IDs'),
splitAt: z.string().datetime().optional(),
reason: z.string().optional(),
}).describe('Payload for TICKET_SPLIT');
export type TicketSplitEventPayload = z.infer<typeof ticketSplitEventPayloadSchema>;
export const ticketTagsChangedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
addedTagIds: z.array(z.string().uuid()).optional(),
removedTagIds: z.array(z.string().uuid()).optional(),
changedAt: z.string().datetime().optional(),
}).describe('Payload for TICKET_TAGS_CHANGED');
export type TicketTagsChangedEventPayload = z.infer<typeof ticketTagsChangedEventPayloadSchema>;
export const ticketQueueChangedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
previousBoardId: z.string().uuid().optional(),
newBoardId: z.string().uuid().optional(),
changedAt: z.string().datetime().optional(),
}).describe('Payload for TICKET_QUEUE_CHANGED');
export type TicketQueueChangedEventPayload = z.infer<typeof ticketQueueChangedEventPayloadSchema>;
export const ticketEscalatedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
fromQueueId: z.string().uuid().optional(),
toQueueId: z.string().uuid().optional(),
escalatedAt: z.string().datetime().optional(),
reason: z.string().optional(),
}).describe('Payload for TICKET_ESCALATED');
export type TicketEscalatedEventPayload = z.infer<typeof ticketEscalatedEventPayloadSchema>;
const visibilitySchema = z.enum(['public', 'internal']).describe('Message visibility');
const messageChannelSchema = z.enum(['email', 'portal', 'ui', 'api']).describe('Message channel');
const authorTypeSchema = z.enum(['user', 'contact']).describe('Author type');
export const ticketMessageAddedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
messageId: messageIdSchema,
visibility: visibilitySchema,
authorId: z.string().uuid().describe('Author ID (user/contact)'),
authorType: authorTypeSchema,
channel: messageChannelSchema,
createdAt: z.string().datetime().optional(),
attachmentsCount: z.number().int().nonnegative().optional(),
}).describe('Payload for TICKET_MESSAGE_ADDED');
export type TicketMessageAddedEventPayload = z.infer<typeof ticketMessageAddedEventPayloadSchema>;
export const ticketCustomerRepliedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
messageId: messageIdSchema,
contactId: contactIdSchema,
channel: messageChannelSchema,
receivedAt: z.string().datetime().optional(),
attachmentsCount: z.number().int().nonnegative().optional(),
}).describe('Payload for TICKET_CUSTOMER_REPLIED');
export type TicketCustomerRepliedEventPayload = z.infer<typeof ticketCustomerRepliedEventPayloadSchema>;
export const ticketInternalNoteAddedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
noteId: noteIdSchema,
createdAt: z.string().datetime().optional(),
}).describe('Payload for TICKET_INTERNAL_NOTE_ADDED');
export type TicketInternalNoteAddedEventPayload = z.infer<
typeof ticketInternalNoteAddedEventPayloadSchema
>;
export const ticketTimeEntryAddedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
timeEntryId: timeEntryIdSchema,
minutes: z.number().int().positive(),
billable: z.boolean(),
createdAt: z.string().datetime().optional(),
}).describe('Payload for TICKET_TIME_ENTRY_ADDED');
export type TicketTimeEntryAddedEventPayload = z.infer<typeof ticketTimeEntryAddedEventPayloadSchema>;
const slaStageSchema = z.enum(['response', 'resolution', 'custom']).describe('SLA stage');
export const ticketSlaStageEnteredEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
slaPolicyId: z.string().uuid(),
stage: slaStageSchema,
enteredAt: z.string().datetime().optional(),
targetAt: z.string().datetime().optional(),
}).describe('Payload for TICKET_SLA_STAGE_ENTERED');
export type TicketSlaStageEnteredEventPayload = z.infer<typeof ticketSlaStageEnteredEventPayloadSchema>;
export const ticketSlaStageMetEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
slaPolicyId: z.string().uuid(),
stage: slaStageSchema,
metAt: z.string().datetime().optional(),
targetAt: z.string().datetime().optional(),
}).describe('Payload for TICKET_SLA_STAGE_MET');
export type TicketSlaStageMetEventPayload = z.infer<typeof ticketSlaStageMetEventPayloadSchema>;
export const ticketSlaStageBreachedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
slaPolicyId: z.string().uuid(),
stage: slaStageSchema,
breachedAt: z.string().datetime().optional(),
targetAt: z.string().datetime().optional(),
overdueBySeconds: z.number().int().nonnegative().optional(),
}).describe('Payload for TICKET_SLA_STAGE_BREACHED');
export type TicketSlaStageBreachedEventPayload = z.infer<
typeof ticketSlaStageBreachedEventPayloadSchema
>;
export const ticketApprovalRequestedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
approvalRequestId: z.string().uuid(),
approvalType: z.string().min(1),
requestedByUserId: userIdSchema.optional(),
requestedAt: z.string().datetime().optional(),
notes: z.string().optional(),
}).describe('Payload for TICKET_APPROVAL_REQUESTED');
export type TicketApprovalRequestedEventPayload = z.infer<typeof ticketApprovalRequestedEventPayloadSchema>;
export const ticketApprovalGrantedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
approvalRequestId: z.string().uuid(),
approvedByUserId: userIdSchema.optional(),
approvedAt: z.string().datetime().optional(),
conditions: z.unknown().optional(),
}).describe('Payload for TICKET_APPROVAL_GRANTED');
export type TicketApprovalGrantedEventPayload = z.infer<typeof ticketApprovalGrantedEventPayloadSchema>;
export const ticketApprovalRejectedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
approvalRequestId: z.string().uuid(),
rejectedByUserId: userIdSchema.optional(),
rejectedAt: z.string().datetime().optional(),
reason: z.string().optional(),
}).describe('Payload for TICKET_APPROVAL_REJECTED');
export type TicketApprovalRejectedEventPayload = z.infer<typeof ticketApprovalRejectedEventPayloadSchema>;
// ---------------------------------------------------------------------------
// Legacy ticket events (still used in harness fixtures)
// ---------------------------------------------------------------------------
export const ticketCommentAddedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
commentId: z.string().uuid().optional(),
visibility: z.enum(['public', 'internal']).optional(),
authorId: z.string().uuid().optional(),
authorType: z.enum(['user', 'contact']).optional(),
createdAt: z.string().datetime().optional(),
}).describe('Payload for TICKET_COMMENT_ADDED');
export type TicketCommentAddedEventPayload = z.infer<typeof ticketCommentAddedEventPayloadSchema>;
export const ticketAdditionalAgentAssignedEventPayloadSchema = BaseDomainEventPayloadSchema.extend({
ticketId: ticketIdSchema,
primaryAgentId: userIdSchema,
additionalAgentId: userIdSchema,
assignedByUserId: userIdSchema.optional(),
assignedAt: z.string().datetime().optional(),
}).describe('Payload for TICKET_ADDITIONAL_AGENT_ASSIGNED');
export type TicketAdditionalAgentAssignedEventPayload = z.infer<typeof ticketAdditionalAgentAssignedEventPayloadSchema>;