R46 A1-A7:落地绩效考核模块首版

这个提交包含在:
2026-03-29 16:30:29 +08:00
父节点 48f395e18d
当前提交 659e3153a3
共修改 19 个文件,包含 2652 行新增和 15 行删除
@@ -27,6 +27,32 @@ async function buildApp({ permissionCodes = ["system.users"], stats } = {}) {
{ employee_id: "emp-3" },
]);
const certificateCount = vi.fn().mockResolvedValue(4);
const performanceCycleFindFirst = vi
.fn()
.mockResolvedValueOnce({
id: "cycle-active",
name: "2026 Q2",
cycle_type: "quarterly",
status: "active",
start_date: new Date("2026-04-01T00:00:00.000Z"),
end_date: new Date("2026-06-30T00:00:00.000Z"),
})
.mockResolvedValueOnce({
id: "cycle-completed",
name: "2026 Q1",
cycle_type: "quarterly",
status: "completed",
start_date: new Date("2026-01-01T00:00:00.000Z"),
end_date: new Date("2026-03-31T00:00:00.000Z"),
});
const performanceReviewAggregate = vi.fn().mockResolvedValue({
_avg: { overall_score: 4.2 },
_count: { _all: 5 },
});
const performanceReviewGroupBy = vi.fn().mockResolvedValue([
{ overall_score: 4, _count: { _all: 3 } },
{ overall_score: 4.5, _count: { _all: 2 } },
]);
app.decorate("prisma", {
employee: {
@@ -47,6 +73,13 @@ async function buildApp({ permissionCodes = ["system.users"], stats } = {}) {
count: stats?.employeeCertificateCount ?? certificateCount,
findMany: vi.fn().mockResolvedValue([]),
},
performanceReviewCycle: {
findFirst: stats?.performanceReviewCycleFindFirst ?? performanceCycleFindFirst,
},
performanceReview: {
aggregate: stats?.performanceReviewAggregate ?? performanceReviewAggregate,
groupBy: stats?.performanceReviewGroupBy ?? performanceReviewGroupBy,
},
});
app.decorate("authenticate", async (request) => {
request.employee = {
@@ -79,12 +112,34 @@ async function buildApp({ permissionCodes = ["system.users"], stats } = {}) {
});
await app.register(employeesRoutes, { prefix: "/api" });
return { app, employeeGroupBy, employeeCount, departmentFindMany, contractCount, certificateGroupBy, certificateCount };
return {
app,
employeeGroupBy,
employeeCount,
departmentFindMany,
contractCount,
certificateGroupBy,
certificateCount,
performanceCycleFindFirst,
performanceReviewAggregate,
performanceReviewGroupBy,
};
}
describe("employees stats route", () => {
it("returns aggregated employee stats for read-only viewers", async () => {
const { app, employeeGroupBy, employeeCount, departmentFindMany, contractCount, certificateGroupBy, certificateCount } = await buildApp({
const {
app,
employeeGroupBy,
employeeCount,
departmentFindMany,
contractCount,
certificateGroupBy,
certificateCount,
performanceCycleFindFirst,
performanceReviewAggregate,
performanceReviewGroupBy,
} = await buildApp({
permissionCodes: ["report.dashboard.view"],
});
@@ -109,12 +164,38 @@ describe("employees stats route", () => {
validHolderCount: 3,
expiringSoonCount: 4,
});
expect(payload.data.performanceSummary).toEqual({
cycle: {
id: "cycle-active",
name: "2026 Q2",
cycleType: "quarterly",
status: "active",
startDate: "2026-04-01",
endDate: "2026-06-30",
},
averageScore: 4.2,
reviewedCount: 5,
distribution: [
{ score: 1, count: 0 },
{ score: 1.5, count: 0 },
{ score: 2, count: 0 },
{ score: 2.5, count: 0 },
{ score: 3, count: 0 },
{ score: 3.5, count: 0 },
{ score: 4, count: 3 },
{ score: 4.5, count: 2 },
{ score: 5, count: 0 },
],
});
expect(employeeGroupBy).toHaveBeenCalledTimes(2);
expect(employeeCount).toHaveBeenCalledTimes(2);
expect(departmentFindMany).toHaveBeenCalledTimes(1);
expect(contractCount).toHaveBeenCalledTimes(1);
expect(certificateGroupBy).toHaveBeenCalledTimes(1);
expect(certificateCount).toHaveBeenCalledTimes(1);
expect(performanceCycleFindFirst).toHaveBeenCalledTimes(2);
expect(performanceReviewAggregate).toHaveBeenCalledTimes(1);
expect(performanceReviewGroupBy).toHaveBeenCalledTimes(1);
await app.close();
});