R19/R20: 项目主域进 OA 并打通报销差旅周报待办

这个提交包含在:
2026-03-22 08:34:18 +08:00
父节点 0235ccb968
当前提交 3f9738ac9c
共修改 28 个文件,包含 3236 行新增和 21 行删除
+55
查看文件
@@ -268,6 +268,13 @@ function mapReimbursementSummary(item) {
: null,
createdAt: item.created_at?.toISOString?.() ?? null,
updatedAt: item.updated_at?.toISOString?.() ?? null,
project: item.project
? {
id: item.project.id,
projectNo: item.project.project_no,
name: item.project.name,
}
: null,
};
}
@@ -549,6 +556,8 @@ async function reimbursementRoutes(fastify) {
reimbursementDate,
status: reimbursement.status,
description: reimbursement.description,
projectId: reimbursement.project_id ?? null,
project: reimbursement.project ? { id: reimbursement.project.id, projectNo: reimbursement.project.project_no, name: reimbursement.project.name } : null,
totalAmount: toMoneyString(reimbursement.total_amount),
});
},
@@ -610,6 +619,7 @@ async function reimbursementRoutes(fastify) {
paid_by: true,
created_at: true,
updated_at: true,
project: { select: { id: true, project_no: true, name: true } },
owner: {
select: { id: true, employee_no: true, name: true },
},
@@ -652,6 +662,8 @@ async function reimbursementRoutes(fastify) {
total_amount: true,
status: true,
description: true,
project_id: true,
project: { select: { id: true, project_no: true, name: true } },
owner: {
select: { id: true, employee_no: true, name: true },
},
@@ -745,6 +757,8 @@ async function reimbursementRoutes(fastify) {
totalAmount: toMoneyString(reimbursement.total_amount),
status: reimbursement.status,
description: reimbursement.description,
projectId: reimbursement.project_id ?? null,
project: reimbursement.project ? { id: reimbursement.project.id, projectNo: reimbursement.project.project_no, name: reimbursement.project.name } : null,
owner: reimbursement.owner,
submitter: reimbursement.submitter,
items: reimbursement.items.map((item) => ({
@@ -1787,6 +1801,47 @@ async function reimbursementRoutes(fastify) {
},
);
fastify.post(
"/reimbursements/:id/link-project",
{
preHandler: fastify.authenticate,
},
async (request, reply) => {
const gate = await evaluateFeatureFlag({
prisma: fastify.prisma,
config: fastify.config,
flagKey: FEATURE_FLAG_KEYS.projectLinking,
departmentId: request.employee.department_id ?? null,
strictDepartment: false,
});
if (!gate.enabled) {
throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 409, details: { featureFlag: "project_linking_v1_disabled", reason: gate.reason } });
}
const reimbursementId = parseReimbursementId(request.params?.id);
const body = parseBody(z.object({ projectId: z.string().uuid().nullable() }).strict(), request.body);
const current = await fastify.prisma.reimbursementForm.findUnique({
where: { id: reimbursementId },
select: { id: true, owner_id: true, submitter_id: true, project_id: true, status: true },
});
if (!current) {
throw new AppError({ code: "SYS_404", message: "Not Found", statusCode: 404 });
}
ensureOwnOrCanViewAll(fastify, request, current);
const updated = await fastify.prisma.reimbursementForm.update({
where: { id: reimbursementId },
data: { project_id: body.projectId ?? null, updated_by: request.employee.id },
select: { id: true, project_id: true, project: { select: { id: true, project_no: true, name: true } } },
});
return sendData(reply, request, {
item: {
reimbursementId: updated.id,
projectId: updated.project_id ?? null,
project: updated.project ? { id: updated.project.id, projectNo: updated.project.project_no, name: updated.project.name } : null,
},
});
},
);
fastify.post(
"/reimbursements/:id/pay",
{