269 lines
9.1 KiB
TypeScript

/**
* Orders Domain - Schemas
*
* Zod schemas for runtime validation of order data.
*/
import { z } from "zod";
// ============================================================================
// Fulfillment Order Schemas
// ============================================================================
export const fulfillmentOrderProductSchema = z.object({
id: z.string().optional(),
sku: z.string().optional(),
name: z.string().optional(),
itemClass: z.string().optional(),
whmcsProductId: z.string().optional(),
billingCycle: z.string().optional(),
});
export const fulfillmentOrderItemSchema = z.object({
id: z.string(),
orderId: z.string(),
quantity: z.number().int().min(1),
product: fulfillmentOrderProductSchema.nullable(),
});
export const fulfillmentOrderDetailsSchema = z.object({
id: z.string(),
orderNumber: z.string().optional(),
orderType: z.string().optional(),
items: z.array(fulfillmentOrderItemSchema),
});
// ============================================================================
// Order Item Summary Schema
// ============================================================================
export const orderItemSummarySchema = z.object({
productName: z.string().optional(),
name: z.string().optional(),
sku: z.string().optional(),
status: z.string().optional(),
billingCycle: z.string().optional(),
itemClass: z.string().optional(),
quantity: z.number().int().min(0).optional(),
unitPrice: z.number().optional(),
totalPrice: z.number().optional(),
});
// ============================================================================
// Order Item Details Schema
// ============================================================================
export const orderItemDetailsSchema = z.object({
id: z.string(),
orderId: z.string(),
quantity: z.number().int().min(1),
unitPrice: z.number().optional(),
totalPrice: z.number().optional(),
billingCycle: z.string().optional(),
product: z.object({
id: z.string().optional(),
name: z.string().optional(),
sku: z.string().optional(),
itemClass: z.string().optional(),
whmcsProductId: z.string().optional(),
internetOfferingType: z.string().optional(),
internetPlanTier: z.string().optional(),
vpnRegion: z.string().optional(),
}).optional(),
});
// ============================================================================
// Order Summary Schema
// ============================================================================
export const orderSummarySchema = z.object({
id: z.string(),
orderNumber: z.string(),
status: z.string(),
orderType: z.string().optional(),
effectiveDate: z.string(), // IsoDateTimeString
totalAmount: z.number().optional(),
createdDate: z.string(), // IsoDateTimeString
lastModifiedDate: z.string(), // IsoDateTimeString
whmcsOrderId: z.string().optional(),
activationStatus: z.string().optional(),
itemsSummary: z.array(orderItemSummarySchema),
});
// ============================================================================
// Order Details Schema
// ============================================================================
export const orderDetailsSchema = orderSummarySchema.extend({
accountId: z.string().optional(),
accountName: z.string().optional(),
pricebook2Id: z.string().optional(),
activationType: z.string().optional(),
activationStatus: z.string().optional(),
activationScheduledAt: z.string().optional(), // IsoDateTimeString
activationErrorCode: z.string().optional(),
activationErrorMessage: z.string().optional(),
activatedDate: z.string().optional(), // IsoDateTimeString
items: z.array(orderItemDetailsSchema),
});
// ============================================================================
// Query Parameter Schemas
// ============================================================================
/**
* Schema for order query parameters
*/
export const orderQueryParamsSchema = z.object({
page: z.coerce.number().int().positive().optional(),
limit: z.coerce.number().int().positive().max(100).optional(),
status: z.string().optional(),
orderType: z.string().optional(),
});
// Duplicate - remove this line
// export type OrderQueryParams = z.infer<typeof orderQueryParamsSchema>;
// ============================================================================
// Order Creation Schemas
// ============================================================================
const orderConfigurationsAddressSchema = z.object({
street: z.string().nullable().optional(),
streetLine2: z.string().nullable().optional(),
city: z.string().nullable().optional(),
state: z.string().nullable().optional(),
postalCode: z.string().nullable().optional(),
country: z.string().nullable().optional(),
});
export const orderConfigurationsSchema = z.object({
activationType: z.enum(["Immediate", "Scheduled"]).optional(),
scheduledAt: z.string().optional(),
accessMode: z.enum(["IPoE-BYOR", "IPoE-HGW", "PPPoE"]).optional(),
simType: z.enum(["eSIM", "Physical SIM"]).optional(),
eid: z.string().optional(),
isMnp: z.string().optional(),
mnpNumber: z.string().optional(),
mnpExpiry: z.string().optional(),
mnpPhone: z.string().optional(),
mvnoAccountNumber: z.string().optional(),
portingLastName: z.string().optional(),
portingFirstName: z.string().optional(),
portingLastNameKatakana: z.string().optional(),
portingFirstNameKatakana: z.string().optional(),
portingGender: z.enum(["Male", "Female", "Corporate/Other"]).optional(),
portingDateOfBirth: z.string().optional(),
address: orderConfigurationsAddressSchema.optional(),
});
const baseCreateOrderSchema = z.object({
orderType: z.enum(["Internet", "SIM", "VPN", "Other"]),
skus: z.array(z.string()),
configurations: orderConfigurationsSchema.optional(),
});
export const createOrderRequestSchema = baseCreateOrderSchema;
export const orderBusinessValidationSchema =
baseCreateOrderSchema
.extend({
userId: z.string().uuid(),
opportunityId: z.string().optional(),
})
.refine(
(data) => {
if (data.orderType === "Internet") {
const mainServiceSkus = data.skus.filter(sku => {
const upperSku = sku.toUpperCase();
return (
!upperSku.includes("INSTALL") &&
!upperSku.includes("ADDON") &&
!upperSku.includes("ACTIVATION") &&
!upperSku.includes("FEE")
);
});
return mainServiceSkus.length >= 1;
}
return true;
},
{
message: "Internet orders must have at least one main service SKU (non-installation, non-addon)",
path: ["skus"],
}
)
.refine(
(data) => {
if (data.orderType === "SIM" && data.configurations) {
return data.configurations.simType !== undefined;
}
return true;
},
{
message: "SIM orders must specify SIM type",
path: ["configurations", "simType"],
}
)
.refine(
(data) => {
if (data.configurations?.simType === "eSIM") {
return data.configurations.eid !== undefined && data.configurations.eid.length > 0;
}
return true;
},
{
message: "eSIM orders must provide EID",
path: ["configurations", "eid"],
}
)
.refine(
(data) => {
if (data.configurations?.isMnp === "true") {
const required = [
"mnpNumber",
"portingLastName",
"portingFirstName",
] as const;
return required.every(field => data.configurations?.[field] !== undefined);
}
return true;
},
{
message: "MNP orders must provide porting information",
path: ["configurations"],
}
);
export const sfOrderIdParamSchema = z.object({
sfOrderId: z
.string()
.length(18, "Salesforce order ID must be 18 characters")
.regex(/^[A-Za-z0-9]+$/, "Salesforce order ID must be alphanumeric"),
});
export type SfOrderIdParam = z.infer<typeof sfOrderIdParamSchema>;
// ============================================================================
// Inferred Types from Schemas (Schema-First Approach)
// ============================================================================
// Fulfillment order types
export type FulfillmentOrderProduct = z.infer<typeof fulfillmentOrderProductSchema>;
export type FulfillmentOrderItem = z.infer<typeof fulfillmentOrderItemSchema>;
export type FulfillmentOrderDetails = z.infer<typeof fulfillmentOrderDetailsSchema>;
// Order item types
export type OrderItemSummary = z.infer<typeof orderItemSummarySchema>;
export type OrderItemDetails = z.infer<typeof orderItemDetailsSchema>;
// Order types
export type OrderSummary = z.infer<typeof orderSummarySchema>;
export type OrderDetails = z.infer<typeof orderDetailsSchema>;
// Query and creation types
export type OrderQueryParams = z.infer<typeof orderQueryParamsSchema>;
export type OrderConfigurationsAddress = z.infer<typeof orderConfigurationsAddressSchema>;
export type OrderConfigurations = z.infer<typeof orderConfigurationsSchema>;
export type CreateOrderRequest = z.infer<typeof createOrderRequestSchema>;
export type OrderBusinessValidation = z.infer<typeof orderBusinessValidationSchema>;