351 lines
12 KiB
TypeScript
Raw Normal View History

/**
* SIM Domain - Schemas
*/
import { z } from "zod";
export const simStatusSchema = z.enum(["active", "suspended", "cancelled", "pending"]);
export const simTypeSchema = z.enum(["standard", "nano", "micro", "esim"]);
export const simDetailsSchema = z.object({
account: z.string(),
status: simStatusSchema,
planCode: z.string(),
planName: z.string(),
simType: simTypeSchema,
iccid: z.string(),
eid: z.string(),
msisdn: z.string(),
imsi: z.string(),
remainingQuotaMb: z.number(),
remainingQuotaKb: z.number(),
voiceMailEnabled: z.boolean(),
callWaitingEnabled: z.boolean(),
internationalRoamingEnabled: z.boolean(),
networkType: z.string(),
activatedAt: z.string().optional(),
expiresAt: z.string().optional(),
});
export const recentDayUsageSchema = z.object({
date: z.string(),
usageKb: z.number(),
usageMb: z.number(),
});
export const simUsageSchema = z.object({
account: z.string(),
todayUsageMb: z.number(),
todayUsageKb: z.number(),
monthlyUsageMb: z.number().optional(),
monthlyUsageKb: z.number().optional(),
recentDaysUsage: z.array(recentDayUsageSchema),
isBlacklisted: z.boolean(),
lastUpdated: z.string().optional(),
});
export const simTopUpHistoryEntrySchema = z.object({
quotaKb: z.number(),
quotaMb: z.number(),
addedDate: z.string(),
expiryDate: z.string(),
campaignCode: z.string(),
});
export const simTopUpHistorySchema = z.object({
account: z.string(),
totalAdditions: z.number(),
additionCount: z.number(),
history: z.array(simTopUpHistoryEntrySchema),
});
/**
* Combined SIM info payload (details + usage)
*/
export const simInfoSchema = z.object({
details: simDetailsSchema,
usage: simUsageSchema,
});
// ============================================================================
// SIM Management Request Schemas
// ============================================================================
export const simTopUpRequestSchema = z.object({
quotaMb: z
.number()
.int()
.min(100, "Quota must be at least 100MB")
.max(51200, "Quota must be 50GB or less"),
});
export const simPlanChangeRequestSchema = z.object({
newPlanCode: z.string().min(1, "New plan code is required"),
assignGlobalIp: z.boolean().optional(),
scheduledAt: z
.string()
.regex(/^\d{8}$/, "Scheduled date must be in YYYYMMDD format")
.optional(),
});
export const simCancelRequestSchema = z.object({
scheduledAt: z
.string()
.regex(/^\d{8}$/, "Scheduled date must be in YYYYMMDD format")
.optional(),
});
export const simTopUpHistoryRequestSchema = z.object({
fromDate: z
.string()
.regex(/^\d{8}$/, "From date must be in YYYYMMDD format"),
toDate: z
.string()
.regex(/^\d{8}$/, "To date must be in YYYYMMDD format"),
});
export const simFeaturesUpdateRequestSchema = z.object({
voiceMailEnabled: z.boolean().optional(),
callWaitingEnabled: z.boolean().optional(),
internationalRoamingEnabled: z.boolean().optional(),
networkType: z.enum(["4G", "5G"]).optional(),
});
export const simReissueRequestSchema = z.object({
newEid: z
.string()
.regex(/^\d{32}$/, "EID must be exactly 32 digits")
.optional(),
});
const simMnpFormSchema = z.object({
reservationNumber: z.string().min(1, "Reservation number is required"),
expiryDate: z.string().regex(/^\d{8}$/, "Expiry date must be in YYYYMMDD format"),
phoneNumber: z.string().min(1, "Phone number is required"),
mvnoAccountNumber: z.string().optional(),
portingLastName: z.string().optional(),
portingFirstName: z.string().optional(),
portingLastNameKatakana: z.string().optional(),
portingFirstNameKatakana: z.string().optional(),
portingGender: z.string().optional(),
portingDateOfBirth: z
.string()
.regex(/^\d{8}$/, "Birthday must be in YYYYMMDD format")
.optional(),
});
export const simCardTypeSchema = z.enum(["eSIM", "Physical SIM"]);
export const simActivationTypeSchema = z.enum(["Immediate", "Scheduled"]);
export const simConfigureFormSchema = z
.object({
simType: simCardTypeSchema,
eid: z
.string()
.min(15, "EID must be at least 15 characters")
.max(32, "EID must be at most 32 characters")
.optional(),
selectedAddons: z.array(z.string()).default([]),
activationType: simActivationTypeSchema,
scheduledActivationDate: z
.string()
.regex(/^\d{8}$/, "Scheduled date must be in YYYYMMDD format")
.optional(),
wantsMnp: z.boolean().default(false),
mnpData: simMnpFormSchema.optional(),
})
.superRefine((data, ctx) => {
if (data.simType === "eSIM" && (!data.eid || data.eid.trim().length < 15)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["eid"],
message: "EID is required for eSIM configuration",
});
}
if (data.activationType === "Scheduled" && !data.scheduledActivationDate) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["scheduledActivationDate"],
message: "Scheduled activation date is required when activation type is Scheduled",
});
}
if (data.wantsMnp) {
if (!data.mnpData) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["mnpData"],
message: "MNP data is required when porting is selected",
});
return;
}
const { reservationNumber, expiryDate, phoneNumber } = data.mnpData;
if (!reservationNumber) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["mnpData", "reservationNumber"],
message: "Reservation number is required",
});
}
if (!expiryDate) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["mnpData", "expiryDate"],
message: "Reservation expiry date is required",
});
}
if (!phoneNumber) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["mnpData", "phoneNumber"],
message: "Phone number is required for porting",
});
}
}
});
// ============================================================================
// SIM Order Activation Schemas
// ============================================================================
export const simOrderActivationMnpSchema = z.object({
reserveNumber: z.string().min(1, "Reserve number is required"),
reserveExpireDate: z.string().regex(/^\d{8}$/, "Reserve expire date must be in YYYYMMDD format"),
account: z.string().optional(),
firstnameKanji: z.string().optional(),
lastnameKanji: z.string().optional(),
firstnameZenKana: z.string().optional(),
lastnameZenKana: z.string().optional(),
gender: z.string().optional(),
birthday: z.string().regex(/^\d{8}$/, "Birthday must be in YYYYMMDD format").optional(),
});
export const simOrderActivationAddonsSchema = z.object({
voiceMail: z.boolean().optional(),
callWaiting: z.boolean().optional(),
});
export const simOrderActivationRequestSchema = z.object({
planSku: z.string().min(1, "Plan SKU is required"),
simType: simCardTypeSchema,
eid: z.string().min(15, "EID must be at least 15 characters").optional(),
activationType: simActivationTypeSchema,
scheduledAt: z.string().regex(/^\d{8}$/, "Scheduled date must be in YYYYMMDD format").optional(),
addons: simOrderActivationAddonsSchema.optional(),
mnp: simOrderActivationMnpSchema.optional(),
msisdn: z.string().min(1, "Phone number (msisdn) is required"),
oneTimeAmountJpy: z.number().nonnegative("One-time amount must be non-negative"),
monthlyAmountJpy: z.number().nonnegative("Monthly amount must be non-negative"),
}).refine(
(data) => {
// If simType is eSIM, eid is required
if (data.simType === "eSIM" && (!data.eid || data.eid.length < 15)) {
return false;
}
return true;
},
{
message: "EID is required for eSIM and must be at least 15 characters",
path: ["eid"],
}
).refine(
(data) => {
// If activationType is Scheduled, scheduledAt is required
if (data.activationType === "Scheduled" && !data.scheduledAt) {
return false;
}
return true;
},
{
message: "Scheduled date is required for Scheduled activation",
path: ["scheduledAt"],
}
);
export type SimOrderActivationRequest = z.infer<typeof simOrderActivationRequestSchema>;
export type SimOrderActivationMnp = z.infer<typeof simOrderActivationMnpSchema>;
export type SimOrderActivationAddons = z.infer<typeof simOrderActivationAddonsSchema>;
// Legacy aliases for backward compatibility
export const simTopupRequestSchema = simTopUpRequestSchema;
export type SimTopupRequest = SimTopUpRequest;
export const simChangePlanRequestSchema = simPlanChangeRequestSchema;
export type SimChangePlanRequest = SimPlanChangeRequest;
export const simFeaturesRequestSchema = simFeaturesUpdateRequestSchema;
export type SimFeaturesRequest = SimFeaturesUpdateRequest;
// ============================================================================
// Inferred Types from Schemas (Schema-First Approach)
// ============================================================================
export type SimStatus = z.infer<typeof simStatusSchema>;
export type SimType = z.infer<typeof simTypeSchema>;
export type SimDetails = z.infer<typeof simDetailsSchema>;
export type RecentDayUsage = z.infer<typeof recentDayUsageSchema>;
export type SimUsage = z.infer<typeof simUsageSchema>;
export type SimTopUpHistoryEntry = z.infer<typeof simTopUpHistoryEntrySchema>;
export type SimTopUpHistory = z.infer<typeof simTopUpHistorySchema>;
export type SimInfo = z.infer<typeof simInfoSchema>;
// Request types (derived from request schemas)
export type SimTopUpRequest = z.infer<typeof simTopUpRequestSchema>;
export type SimPlanChangeRequest = z.infer<typeof simPlanChangeRequestSchema>;
export type SimCancelRequest = z.infer<typeof simCancelRequestSchema>;
export type SimTopUpHistoryRequest = z.infer<typeof simTopUpHistoryRequestSchema>;
export type SimFeaturesUpdateRequest = z.infer<typeof simFeaturesUpdateRequestSchema>;
export type SimReissueRequest = z.infer<typeof simReissueRequestSchema>;
export type SimConfigureFormData = z.infer<typeof simConfigureFormSchema>;
export type SimCardType = z.infer<typeof simCardTypeSchema>;
export type ActivationType = z.infer<typeof simActivationTypeSchema>;
export type MnpData = z.infer<typeof simMnpFormSchema>;
export interface SimConfigureFormToRequestOptions {
planSku: string;
msisdn: string;
monthlyAmountJpy: number;
oneTimeAmountJpy: number;
addons?: SimOrderActivationAddons;
}
export function simConfigureFormToRequest(
formData: SimConfigureFormData,
options: SimConfigureFormToRequestOptions
): SimOrderActivationRequest {
const scheduledAt =
formData.activationType === "Scheduled" ? formData.scheduledActivationDate : undefined;
const mnp =
formData.wantsMnp && formData.mnpData
? {
reserveNumber: formData.mnpData.reservationNumber,
reserveExpireDate: formData.mnpData.expiryDate,
account: formData.mnpData.mvnoAccountNumber ?? undefined,
firstnameKanji: formData.mnpData.portingFirstName ?? undefined,
lastnameKanji: formData.mnpData.portingLastName ?? undefined,
firstnameZenKana: formData.mnpData.portingFirstNameKatakana ?? undefined,
lastnameZenKana: formData.mnpData.portingLastNameKatakana ?? undefined,
gender: formData.mnpData.portingGender ?? undefined,
birthday: formData.mnpData.portingDateOfBirth ?? undefined,
}
: undefined;
return simOrderActivationRequestSchema.parse({
planSku: options.planSku,
simType: formData.simType,
eid: formData.simType === "eSIM" ? formData.eid?.trim() || undefined : undefined,
activationType: formData.activationType,
scheduledAt,
addons: options.addons,
mnp,
msisdn: options.msisdn,
oneTimeAmountJpy: options.oneTimeAmountJpy,
monthlyAmountJpy: options.monthlyAmountJpy,
});
}