|
|
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: import("@/modules/quote/unit-converter").ApiWeightUnit;
|
|
|
};
|
|
|
|
|
|
export type DimensionsInput = {
|
|
|
length: number;
|
|
|
width: number;
|
|
|
height: number;
|
|
|
unit: import("@/modules/quote/unit-converter").ApiDimUnit;
|
|
|
};
|
|
|
|
|
|
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;
|
|
|
/** MotherShip 登录态:提货附加服务 id */
|
|
|
pickup_accessorials?: string[];
|
|
|
/** MotherShip 登录态:派送附加服务 id */
|
|
|
delivery_accessorials?: string[];
|
|
|
/** MotherShip 登录态:可提货日期 YYYY-MM-DD */
|
|
|
ready_date?: string;
|
|
|
/** MotherShip 登录态:可提货时刻,如 3:00 PM */
|
|
|
ready_time?: string;
|
|
|
/** MotherShip 登录态:多行货物 */
|
|
|
cargo_lines?: Array<{
|
|
|
cargo_type: string;
|
|
|
quantity: number;
|
|
|
weight_lb: number;
|
|
|
length_in: number;
|
|
|
width_in: number;
|
|
|
height_in: number;
|
|
|
}>;
|
|
|
mothership_details?: {
|
|
|
pickup: {
|
|
|
company_name?: string;
|
|
|
suite?: string;
|
|
|
contact_first?: string;
|
|
|
contact_last?: string;
|
|
|
contact_email?: string;
|
|
|
contact_phone?: string;
|
|
|
reference?: string;
|
|
|
notes?: string;
|
|
|
opens_at?: string;
|
|
|
closes_at?: string;
|
|
|
};
|
|
|
delivery: {
|
|
|
company_name?: string;
|
|
|
suite?: string;
|
|
|
contact_first?: string;
|
|
|
contact_last?: string;
|
|
|
contact_email?: string;
|
|
|
contact_phone?: string;
|
|
|
reference?: string;
|
|
|
notes?: string;
|
|
|
opens_at?: string;
|
|
|
closes_at?: string;
|
|
|
};
|
|
|
request_delivery_appointment?: boolean;
|
|
|
fba_number?: string;
|
|
|
fba_po_number?: string;
|
|
|
cargo?: Array<{
|
|
|
piece_count_type?: string;
|
|
|
piece_count_qty?: number;
|
|
|
description?: string;
|
|
|
nmfc?: string;
|
|
|
hazmat?: boolean;
|
|
|
alcohol?: boolean;
|
|
|
tobacco?: boolean;
|
|
|
}>;
|
|
|
};
|
|
|
};
|
|
|
|
|
|
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;
|
|
|
/** MotherShip 登录态附加服务 / 可提货时间(可选) */
|
|
|
pickupAccessorials?: string[];
|
|
|
deliveryAccessorials?: string[];
|
|
|
readyDate?: string;
|
|
|
readyTime?: string;
|
|
|
/** MotherShip 登录态多行货物 */
|
|
|
cargoLines?: Array<{
|
|
|
cargoType: string;
|
|
|
quantity: number;
|
|
|
weightLb: number;
|
|
|
lengthIn: number;
|
|
|
widthIn: number;
|
|
|
heightIn: number;
|
|
|
}>;
|
|
|
mothershipDetails?: QuoteRequestBody["mothership_details"];
|
|
|
};
|
|
|
|
|
|
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";
|
|
|
}
|
|
|
}
|