fix: 财务本人报销由总经理独立审批(09-04-reimbursement-state-gap-finance-view)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-09-12 20:03:29 +08:00
父节点 31ab9fbb2b
当前提交 229867f5fd
共修改 18 个文件,包含 629 行新增和 20 行删除
+31 -1
查看文件
@@ -1,10 +1,12 @@
const { z } = require("zod");
const { AppError } = require("../utils/AppError");
const { buildBusinessValidationError } = require("../utils/businessValidationError");
const { buildAuditLogData } = require("../utils/audit");
const { sendData } = require("../utils/reply");
const { hasPermission, ROLE_PERMISSIONS, resolveEntityPermission } = require("../constants/permissions");
const {
BUSINESS_APPROVAL_ROLE_CODES,
FINANCE_OWNER_APPROVAL_STEP_ID,
getWorkflowStepsSnapshot,
isBusinessApprovalRoleCode,
resolveActionableStep,
@@ -685,6 +687,11 @@ function listAllowedActionsForTask(request, task, actionable) {
if (task.status !== "pending" || !actionable) {
return [];
}
if (task.workflow_instance?.entity_type === "reimbursement"
&& task.step_id === FINANCE_OWNER_APPROVAL_STEP_ID
&& !request.employee.roleCodes.includes("ceo")) {
return [];
}
const actions = [];
const approvePermission = resolveEntityPermission(task.workflow_instance?.entity_type, "approve");
@@ -1066,10 +1073,32 @@ function ensureNoSelfApproval(actorIds, applicantIds) {
});
}
async function ensureFinanceOwnerCeoActors(tx, workflowInstance, actorIds) {
if (workflowInstance.entity_type !== "reimbursement"
|| workflowInstance.current_step_id !== FINANCE_OWNER_APPROVAL_STEP_ID) {
return;
}
const eligible = await tx.employeeRole.findMany({
where: {
employee_id: { in: actorIds },
role_code: "ceo",
employee: { status: "active", archived_at: null },
},
select: { employee_id: true },
});
if (actorIds.some((actorId) => !eligible.some((holder) => holder.employee_id === actorId))) {
throw buildBusinessValidationError("reimbursement.finance_owner_ceo_required", {
workflow: "finance_owner_ceo_required",
}, 403);
}
}
async function ensureWorkflowApprovalAllowed(tx, workflowInstance, request, assignment) {
ensureBusinessApproveRole(request.employee, workflowInstance.entity_type);
const applicantIds = await loadWorkflowApplicantIds(tx, workflowInstance);
ensureNoSelfApproval(uniq([request.employee.id, assignment?.principalId]), applicantIds);
const actorIds = uniq([request.employee.id, assignment?.principalId]);
ensureNoSelfApproval(actorIds, applicantIds);
await ensureFinanceOwnerCeoActors(tx, workflowInstance, actorIds);
return applicantIds;
}
@@ -3197,6 +3226,7 @@ async function workflowRoutes(fastify) {
const transferCount = await ensureTransferChainNotExceeded(tx, taskId);
await ensureTransferTargetEligible(tx, toApproverId, approvePermission);
ensureNoSelfApproval([toApproverId], applicantIds);
await ensureFinanceOwnerCeoActors(tx, workflowInstance, [toApproverId]);
if (toApproverId === request.employee.id) {
throw new AppError({