|
|
export type AddressInput = {
|
|
|
street: string;
|
|
|
city: string;
|
|
|
state: string;
|
|
|
zip: string;
|
|
|
place_id: string;
|
|
|
formatted_address: string;
|
|
|
selected_from_suggestions: boolean;
|
|
|
/** MotherShip 联想候选项 ID(用户确认后必填) */
|
|
|
mothership_option_id?: string;
|
|
|
/** MotherShip 下拉展示文案,RPA 精确点选 */
|
|
|
mothership_display_label?: string;
|
|
|
/** 用户已从 MotherShip 多候选中确认 */
|
|
|
selected_from_mothership?: boolean;
|
|
|
};
|
|
|
|
|
|
export type WeightInput = {
|
|
|
value: number;
|
|
|
unit: "lb" | "kg";
|
|
|
};
|
|
|
|
|
|
export type DimensionsInput = {
|
|
|
length: number;
|
|
|
width: number;
|
|
|
height: number;
|
|
|
unit: "in" | "cm";
|
|
|
};
|
|
|
|
|
|
export type QuoteRequestBody = {
|
|
|
request_id: string;
|
|
|
customer_id: string;
|
|
|
pickup_address: AddressInput;
|
|
|
delivery_address: AddressInput;
|
|
|
weight: WeightInput;
|
|
|
dimensions: DimensionsInput;
|
|
|
pallet_count: number;
|
|
|
cargo_type: string;
|
|
|
service_level?: "standard" | "guaranteed";
|
|
|
rate_option?: "lowest" | "fastest";
|
|
|
display_unit?: "imperial" | "metric";
|
|
|
/** 候选 RPA 会话 ID(可选,10 分钟内有效) */
|
|
|
quote_session_id?: string;
|
|
|
};
|
|
|
|
|
|
export type NormalizedCargo = {
|
|
|
requestId: string;
|
|
|
customerId: string;
|
|
|
pickupAddress: AddressInput;
|
|
|
deliveryAddress: AddressInput;
|
|
|
weightLb: number;
|
|
|
dimLIn: number;
|
|
|
dimWIn: number;
|
|
|
dimHIn: number;
|
|
|
palletCount: number;
|
|
|
cargoType: string;
|
|
|
serviceLevel: "standard" | "guaranteed";
|
|
|
rateOption: "lowest" | "fastest";
|
|
|
cargoHash: string;
|
|
|
pickupSerialized: string;
|
|
|
deliverySerialized: string;
|
|
|
quoteSessionId?: string;
|
|
|
};
|
|
|
|
|
|
export type QuoteSubmitResult = {
|
|
|
quoteId: string;
|
|
|
status: "processing" | "done";
|
|
|
cached?: boolean;
|
|
|
};
|
|
|
|
|
|
export type L2CachePayload = {
|
|
|
quotes: Array<{
|
|
|
service_level: string;
|
|
|
rate_option: string;
|
|
|
carrier: string;
|
|
|
transit_days: string;
|
|
|
transit_description: string;
|
|
|
raw_freight: number;
|
|
|
surcharges: number;
|
|
|
raw_total: number;
|
|
|
}>;
|
|
|
};
|
|
|
|
|
|
export class ValidationError extends Error {
|
|
|
readonly code = "VALIDATION_FAILED";
|
|
|
|
|
|
constructor(message: string) {
|
|
|
super(message);
|
|
|
this.name = "ValidationError";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** SQL 注入等安全校验失败(task-105 / TC-504) */
|
|
|
export class SecurityValidationError extends ValidationError {
|
|
|
readonly isSecurity = true;
|
|
|
|
|
|
constructor(message: string) {
|
|
|
super(message);
|
|
|
this.name = "SecurityValidationError";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
export class QuoteIdConflictError extends Error {
|
|
|
readonly code = "INTERNAL_ERROR";
|
|
|
|
|
|
constructor(message: string) {
|
|
|
super(message);
|
|
|
this.name = "QuoteIdConflictError";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
export class RpaDataInvalidError extends Error {
|
|
|
readonly code = "RPA_DATA_INVALID";
|
|
|
|
|
|
constructor(message: string) {
|
|
|
super(message);
|
|
|
this.name = "RpaDataInvalidError";
|
|
|
}
|
|
|
}
|