barsa 7da032fd95 refactor: tighten support schema to use defined enum validators
Replace loose z.string() fields in supportCaseSchema and supportCaseFilterSchema
with the already-defined enum schemas (status, priority, category). Add JSDoc
to intentional escape hatches in customer contract interfaces. Fix portal
type assertions for the stricter filter types.
2026-03-02 13:11:10 +09:00

190 lines
6.3 KiB
TypeScript

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<typeof supportCaseStatusSchema>;
export type SupportCasePriority = z.infer<typeof supportCasePrioritySchema>;
export type SupportCaseCategory = z.infer<typeof supportCaseCategorySchema>;
export type SupportCase = z.infer<typeof supportCaseSchema>;
export type SupportCaseSummary = z.infer<typeof supportCaseSummarySchema>;
export type SupportCaseList = z.infer<typeof supportCaseListSchema>;
export type SupportCaseFilter = z.infer<typeof supportCaseFilterSchema>;
export type CreateCaseRequest = z.infer<typeof createCaseRequestSchema>;
export type CreateCaseResponse = z.infer<typeof createCaseResponseSchema>;
export type PublicContactRequest = z.infer<typeof publicContactRequestSchema>;
export type CaseMessageType = z.infer<typeof caseMessageTypeSchema>;
export type CaseMessageDirection = z.infer<typeof caseMessageDirectionSchema>;
export type CaseMessage = z.infer<typeof caseMessageSchema>;
export type CaseMessageList = z.infer<typeof caseMessageListSchema>;
export type AddCaseCommentRequest = z.infer<typeof addCaseCommentRequestSchema>;
export type AddCaseCommentResponse = z.infer<typeof addCaseCommentResponseSchema>;