|
|
import type { MothershipAddressCandidate } from "@/modules/address/types";
|
|
|
import type { QuoteRequest } from "@/modules/providers/quote-provider";
|
|
|
import { RpaError } from "@/modules/rpa/errors";
|
|
|
import { RPAContext } from "@/workers/rpa/kernel/context";
|
|
|
import { fillAddressStep } from "@/workers/rpa/kernel/steps/address-step";
|
|
|
import {
|
|
|
parkQuoteSession,
|
|
|
prepareParkedQuotePage,
|
|
|
takeParkedQuoteSession,
|
|
|
} from "@/workers/rpa/parked-quote-session";
|
|
|
import { captureRpaDebugScreenshot } from "@/workers/rpa/page-prep";
|
|
|
import { persistContext } from "@/workers/rpa/session-manager";
|
|
|
|
|
|
function candidateToAddressFields(
|
|
|
candidate: MothershipAddressCandidate,
|
|
|
): QuoteRequest["pickup"] {
|
|
|
return {
|
|
|
street: candidate.street,
|
|
|
city: candidate.city,
|
|
|
state: candidate.state,
|
|
|
zip: candidate.zip,
|
|
|
placeId: candidate.option_id,
|
|
|
formattedAddress: candidate.formatted_address,
|
|
|
selectedFromSuggestions: true,
|
|
|
mothershipOptionId: candidate.option_id,
|
|
|
mothershipDisplayLabel: candidate.display_label,
|
|
|
selectedFromMothership: true,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function minimalQuoteRequest(
|
|
|
sessionId: string,
|
|
|
pickup: MothershipAddressCandidate,
|
|
|
delivery: MothershipAddressCandidate,
|
|
|
): QuoteRequest {
|
|
|
return {
|
|
|
cargoHash: "session-confirm",
|
|
|
quoteSessionId: sessionId,
|
|
|
pickup: candidateToAddressFields(pickup),
|
|
|
delivery: candidateToAddressFields(delivery),
|
|
|
weightLb: 1,
|
|
|
dimsIn: { l: 48, w: 40, h: 48 },
|
|
|
palletCount: 1,
|
|
|
cargoType: "general_freight",
|
|
|
};
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 用户确认候选后:在同驻留页点选双地址并重新 park,
|
|
|
* 后续询价 job 可跳过 fillAddress + 跳过 openQuotePage。
|
|
|
*/
|
|
|
export async function confirmAddressesOnParkedSession(input: {
|
|
|
sessionId: string;
|
|
|
pickup: MothershipAddressCandidate;
|
|
|
delivery: MothershipAddressCandidate;
|
|
|
}): Promise<void> {
|
|
|
const parked = await takeParkedQuoteSession(input.sessionId, {
|
|
|
waitMs: 3_000,
|
|
|
});
|
|
|
if (!parked) {
|
|
|
throw new RpaError(
|
|
|
"PAGE_LOAD_TIMEOUT",
|
|
|
"驻留浏览器会话不可用(请重新获取地址候选,并确认 RPA Worker 已启动且未走 spawn 子进程)",
|
|
|
{ retryable: true },
|
|
|
);
|
|
|
}
|
|
|
|
|
|
const started = Date.now();
|
|
|
try {
|
|
|
await prepareParkedQuotePage(parked.page);
|
|
|
const ctx = RPAContext.fromSession(parked.page, parked.context);
|
|
|
await fillAddressStep(
|
|
|
ctx,
|
|
|
minimalQuoteRequest(input.sessionId, input.pickup, input.delivery),
|
|
|
);
|
|
|
await persistContext(parked.context);
|
|
|
await parkQuoteSession(input.sessionId, parked.page, parked.context);
|
|
|
console.log(
|
|
|
`[parked-session] 地址已确认并重新驻留 session=${input.sessionId.slice(0, 8)}… ${Date.now() - started}ms`,
|
|
|
);
|
|
|
} catch (error) {
|
|
|
await captureRpaDebugScreenshot(parked.page, "session-confirm-failure");
|
|
|
await parked.page.close().catch(() => undefined);
|
|
|
await parked.context.close().catch(() => undefined);
|
|
|
throw error;
|
|
|
}
|
|
|
}
|