|
|
import type { Page } from "playwright";
|
|
|
import { isRpaAddressMockMode } from "@/lib/rpa/address-mode";
|
|
|
import { isRpaHumanSubmitStrict, isRpaVisualRushMode } from "@/lib/rpa/env";
|
|
|
import {
|
|
|
clickFirstVisibleFromSpecs,
|
|
|
getSelectorSpecs,
|
|
|
} from "@/lib/rpa/selector-specs";
|
|
|
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 type { QuoteSubmitOptions } from "@/workers/rpa/quote-capture/types";
|
|
|
|
|
|
const WIDGET_SELECTOR = "#react-quoter-landing-plugin";
|
|
|
|
|
|
/**
|
|
|
* DOM 仅用于提交动作;填表由 kernel fillAddress / fillCargo 完成。
|
|
|
* submit 前必须通过 cargo committed + business-ready 闸门。
|
|
|
*/
|
|
|
export async function submitQuoteForm(
|
|
|
page: Page,
|
|
|
options?: QuoteSubmitOptions,
|
|
|
): Promise<void> {
|
|
|
const rush = isRpaVisualRushMode();
|
|
|
const humanStrict = isRpaHumanSubmitStrict();
|
|
|
if (rush && !humanStrict) {
|
|
|
console.log("[rpa] visual-rush: 跳过 submit 前全部闸门,直接点报价");
|
|
|
await page.keyboard.press("Escape").catch(() => undefined);
|
|
|
await page.waitForTimeout(200);
|
|
|
await page.keyboard.press("Escape").catch(() => undefined);
|
|
|
await page.waitForTimeout(200);
|
|
|
const clicked = await clickFirstVisibleFromSpecs(
|
|
|
page,
|
|
|
getSelectorSpecs("RPA_SELECTOR_SUBMIT"),
|
|
|
);
|
|
|
if (!clicked) {
|
|
|
throw new RpaError("STRUCT_CHANGE", "提交按钮不可见", {
|
|
|
retryable: false,
|
|
|
});
|
|
|
}
|
|
|
const resultUrl = await waitForPostSubmitDestination(page);
|
|
|
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 clickFirstVisibleFromSpecs(
|
|
|
page,
|
|
|
getSelectorSpecs("RPA_SELECTOR_SUBMIT"),
|
|
|
);
|
|
|
if (!clicked) {
|
|
|
throw new RpaError("STRUCT_CHANGE", "提交按钮不可见", { retryable: false });
|
|
|
}
|
|
|
const resultUrl = await waitForPostSubmitDestination(page);
|
|
|
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 clickFirstVisibleFromSpecs(
|
|
|
page,
|
|
|
getSelectorSpecs("RPA_SELECTOR_SUBMIT"),
|
|
|
);
|
|
|
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);
|
|
|
console.log(`[rpa] post-submit destination ok url=${resultUrl}`);
|
|
|
}
|
|
|
|
|
|
export { debugBusinessStateSnapshot, waitForBusinessReadyState };
|