|
|
import { z } from "zod";
|
|
|
import { CARGO_TYPES } from "@/modules/quote/validation";
|
|
|
import {
|
|
|
normalizeDimUnit,
|
|
|
normalizeWeightUnit,
|
|
|
weightUnitAmbiguousMessage,
|
|
|
} from "@/modules/quote/unit-converter";
|
|
|
|
|
|
const US_ZIP_REGEX = /^\d{5}(-\d{4})?$/;
|
|
|
|
|
|
const addressDraftSchema = z.object({
|
|
|
street: z.string().min(1, "请填写街道地址"),
|
|
|
city: z.string().min(1, "请填写城市"),
|
|
|
state: z.string().length(2, "请填写 2 位州码"),
|
|
|
zip: z
|
|
|
.string()
|
|
|
.refine((v) => v === "" || US_ZIP_REGEX.test(v), "邮编格式无效")
|
|
|
.optional()
|
|
|
.default(""),
|
|
|
});
|
|
|
|
|
|
const apiWeightUnitSchema = z
|
|
|
.string({ errorMap: () => ({ message: "请选择有效的重量单位" }) })
|
|
|
.superRefine((s, ctx) => {
|
|
|
const ambiguous = weightUnitAmbiguousMessage(s);
|
|
|
if (ambiguous) {
|
|
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: ambiguous });
|
|
|
return;
|
|
|
}
|
|
|
if (normalizeWeightUnit(s) === null) {
|
|
|
ctx.addIssue({
|
|
|
code: z.ZodIssueCode.custom,
|
|
|
message: "请选择有效的重量单位(lb / kg / t)",
|
|
|
});
|
|
|
}
|
|
|
})
|
|
|
.transform((s) => normalizeWeightUnit(s)!);
|
|
|
|
|
|
const apiDimUnitSchema = z
|
|
|
.string({ errorMap: () => ({ message: "请选择有效的尺寸单位" }) })
|
|
|
.refine(
|
|
|
(s) => normalizeDimUnit(s) !== null,
|
|
|
"请选择有效的尺寸单位(in / cm / m)",
|
|
|
)
|
|
|
.transform((s) => normalizeDimUnit(s)!);
|
|
|
|
|
|
const positiveFinite = (label: string) =>
|
|
|
z
|
|
|
.number({ invalid_type_error: `请填写${label}` })
|
|
|
.finite(`请填写有效的${label}`)
|
|
|
.positive(`请填写有效的${label}`);
|
|
|
|
|
|
const cargoSchema = z.object({
|
|
|
weight: z.object({
|
|
|
value: positiveFinite("重量"),
|
|
|
unit: apiWeightUnitSchema,
|
|
|
}),
|
|
|
dimensions: z.object({
|
|
|
length: positiveFinite("长度"),
|
|
|
width: positiveFinite("宽度"),
|
|
|
height: positiveFinite("高度"),
|
|
|
unit: apiDimUnitSchema,
|
|
|
}),
|
|
|
pallet_count: z
|
|
|
.number({ invalid_type_error: "请填写托盘数" })
|
|
|
.finite("请填写有效的托盘数")
|
|
|
.int("请填写有效的托盘数"),
|
|
|
cargo_type: z.enum(CARGO_TYPES),
|
|
|
});
|
|
|
|
|
|
const candidateSchema = z.object({
|
|
|
option_id: z.string().min(1),
|
|
|
display_label: z.string().min(1),
|
|
|
formatted_address: z.string().min(1),
|
|
|
street: z.string().min(1),
|
|
|
city: z.string().min(1),
|
|
|
state: z.string().length(2),
|
|
|
zip: z.string().optional().default(""),
|
|
|
selectable: z.boolean().optional(),
|
|
|
unavailable_reason: z.string().optional(),
|
|
|
});
|
|
|
|
|
|
export const hostCandidatesBodySchema = z.object({
|
|
|
pickup_address: addressDraftSchema,
|
|
|
delivery_address: addressDraftSchema,
|
|
|
cargo: cargoSchema,
|
|
|
});
|
|
|
|
|
|
export const hostSubmitBodySchema = z.object({
|
|
|
host_session_id: z.string().uuid("会话标识无效"),
|
|
|
pickup_candidate: candidateSchema,
|
|
|
delivery_candidate: candidateSchema,
|
|
|
});
|