import { z } from "zod"; import { SUPPORT_CASE_STATUS, SUPPORT_CASE_PRIORITY, SUPPORT_CASE_CATEGORY } from "./contract.js"; /** * Portal status values - customer-friendly statuses only * * Internal Salesforce statuses are mapped to these customer-facing values: * - "新規" → New * - "対応中", "Awaiting Approval", "VPN Pending", "Pending" → In Progress * - "完了済み" (Replied) → Awaiting Customer * - "Closed" → Closed */ const supportCaseStatusValues = [ SUPPORT_CASE_STATUS.NEW, SUPPORT_CASE_STATUS.IN_PROGRESS, SUPPORT_CASE_STATUS.AWAITING_CUSTOMER, SUPPORT_CASE_STATUS.CLOSED, ] as const; /** * Portal priority values (mapped from Salesforce Japanese API names) */ const supportCasePriorityValues = [ SUPPORT_CASE_PRIORITY.LOW, SUPPORT_CASE_PRIORITY.MEDIUM, SUPPORT_CASE_PRIORITY.HIGH, ] as const; const supportCaseCategoryValues = [ SUPPORT_CASE_CATEGORY.TECHNICAL, SUPPORT_CASE_CATEGORY.BILLING, SUPPORT_CASE_CATEGORY.GENERAL, SUPPORT_CASE_CATEGORY.FEATURE_REQUEST, ] as const; export const supportCaseStatusSchema = z.enum(supportCaseStatusValues); export const supportCasePrioritySchema = z.enum(supportCasePriorityValues); export const supportCaseCategorySchema = z.enum(supportCaseCategoryValues); /** * Support case schema - compatible with Salesforce Case object * ID is a string (Salesforce ID format: 15 or 18 char alphanumeric) */ export const supportCaseSchema = z.object({ id: z.string().min(15).max(18), caseNumber: z.string(), subject: z.string().min(1), status: supportCaseStatusSchema, priority: supportCasePrioritySchema, category: supportCaseCategorySchema.nullable(), createdAt: z.string(), updatedAt: z.string(), closedAt: z.string().nullable(), description: z.string(), }); export const supportCaseSummarySchema = z.object({ total: z.number().int().nonnegative(), open: z.number().int().nonnegative(), highPriority: z.number().int().nonnegative(), resolved: z.number().int().nonnegative(), }); export const supportCaseListSchema = z.object({ cases: z.array(supportCaseSchema), summary: supportCaseSummarySchema, }); export const supportCaseFilterSchema = z .object({ status: supportCaseStatusSchema.optional(), priority: supportCasePrioritySchema.optional(), category: supportCaseCategorySchema.optional(), search: z.string().trim().min(1).optional(), }) .default({}); /** * Request schema for creating a new support case */ export const createCaseRequestSchema = z.object({ subject: z.string().min(1).max(255), description: z.string().min(1).max(32000), category: supportCaseCategorySchema.optional(), priority: supportCasePrioritySchema.optional(), }); /** * Response schema for case creation */ export const createCaseResponseSchema = z.object({ id: z.string(), caseNumber: z.string(), }); /** * Public contact form schema (unauthenticated) */ export const publicContactRequestSchema = z.object({ name: z.string().min(1, "Name is required"), email: z.string().email("Valid email required"), phone: z.string().optional(), subject: z.string().min(1, "Subject is required"), message: z.string().min(10, "Message must be at least 10 characters"), }); // ============================================================================ // Case Message Schemas (for conversation view) // ============================================================================ /** * Message type - either from email exchange or case comment */ export const caseMessageTypeSchema = z.enum(["email", "comment"]); /** * Message direction for emails */ export const caseMessageDirectionSchema = z.enum(["inbound", "outbound"]); /** * Unified case message schema - represents either an EmailMessage or CaseComment * Used for displaying conversation threads in the portal */ export const caseMessageSchema = z.object({ /** Unique identifier (EmailMessage.Id or CaseComment.Id) */ id: z.string(), /** Message type: email or comment */ type: caseMessageTypeSchema, /** Message body/content */ body: z.string(), /** Who sent/wrote the message */ author: z.object({ name: z.string(), email: z.string().nullable(), isCustomer: z.boolean(), }), /** When the message was created/sent */ createdAt: z.string(), /** For emails: inbound (customer→agent) or outbound (agent→customer) */ direction: caseMessageDirectionSchema.nullable(), /** Whether the message has attachments (for emails) */ hasAttachment: z.boolean().optional(), }); /** * List of case messages for conversation view */ export const caseMessageListSchema = z.object({ messages: z.array(caseMessageSchema), /** Case thread identifier for email threading */ threadId: z.string().nullable(), }); /** * Request schema for adding a comment to a case */ export const addCaseCommentRequestSchema = z.object({ body: z.string().min(1, "Message is required").max(32000), }); /** * Response schema for adding a comment */ export const addCaseCommentResponseSchema = z.object({ id: z.string(), createdAt: z.string(), }); // ============================================================================ // Type Exports // ============================================================================ export type SupportCaseStatus = z.infer; export type SupportCasePriority = z.infer; export type SupportCaseCategory = z.infer; export type SupportCase = z.infer; export type SupportCaseSummary = z.infer; export type SupportCaseList = z.infer; export type SupportCaseFilter = z.infer; export type CreateCaseRequest = z.infer; export type CreateCaseResponse = z.infer; export type PublicContactRequest = z.infer; export type CaseMessageType = z.infer; export type CaseMessageDirection = z.infer; export type CaseMessage = z.infer; export type CaseMessageList = z.infer; export type AddCaseCommentRequest = z.infer; export type AddCaseCommentResponse = z.infer;