R5-M1-M5 收口正式审批规则与移动反馈
这个提交包含在:
@@ -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 = {
|
||||
|
||||
在新工单中引用
屏蔽一个用户