You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 lines
4.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import type { Page } from "playwright";
import { isRpaAddressMockMode } from "@/lib/rpa/address-mode";
import {
isRpaHumanSubmitStrict,
isRpaAddressRushMode,
isRpaBlindAddressFlow,
} from "@/lib/rpa/env";
import { RpaError } from "@/modules/rpa/errors";
import {
assertCargoCommittedState,
assertBothAddressesLocked,
} from "@/workers/rpa/cargo-lock";
import {
debugBusinessStateSnapshot,
waitForBusinessReadyState,
} from "@/workers/rpa/quote-capture/quote-business-ready";
import { waitForPostSubmitDestination } from "@/workers/rpa/quote-capture/post-submit-navigation";
import { clickWidgetQuoteSubmitButton } from "@/workers/rpa/quote-capture/click-widget-quote-submit";
import type { QuoteSubmitOptions } from "@/workers/rpa/quote-capture/types";
const WIDGET_SELECTOR = "#react-quoter-landing-plugin";
function submitClickOptions(
options?: QuoteSubmitOptions,
): Pick<QuoteSubmitOptions, "blindAddressFlow"> | undefined {
if (!options?.blindAddressFlow) {
return undefined;
}
return { blindAddressFlow: true };
}
/**
* DOM 仅用于提交动作;填表由 kernel fillAddress / fillCargo 完成。
* submit 前必须通过 cargo committed + business-ready 闸门。
*/
export async function submitQuoteForm(
page: Page,
options?: QuoteSubmitOptions,
): Promise<void> {
const rush = isRpaAddressRushMode();
const blind =
options?.blindAddressFlow === true || isRpaBlindAddressFlow();
const humanStrict = isRpaHumanSubmitStrict();
if ((rush || blind) && !humanStrict) {
console.log("[rpa] visual-rush: 跳过 submit 前全部闸门,直接点报价");
const clicked = await clickWidgetQuoteSubmitButton(
page,
WIDGET_SELECTOR,
submitClickOptions(options),
);
if (!clicked) {
throw new RpaError("STRUCT_CHANGE", "提交按钮不可见", {
retryable: false,
});
}
const resultUrl = await waitForPostSubmitDestination(page).catch(() => {
console.log(
`[rpa] post-submit: 未跳转结果页继续网络抓价url=${page.url()}`,
);
return page.url();
});
console.log(`[rpa] post-submit destination ok url=${resultUrl}`);
return;
}
if (rush && humanStrict) {
console.log("[rpa] human-submit-strict: rush 填表 + 完整 submit 闸门");
if (!isRpaAddressMockMode() && !options?.skipAddressLock) {
await assertBothAddressesLocked(page, WIDGET_SELECTOR);
}
await assertCargoCommittedState(page, WIDGET_SELECTOR);
const snapshot = await waitForBusinessReadyState(page, options);
if (!snapshot.ready) {
await debugBusinessStateSnapshot(page);
throw new RpaError(
"QUOTE_ENTRY_UNAVAILABLE",
`禁止提交business-ready=false${snapshot.reasons.join("")}`,
{ retryable: true },
);
}
const clicked = await clickWidgetQuoteSubmitButton(
page,
WIDGET_SELECTOR,
submitClickOptions(options),
);
if (!clicked) {
throw new RpaError("STRUCT_CHANGE", "提交按钮不可见", { retryable: false });
}
const resultUrl = await waitForPostSubmitDestination(page).catch(() => {
console.log(
`[rpa] post-submit: 未跳转结果页继续网络抓价url=${page.url()}`,
);
return page.url();
});
console.log(`[rpa] post-submit destination ok url=${resultUrl}`);
return;
}
if (!isRpaAddressMockMode() && !options?.skipAddressLock) {
await assertBothAddressesLocked(page, WIDGET_SELECTOR);
} else if (options?.skipAddressLock) {
console.log("[rpa] axel-place-prefetch: 跳过 submit 前双地址锁定闸门");
} else {
console.log("[rpa] address-mock: 跳过 submit 前双地址锁定闸门");
}
await assertCargoCommittedState(page, WIDGET_SELECTOR);
const snapshot = await waitForBusinessReadyState(page, options);
if (!snapshot.ready) {
await debugBusinessStateSnapshot(page);
throw new RpaError(
"QUOTE_ENTRY_UNAVAILABLE",
`禁止提交business-ready=false${snapshot.reasons.join("")}`,
{ retryable: true },
);
}
const clicked = await clickWidgetQuoteSubmitButton(
page,
WIDGET_SELECTOR,
submitClickOptions(options),
);
if (!clicked) {
throw new RpaError("STRUCT_CHANGE", "提交按钮不可见", {
retryable: false,
});
}
if (isRpaAddressMockMode()) {
console.log(
`[rpa] address-mock: 跳过 post-submit URL 等待url=${page.url()}`,
);
await page.waitForTimeout(1_500);
return;
}
const resultUrl = await waitForPostSubmitDestination(page).catch(() => {
console.log(
`[rpa] post-submit: 未跳转结果页继续网络抓价url=${page.url()}`,
);
return page.url();
});
console.log(`[rpa] post-submit destination ok url=${resultUrl}`);
}
export { debugBusinessStateSnapshot, waitForBusinessReadyState };