feat(P15B-1,P15B-6): 完成请假V1闭环

这个提交包含在:
2026-03-07 08:11:24 +08:00
父节点 03c5a78485
当前提交 4ba311199d
共修改 23 个文件,包含 3202 行新增和 308 行删除
+169
查看文件
@@ -3,6 +3,7 @@ const { AppError } = require("../utils/AppError");
const { sendData } = require("../utils/reply");
const { resolvePermissionsForRoles, hasPermission, ROLE_PERMISSIONS, resolveEntityPermission } = require("../constants/permissions");
const { getWorkflowStepsSnapshot, resolveActionableStep, toMoneyString } = require("../utils/workflow");
const { consumeReservedLeave, releaseReservedLeave, formatDateOnly } = require("../services/leave");
const {
createNotifications,
resolveRecipientsForAssignee,
@@ -538,6 +539,34 @@ async function workflowRoutes(fastify) {
},
},
},
leave_request: {
select: {
id: true,
status: true,
start_date: true,
end_date: true,
start_session: true,
end_session: true,
requested_days: true,
reason: true,
attachment_original_name: true,
attachment_mime_type: true,
leave_type: {
select: {
id: true,
code: true,
name: true,
},
},
owner: {
select: {
id: true,
employee_no: true,
name: true,
},
},
},
},
},
},
},
@@ -616,6 +645,7 @@ async function workflowRoutes(fastify) {
workflowInstance: task.workflow_instance
? {
id: task.workflow_instance.id,
entityType: task.workflow_instance.entity_type,
currentStatus: task.workflow_instance.current_status,
currentStepId: task.workflow_instance.current_step_id,
startedAt: isoOrNull(task.workflow_instance.started_at),
@@ -649,6 +679,40 @@ async function workflowRoutes(fastify) {
: null,
}
: null,
leaveRequest: task.workflow_instance?.leave_request
? {
id: task.workflow_instance.leave_request.id,
status: task.workflow_instance.leave_request.status,
startDate: formatDateOnly(task.workflow_instance.leave_request.start_date),
endDate: formatDateOnly(task.workflow_instance.leave_request.end_date),
startSession: task.workflow_instance.leave_request.start_session,
endSession: task.workflow_instance.leave_request.end_session,
requestedDays: Number(task.workflow_instance.leave_request.requested_days),
reason: task.workflow_instance.leave_request.reason ?? null,
attachment:
task.workflow_instance.leave_request.attachment_original_name ||
task.workflow_instance.leave_request.attachment_mime_type
? {
originalName: task.workflow_instance.leave_request.attachment_original_name ?? null,
mimeType: task.workflow_instance.leave_request.attachment_mime_type ?? null,
}
: null,
leaveType: task.workflow_instance.leave_request.leave_type
? {
id: task.workflow_instance.leave_request.leave_type.id,
code: task.workflow_instance.leave_request.leave_type.code,
name: task.workflow_instance.leave_request.leave_type.name,
}
: null,
owner: task.workflow_instance.leave_request.owner
? {
id: task.workflow_instance.leave_request.owner.id,
employeeNo: task.workflow_instance.leave_request.owner.employee_no,
name: task.workflow_instance.leave_request.owner.name,
}
: null,
}
: null,
allowedActions: listAllowedActionsForTask(request, task, actionable),
history: historyItems.map((item) => ({
id: item.id,
@@ -885,6 +949,28 @@ async function workflowRoutes(fastify) {
},
});
} else if (workflowInstance.entity_type === "leave") {
const leaveRequest = await tx.leaveRequest.findUnique({
where: { id: workflowInstance.entity_id },
select: {
id: true,
owner_id: true,
leave_type_id: true,
requested_days: true,
},
});
const reserveAnchor = await tx.leaveLedger.findFirst({
where: {
leave_request_id: workflowInstance.entity_id,
entry_type: "reserve",
},
orderBy: { created_at: "desc" },
select: {
leave_policy_id: true,
period_start: true,
period_end: true,
},
});
await tx.leaveRequest.update({
where: { id: workflowInstance.entity_id },
data: {
@@ -892,6 +978,19 @@ async function workflowRoutes(fastify) {
updated_by: request.employee.id,
},
});
if (leaveRequest) {
await consumeReservedLeave(tx, {
leaveRequestId: workflowInstance.entity_id,
employeeId: leaveRequest.owner_id,
leaveTypeId: leaveRequest.leave_type_id,
leavePolicyId: reserveAnchor?.leave_policy_id ?? null,
periodStart: reserveAnchor?.period_start ?? null,
periodEnd: reserveAnchor?.period_end ?? null,
requestedDays: leaveRequest.requested_days,
createdBy: request.employee.id,
});
}
}
await tx.workflowHistory.create({
@@ -1016,6 +1115,27 @@ async function workflowRoutes(fastify) {
},
});
} else if (workflowInstance.entity_type === "leave") {
const leaveRequest = await tx.leaveRequest.findUnique({
where: { id: workflowInstance.entity_id },
select: {
id: true,
owner_id: true,
leave_type_id: true,
},
});
const reserveAnchor = await tx.leaveLedger.findFirst({
where: {
leave_request_id: workflowInstance.entity_id,
entry_type: "reserve",
},
orderBy: { created_at: "desc" },
select: {
leave_policy_id: true,
period_start: true,
period_end: true,
},
});
await tx.leaveRequest.update({
where: { id: workflowInstance.entity_id },
data: {
@@ -1023,6 +1143,20 @@ async function workflowRoutes(fastify) {
updated_by: request.employee.id,
},
});
if (leaveRequest) {
await releaseReservedLeave(tx, {
leaveRequestId: workflowInstance.entity_id,
employeeId: leaveRequest.owner_id,
leaveTypeId: leaveRequest.leave_type_id,
leavePolicyId: reserveAnchor?.leave_policy_id ?? null,
periodStart: reserveAnchor?.period_start ?? null,
periodEnd: reserveAnchor?.period_end ?? null,
note: "rejected_release_leave",
payload: { source: "workflow_reject" },
createdBy: request.employee.id,
});
}
}
await tx.workflowHistory.create({
@@ -1146,6 +1280,27 @@ async function workflowRoutes(fastify) {
},
});
} else if (workflowInstance.entity_type === "leave") {
const leaveRequest = await tx.leaveRequest.findUnique({
where: { id: workflowInstance.entity_id },
select: {
id: true,
owner_id: true,
leave_type_id: true,
},
});
const reserveAnchor = await tx.leaveLedger.findFirst({
where: {
leave_request_id: workflowInstance.entity_id,
entry_type: "reserve",
},
orderBy: { created_at: "desc" },
select: {
leave_policy_id: true,
period_start: true,
period_end: true,
},
});
await tx.leaveRequest.update({
where: { id: workflowInstance.entity_id },
data: {
@@ -1153,6 +1308,20 @@ async function workflowRoutes(fastify) {
updated_by: request.employee.id,
},
});
if (leaveRequest) {
await releaseReservedLeave(tx, {
leaveRequestId: workflowInstance.entity_id,
employeeId: leaveRequest.owner_id,
leaveTypeId: leaveRequest.leave_type_id,
leavePolicyId: reserveAnchor?.leave_policy_id ?? null,
periodStart: reserveAnchor?.period_start ?? null,
periodEnd: reserveAnchor?.period_end ?? null,
note: "returned_release_leave",
payload: { source: "workflow_return" },
createdBy: request.employee.id,
});
}
}
await tx.workflowHistory.create({