BIZ8-11 增强采购项目关联

这个提交包含在:
2026-05-13 21:42:34 +08:00
父节点 83be13df27
当前提交 32823edc1b
共修改 11 个文件,包含 737 行新增和 6 行删除
+83 -1
查看文件
@@ -66,11 +66,12 @@ function buildProjectState(overrides = {}) {
};
}
function createPrismaState({ communicationLogs = [], closureChecklistItems = [] } = {}) {
function createPrismaState({ communicationLogs = [], closureChecklistItems = [], purchaseRequests = [] } = {}) {
const state = {
project: buildProjectState(),
communicationLogs: communicationLogs.map((item) => ({ ...item })),
closureChecklistItems: closureChecklistItems.map((item) => ({ ...item })),
purchaseRequests: purchaseRequests.map((item) => ({ ...item })),
openRiskCount: 0,
openReimbursementCount: 0,
openTravelCount: 0,
@@ -89,6 +90,7 @@ function createPrismaState({ communicationLogs = [], closureChecklistItems = []
tasks: (project.tasks ?? []).length,
risks: (project.risks ?? []).length,
reimbursements: 0,
purchase_requests: state.purchaseRequests.filter((item) => item.project_id === project.id).length,
travel_requests: 0,
todo_items: 0,
weekly_reports: 0,
@@ -277,6 +279,13 @@ function createPrismaState({ communicationLogs = [], closureChecklistItems = []
projectRisk: { count: vi.fn().mockImplementation(async () => state.openRiskCount) },
reimbursementForm: { count: vi.fn().mockImplementation(async () => state.openReimbursementCount) },
travelRequest: { count: vi.fn().mockImplementation(async () => state.openTravelCount) },
purchaseRequest: {
findMany: vi.fn().mockImplementation(async ({ where }) => {
return state.purchaseRequests
.filter((item) => item.project_id === where.project_id)
.sort((left, right) => new Date(right.updated_at).getTime() - new Date(left.updated_at).getTime());
}),
},
projectDeliveryRecord: { count: vi.fn().mockImplementation(async () => state.deliveryRecordCount) },
contractRegistry: { count: vi.fn().mockImplementation(async () => state.contractCount) },
confidentialDocumentRef: { count: vi.fn().mockImplementation(async () => state.confidentialDocRefCount) },
@@ -329,6 +338,79 @@ async function buildApp({ employee, prisma }) {
}
describe("project R23 routes", () => {
it("returns purchase request summary only to readable project users", async () => {
const { prisma } = createPrismaState({
purchaseRequests: [
{
id: "77777777-7777-4777-8777-777777777777",
project_id: PROJECT_ID,
request_no: "PR-20260513-001",
title: "测试设备采购",
status: "approved",
total_amount: "1200.50",
updated_at: new Date("2026-05-13T08:00:00.000Z"),
},
{
id: "88888888-8888-4888-8888-888888888888",
project_id: PROJECT_ID,
request_no: null,
title: "未编号采购草稿",
status: "draft",
total_amount: "99.50",
updated_at: new Date("2026-05-13T09:00:00.000Z"),
},
],
});
const memberApp = await buildApp({
employee: { id: MEMBER_ID, department_id: null, permissionCodes: ["project.view_own"] },
prisma,
});
const response = await memberApp.inject({
method: "GET",
url: `/api/projects/${PROJECT_ID}/purchase-requests/summary`,
headers: { authorization: "Bearer test-token" },
});
expect(response.statusCode).toBe(200);
expect(response.json().data).toEqual({
totalAmount: "1300.00",
items: [
{
id: "88888888-8888-4888-8888-888888888888",
requestNo: "88888888-8888-4888-8888-888888888888",
title: "未编号采购草稿",
status: "draft",
totalAmount: "99.50",
updatedAt: "2026-05-13T09:00:00.000Z",
},
{
id: "77777777-7777-4777-8777-777777777777",
requestNo: "PR-20260513-001",
title: "测试设备采购",
status: "approved",
totalAmount: "1200.50",
updatedAt: "2026-05-13T08:00:00.000Z",
},
],
});
const outsiderApp = await buildApp({
employee: { id: OUTSIDER_ID, department_id: null, permissionCodes: ["project.view_own"] },
prisma,
});
const forbiddenResponse = await outsiderApp.inject({
method: "GET",
url: `/api/projects/${PROJECT_ID}/purchase-requests/summary`,
headers: { authorization: "Bearer test-token" },
});
expect(forbiddenResponse.statusCode).toBe(403);
expect(forbiddenResponse.json().error.code).toBe("PERM_001");
await Promise.all([memberApp.close(), outsiderApp.close()]);
});
it("allows project member to create communication logs and blocks outsiders", async () => {
const { prisma } = createPrismaState();
const memberApp = await buildApp({