R19/R20: 项目主域进 OA 并打通报销差旅周报待办
这个提交包含在:
@@ -76,6 +76,7 @@ const TRAVEL_SUMMARY_SELECT = {
|
||||
legal_entity_id: true,
|
||||
cost_center_id: true,
|
||||
budget_pool_id: true,
|
||||
project_id: true,
|
||||
purpose: true,
|
||||
trip_type: true,
|
||||
start_date: true,
|
||||
@@ -126,6 +127,13 @@ const TRAVEL_SUMMARY_SELECT = {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
project: {
|
||||
select: {
|
||||
id: true,
|
||||
project_no: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const TRAVEL_DETAIL_SELECT = {
|
||||
@@ -418,6 +426,7 @@ function mapTravelRequestSummary(item) {
|
||||
legalEntityId: item.legal_entity_id,
|
||||
costCenterId: item.cost_center_id,
|
||||
budgetPoolId: item.budget_pool_id ?? null,
|
||||
projectId: item.project_id ?? null,
|
||||
purpose: item.purpose,
|
||||
tripType: item.trip_type,
|
||||
startDate: toIsoDate(item.start_date),
|
||||
@@ -456,6 +465,13 @@ function mapTravelRequestSummary(item) {
|
||||
name: item.budget_pool.name,
|
||||
}
|
||||
: null,
|
||||
project: item.project
|
||||
? {
|
||||
id: item.project.id,
|
||||
projectNo: item.project.project_no,
|
||||
name: item.project.name,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1201,6 +1217,49 @@ async function createTravelRequestsRoutes(fastify) {
|
||||
},
|
||||
);
|
||||
|
||||
fastify.post(
|
||||
"/travel-requests/: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 buildValidationError({ featureFlag: "project_linking_v1_disabled", reason: gate.reason }, 409);
|
||||
}
|
||||
const travelRequestId = parseUuid(request.params?.id, "id");
|
||||
const body = parseBody(z.object({ projectId: UUID_SCHEMA.nullable() }).strict(), request.body);
|
||||
const current = await fastify.prisma.travelRequest.findUnique({
|
||||
where: { id: travelRequestId },
|
||||
select: { id: true, applicant_employee_id: true, submitter_id: true, status: true },
|
||||
});
|
||||
if (!current) {
|
||||
throw new AppError({ code: "SYS_404", message: "Not Found", statusCode: 404 });
|
||||
}
|
||||
if (!canReadTravelRequest(request, current)) {
|
||||
throw new AppError({ code: "PERM_001", message: "无权限", statusCode: 403, details: { permission: "travel.view_own" } });
|
||||
}
|
||||
const updated = await fastify.prisma.travelRequest.update({
|
||||
where: { id: travelRequestId },
|
||||
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: {
|
||||
travelRequestId: 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(
|
||||
"/travel-requests/:id/withdraw",
|
||||
{
|
||||
|
||||
在新工单中引用
屏蔽一个用户