恢复付款入口与常开基线(08-01-reimbursement-notification-progress-clarity)
这个提交包含在:
@@ -24,6 +24,14 @@ type MockNotification = {
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
type FinanceSummaryOverride = {
|
||||
pendingBatchCount: number;
|
||||
failedItemCount: number;
|
||||
riskCount: number;
|
||||
paymentBatchEnabled: boolean;
|
||||
href: string;
|
||||
};
|
||||
|
||||
function envelope(data: unknown) {
|
||||
return { requestId: "mock-dg7-mobile-information-dedupe", data };
|
||||
}
|
||||
@@ -453,7 +461,12 @@ function reimbursementDetail() {
|
||||
};
|
||||
}
|
||||
|
||||
async function mockApis(page: Page, role: RolePreset, notifications: MockNotification[]) {
|
||||
async function mockApis(
|
||||
page: Page,
|
||||
role: RolePreset,
|
||||
notifications: MockNotification[],
|
||||
financeSummaryOverride?: FinanceSummaryOverride,
|
||||
) {
|
||||
await page.route((url) => url.pathname.startsWith("/api/"), async (route) => {
|
||||
const request = route.request();
|
||||
const url = new URL(request.url());
|
||||
@@ -544,7 +557,7 @@ async function mockApis(page: Page, role: RolePreset, notifications: MockNotific
|
||||
isAdminOnly: role === "admin",
|
||||
},
|
||||
financeSummary: isFinance
|
||||
? {
|
||||
? financeSummaryOverride ?? {
|
||||
pendingBatchCount: 2,
|
||||
failedItemCount: 1,
|
||||
riskCount: 3,
|
||||
@@ -889,7 +902,7 @@ test.describe("MIF7 移动信息架构整改", () => {
|
||||
const target = { targetType: "payment_batch", targetId: "batch/dg7" };
|
||||
|
||||
expect(resolveNotificationTargetLink(target)).toBe("/payment-batches?id=batch%2Fdg7");
|
||||
expect(resolveMobileNotificationTargetLink(target)).toBe("/payment-batches?id=batch%2Fdg7");
|
||||
expect(resolveMobileNotificationTargetLink(target)).toBe("/mobile/payment-batches?id=batch%2Fdg7");
|
||||
});
|
||||
|
||||
test.skip(process.env.E2E_MODE === "live", "MIF7 本地专项使用 mock 数据验证信息架构。");
|
||||
@@ -1161,6 +1174,27 @@ test.describe("MIF7 移动信息架构整改", () => {
|
||||
await financeMobile.context.close();
|
||||
}
|
||||
|
||||
const firstUseFinanceMobile = await newMobilePage(browser, 390);
|
||||
try {
|
||||
await mockApis(firstUseFinanceMobile.page, "finance", buildNotifications(), {
|
||||
pendingBatchCount: 0,
|
||||
failedItemCount: 0,
|
||||
riskCount: 0,
|
||||
paymentBatchEnabled: true,
|
||||
href: "/payment-batches",
|
||||
});
|
||||
await loginForMobile(firstUseFinanceMobile.page, "finance");
|
||||
await firstUseFinanceMobile.page.goto("/mobile");
|
||||
const financeSummary = firstUseFinanceMobile.page.getByTestId("mobile-finance-summary");
|
||||
await expect(financeSummary).toBeVisible();
|
||||
await expect(financeSummary).toContainText("待付款");
|
||||
await expect(financeSummary.getByRole("link", { name: "进入付款" })).toHaveAttribute("href", "/mobile/payment-batches");
|
||||
await financeSummary.getByRole("link", { name: "进入付款" }).click();
|
||||
await expect(firstUseFinanceMobile.page).toHaveURL(/\/mobile\/payment-batches$/);
|
||||
} finally {
|
||||
await firstUseFinanceMobile.context.close();
|
||||
}
|
||||
|
||||
const adminMobile = await newMobilePage(browser, 390);
|
||||
try {
|
||||
await mockApis(adminMobile.page, "admin", buildNotifications());
|
||||
|
||||
@@ -19,7 +19,7 @@ type FeatureFlagItem = {
|
||||
|
||||
type FeatureFlagSnapshot = Pick<
|
||||
FeatureFlagItem,
|
||||
"flagKey" | "overrideMode" | "rolloutType" | "departmentIds" | "version" | "effectiveEnabled" | "effectiveReason"
|
||||
"flagKey" | "overrideMode" | "rolloutType" | "departmentIds" | "note" | "version" | "effectiveEnabled" | "effectiveReason"
|
||||
>;
|
||||
|
||||
type ReimbursementDetail = {
|
||||
@@ -99,7 +99,7 @@ const baseUrl = process.env.LIVE_SMOKE_BASE_URL?.trim() || "https://oa.hyinda.co
|
||||
const reportDir = process.env.P2_FIN_LIVE_REPORT_DIR?.trim() ||
|
||||
path.resolve(process.cwd(), "..", "artifacts/qa-flow/p2-fin-live-local");
|
||||
const summaryPath = path.join(reportDir, "live-result.json");
|
||||
const finalFlagState = process.env.P2_FIN_LIVE_FINAL_STATE?.trim() === "force_on" ? "force_on" : "force_off";
|
||||
const finalFlagState = process.env.P2_FIN_LIVE_FINAL_STATE?.trim() === "force_on" ? "force_on" : "restore";
|
||||
const alreadyOnMode =
|
||||
process.env.P2_FIN_LIVE_ALREADY_ON === "1" ||
|
||||
process.env.LAUNCH_AUTO_PAYMENT_BATCH_ALREADY_ON === "1";
|
||||
@@ -130,6 +130,7 @@ function snapshotFlag(item: FeatureFlagItem): FeatureFlagSnapshot {
|
||||
overrideMode: item.overrideMode,
|
||||
rolloutType: item.rolloutType,
|
||||
departmentIds: item.departmentIds,
|
||||
note: item.note,
|
||||
version: item.version,
|
||||
effectiveEnabled: item.effectiveEnabled,
|
||||
effectiveReason: item.effectiveReason,
|
||||
@@ -183,13 +184,15 @@ async function updateFlag(
|
||||
flagKey: string,
|
||||
overrideMode: FeatureFlagItem["overrideMode"],
|
||||
note: string,
|
||||
rolloutType: FeatureFlagItem["rolloutType"] = "global",
|
||||
departmentIds: string[] = [],
|
||||
) {
|
||||
const current = await loadFlag(api, flagKey);
|
||||
const response = await api.put(`/api/system/feature-flags/${flagKey}`, {
|
||||
data: {
|
||||
overrideMode,
|
||||
rolloutType: "global",
|
||||
departmentIds: [],
|
||||
rolloutType,
|
||||
departmentIds,
|
||||
note,
|
||||
expectedVersion: current.version,
|
||||
},
|
||||
@@ -198,12 +201,24 @@ async function updateFlag(
|
||||
return data.item;
|
||||
}
|
||||
|
||||
async function forcePaymentBatchOff(api: APIRequestContext, note: string) {
|
||||
async function restoreFlagSnapshot(api: APIRequestContext, snapshot: FeatureFlagSnapshot) {
|
||||
const current = await loadFlag(api, "payment_batch_v1");
|
||||
if (current.overrideMode === "force_off" && !current.effectiveEnabled) {
|
||||
const configurationMatches =
|
||||
current.overrideMode === snapshot.overrideMode &&
|
||||
current.rolloutType === snapshot.rolloutType &&
|
||||
JSON.stringify(current.departmentIds) === JSON.stringify(snapshot.departmentIds) &&
|
||||
current.note === snapshot.note;
|
||||
if (configurationMatches) {
|
||||
return current;
|
||||
}
|
||||
return updateFlag(api, "payment_batch_v1", "force_off", note);
|
||||
return updateFlag(
|
||||
api,
|
||||
"payment_batch_v1",
|
||||
snapshot.overrideMode,
|
||||
snapshot.note,
|
||||
snapshot.rolloutType,
|
||||
snapshot.departmentIds,
|
||||
);
|
||||
}
|
||||
|
||||
function resolveLivePdfBuffer(marker: string) {
|
||||
@@ -397,6 +412,7 @@ test.describe("公网付款批次受控验收", () => {
|
||||
|
||||
const clients: AuthenticatedApiClient[] = [];
|
||||
let admin: AuthenticatedApiClient | null = null;
|
||||
let paymentFlagBefore: FeatureFlagItem | null = null;
|
||||
let caughtError: unknown = null;
|
||||
let rollbackError: unknown = null;
|
||||
|
||||
@@ -408,7 +424,7 @@ test.describe("公网付款批次受控验收", () => {
|
||||
const financeMgr = await createLiveClientWithRetry("financeMgr");
|
||||
clients.push(admin, employee, manager, finance, financeMgr);
|
||||
|
||||
const paymentFlagBefore = await loadFlag(admin.api, "payment_batch_v1");
|
||||
paymentFlagBefore = await loadFlag(admin.api, "payment_batch_v1");
|
||||
const erpFlagBefore = await loadFlag(admin.api, "erp_sync");
|
||||
summary.stages.preflight = {
|
||||
paymentFlagBefore: snapshotFlag(paymentFlagBefore),
|
||||
@@ -587,19 +603,7 @@ test.describe("公网付款批次受控验收", () => {
|
||||
} finally {
|
||||
if (admin) {
|
||||
try {
|
||||
if (alreadyOnMode) {
|
||||
const finalPaymentFlag = await loadFlag(admin.api, "payment_batch_v1");
|
||||
const finalErpFlag = await loadFlag(admin.api, "erp_sync");
|
||||
summary.stages.finalState = {
|
||||
paymentFlag: snapshotFlag(finalPaymentFlag),
|
||||
erpSyncEffectiveEnabled: finalErpFlag.effectiveEnabled,
|
||||
alreadyOnMode,
|
||||
};
|
||||
expect(finalPaymentFlag.effectiveEnabled).toBe(true);
|
||||
expect(finalPaymentFlag.overrideMode).toBe("force_on");
|
||||
expect(finalFlagState).toBe("force_on");
|
||||
expect(finalErpFlag.effectiveEnabled).toBe(false);
|
||||
} else if (finalFlagState === "force_on" && !caughtError) {
|
||||
if (finalFlagState === "force_on" && !caughtError) {
|
||||
const paymentFlag = await loadFlag(admin.api, "payment_batch_v1");
|
||||
const finalPaymentFlag =
|
||||
paymentFlag.overrideMode === "force_on" && paymentFlag.effectiveEnabled
|
||||
@@ -609,14 +613,18 @@ test.describe("公网付款批次受控验收", () => {
|
||||
summary.stages.finalState = {
|
||||
paymentFlag: snapshotFlag(finalPaymentFlag),
|
||||
erpSyncEffectiveEnabled: finalErpFlag.effectiveEnabled,
|
||||
alreadyOnMode,
|
||||
};
|
||||
expect(finalPaymentFlag.effectiveEnabled).toBe(true);
|
||||
expect(finalPaymentFlag.overrideMode).toBe("force_on");
|
||||
expect(finalErpFlag.effectiveEnabled).toBe(false);
|
||||
} else {
|
||||
const rolledBack = await forcePaymentBatchOff(admin.api, `${issueId} rollback ${marker}`);
|
||||
summary.stages.rollback = { paymentFlagAfterRollback: snapshotFlag(rolledBack) };
|
||||
expect(rolledBack.effectiveEnabled).toBe(false);
|
||||
} else if (paymentFlagBefore) {
|
||||
const restored = await restoreFlagSnapshot(admin.api, snapshotFlag(paymentFlagBefore));
|
||||
summary.stages.rollback = { paymentFlagAfterRestore: snapshotFlag(restored) };
|
||||
expect(restored.overrideMode).toBe(paymentFlagBefore.overrideMode);
|
||||
expect(restored.rolloutType).toBe(paymentFlagBefore.rolloutType);
|
||||
expect(restored.departmentIds).toEqual(paymentFlagBefore.departmentIds);
|
||||
expect(restored.note).toBe(paymentFlagBefore.note);
|
||||
}
|
||||
} catch (error) {
|
||||
rollbackError = error;
|
||||
|
||||
在新工单中引用
屏蔽一个用户