R5-M1-M5 收口正式审批规则与移动反馈

这个提交包含在:
2026-05-10 19:09:09 +08:00
父节点 f6e0d5ae78
当前提交 8da16f5d97
共修改 24 个文件,包含 824 行新增和 99 行删除
@@ -4,6 +4,7 @@ const workflowRoutes = require("../../../src/routes/workflow");
const { AppError } = require("../../../src/utils/AppError");
const ACTOR_ID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
const OWNER_ID = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb";
function buildTask(taskId, overrides = {}) {
return {
@@ -112,7 +113,14 @@ async function buildApp(options = {}) {
},
reimbursementForm: {
update: vi.fn(async () => ({})),
findUnique: vi.fn(async () => null),
findUnique: vi.fn(async () => ({
id: "reimbursement-1",
form_number: "BX-TEST",
total_amount: "100.00",
owner_id: options.reimbursementOwnerId ?? OWNER_ID,
submitter_id: options.reimbursementSubmitterId ?? options.reimbursementOwnerId ?? OWNER_ID,
owner: { id: options.reimbursementOwnerId ?? OWNER_ID, name: "申请人" },
})),
},
leaveRequest: {
findUnique: vi.fn(async () => null),
@@ -180,6 +188,30 @@ async function buildApp(options = {}) {
}
describe("workflow batch routes", () => {
it("rejects self approval on a single task action", async () => {
const { app, state } = await buildApp({
reimbursementOwnerId: ACTOR_ID,
});
const response = await app.inject({
method: "POST",
url: "/api/workflow/tasks/11111111-1111-4111-8111-111111111111/approve",
});
expect(response.statusCode).toBe(409);
expect(response.json().error).toEqual(
expect.objectContaining({
code: "VALID_001",
details: expect.objectContaining({
workflow: "self_approval_not_allowed",
}),
}),
);
expect(state.tasks.get("11111111-1111-4111-8111-111111111111").status).toBe("pending");
await app.close();
});
it("approves reimbursement tasks and skips already processed ones", async () => {
const { app, prisma, state } = await buildApp();
@@ -311,6 +343,40 @@ describe("workflow batch routes", () => {
await app.close();
});
it("rejects self approval in batch actions without changing the task", async () => {
const { app, state } = await buildApp({
reimbursementOwnerId: ACTOR_ID,
});
const response = await app.inject({
method: "POST",
url: "/api/workflow/batch-approve",
payload: {
taskIds: ["11111111-1111-4111-8111-111111111111"],
},
});
expect(response.statusCode).toBe(200);
expect(response.json().data).toEqual({
results: [
{
taskId: "11111111-1111-4111-8111-111111111111",
outcome: "error",
reason: "self_approval_not_allowed",
},
],
summary: {
approved: 0,
rejected: 0,
skipped: 0,
errored: 1,
},
});
expect(state.tasks.get("11111111-1111-4111-8111-111111111111").status).toBe("pending");
await app.close();
});
it("rejects oversized batch payloads and missing permissions", async () => {
const { app } = await buildApp();
const oversizedPayload = {