feat(reimbursement-small-team-form-validation): 完善小团队报销填报校验
这个提交包含在:
@@ -66,7 +66,7 @@ function reimbursementListItem() {
|
||||
};
|
||||
}
|
||||
|
||||
function reimbursementDetail() {
|
||||
function reimbursementDetail(overrides: { reimbursementDate?: string; description?: string } = {}) {
|
||||
return {
|
||||
id: reimbursementId,
|
||||
formNumber,
|
||||
@@ -77,9 +77,9 @@ function reimbursementDetail() {
|
||||
status: "draft",
|
||||
createdAt: "2026-05-06T09:00:00.000Z",
|
||||
submittedAt: null,
|
||||
reimbursementDate: "2026-05-06",
|
||||
reimbursementDate: overrides.reimbursementDate ?? "2026-05-06",
|
||||
totalAmount: "168.30",
|
||||
description: "客户拜访交通费",
|
||||
description: overrides.description ?? "客户拜访交通费",
|
||||
projectId: null,
|
||||
linkedTravelRequestId: null,
|
||||
linkedTravelRequest: null,
|
||||
@@ -113,6 +113,8 @@ function reimbursementDetail() {
|
||||
|
||||
async function mockApis(page: Page) {
|
||||
let draftExists = true;
|
||||
let detailReimbursementDate = "2026-05-06";
|
||||
let detailDescription = "客户拜访交通费";
|
||||
|
||||
await page.route((url) => url.pathname.startsWith("/api/"), async (route) => {
|
||||
const request = route.request();
|
||||
@@ -245,7 +247,25 @@ async function mockApis(page: Page) {
|
||||
}
|
||||
|
||||
if (path === `/api/reimbursements/${reimbursementId}` && request.method() === "GET") {
|
||||
await fulfillJson(route, reimbursementDetail());
|
||||
await fulfillJson(route, reimbursementDetail({
|
||||
reimbursementDate: detailReimbursementDate,
|
||||
description: detailDescription,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === `/api/reimbursements/${reimbursementId}` && request.method() === "PATCH") {
|
||||
const body = request.postDataJSON() as { reimbursementDate?: string; description?: string | null };
|
||||
detailReimbursementDate = body.reimbursementDate ?? detailReimbursementDate;
|
||||
detailDescription = body.description ?? "";
|
||||
await fulfillJson(route, {
|
||||
id: reimbursementId,
|
||||
status: "draft",
|
||||
reimbursementDate: detailReimbursementDate,
|
||||
description: detailDescription || null,
|
||||
totalAmount: "168.30",
|
||||
travelSummary: { purpose: null, location: null, startDate: null, endDate: null },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -256,7 +276,25 @@ async function mockApis(page: Page) {
|
||||
}
|
||||
|
||||
if (path === `/api/reimbursements/${reimbursementId}/precheck`) {
|
||||
await fulfillJson(route, { decision: "pass", standardChecks: [], riskFlags: [] });
|
||||
await fulfillJson(route, {
|
||||
entityType: "reimbursement",
|
||||
entityId: reimbursementId,
|
||||
decision: "warn",
|
||||
matchedBudgetControlRule: null,
|
||||
budgetHint: null,
|
||||
standardChecks: [],
|
||||
evidenceSummary: {
|
||||
invoiceCount: 0,
|
||||
attachmentCount: 0,
|
||||
items: [{ itemId, linkedInvoiceCount: 0, invoiceState: "unlinked" }],
|
||||
},
|
||||
timelinessChecks: [],
|
||||
assetHint: { required: false, itemCount: 0 },
|
||||
warnings: [],
|
||||
blockingReasons: [],
|
||||
riskFlags: [],
|
||||
nextActions: ["确认提示后可继续提交"],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -369,6 +407,26 @@ test.describe("R102 reimbursement main flow", () => {
|
||||
await expect(page.getByTestId("reimbursement-invoice-dialog")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("PC 详情显示权威合计并可编辑报销日期和事由", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 960 });
|
||||
await page.goto(`/reimbursements/${reimbursementId}`);
|
||||
|
||||
await expect(page.getByTestId("reimbursement-summary")).toContainText("报销总额");
|
||||
await expect(page.getByTestId("reimbursement-items-table")).toContainText("共 1 条明细");
|
||||
await expect(page.getByTestId("reimbursement-items-table")).toContainText("报销合计");
|
||||
await expect(page.getByTestId("reimbursement-operational-hints")).toContainText("待关联发票或补充无票说明");
|
||||
|
||||
await page.getByTestId("reimbursement-business-summary").getByRole("button", { name: "编辑", exact: true }).click();
|
||||
const editor = page.getByTestId("reimbursement-header-editor");
|
||||
await editor.getByLabel("报销日期").fill("2026-05-07");
|
||||
await editor.getByLabel("报销事由").fill("办公网络费用");
|
||||
await editor.getByRole("button", { name: "保存", exact: true }).click();
|
||||
|
||||
await expect(page.getByTestId("reimbursement-header-editor")).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-business-summary")).toContainText("办公网络费用");
|
||||
await expect(page.getByTestId("reimbursement-summary")).toContainText("2026-05-07");
|
||||
});
|
||||
|
||||
test("移动报销卡片显示业务编号", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
const listRequest = page.waitForRequest((request) => {
|
||||
@@ -386,6 +444,10 @@ test.describe("R102 reimbursement main flow", () => {
|
||||
await expect(page.getByText(formNumber)).toBeVisible();
|
||||
await expect(page.getByText(reimbursementId)).toHaveCount(0);
|
||||
await expect(page.locator("table:visible")).toHaveCount(0);
|
||||
|
||||
await page.getByTestId("mobile-reimbursement-card").getByRole("link").click();
|
||||
await expect(page.getByTestId("mobile-reimbursement-items")).toContainText("共 1 条明细");
|
||||
await expect(page.getByTestId("mobile-reimbursement-items")).toContainText("报销合计 ¥168.30");
|
||||
});
|
||||
|
||||
test("移动管理员默认查看本人并可切换全部报销", async ({ page }) => {
|
||||
@@ -402,6 +464,22 @@ test.describe("R102 reimbursement main flow", () => {
|
||||
await page.route("**/api/me", async (route) => {
|
||||
await fulfillJson(route, adminMe);
|
||||
});
|
||||
await page.route(`**/api/reimbursements/${otherReimbursementId}`, async (route) => {
|
||||
if (route.request().method() !== "GET") {
|
||||
await route.fallback();
|
||||
return;
|
||||
}
|
||||
await fulfillJson(route, {
|
||||
...reimbursementDetail(),
|
||||
id: otherReimbursementId,
|
||||
formNumber: "BX-20260731-0002",
|
||||
status: "reviewing",
|
||||
ownerId: "employee-other-r102",
|
||||
submitterId: "employee-other-r102",
|
||||
owner: { id: "employee-other-r102", employee_no: "E103", name: "其他员工" },
|
||||
submitter: { id: "employee-other-r102", employee_no: "E103", name: "其他员工" },
|
||||
});
|
||||
});
|
||||
await page.route((url) => url.pathname === "/api/reimbursements", async (route) => {
|
||||
const request = route.request();
|
||||
if (request.method() !== "GET") {
|
||||
|
||||
在新工单中引用
屏蔽一个用户