const { AppError } = require("./AppError"); const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; const MAX_MANAGER_DEPTH = 10; function parseWorkflowSteps(definitionJson) { const steps = definitionJson?.steps; if (!Array.isArray(steps) || steps.length === 0) { throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 400, details: { workflow: "invalid_steps" }, }); } return steps; } function buildWorkflowStepsFromApprovalRule(approvalRule) { if (!approvalRule || typeof approvalRule !== "object") { throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 400, details: { workflow: "invalid_approval_rule" }, }); } const roleCodes = [ approvalRule.level1_role, approvalRule.level2_role, approvalRule.level3_role, ].filter(Boolean); if (roleCodes.length === 0) { throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 400, details: { workflow: "empty_approval_chain" }, }); } return roleCodes.map((roleCode, index) => ({ id: `approval_${index + 1}_${roleCode}`, name: `审批节点${index + 1}`, type: "approval", assignee_rule: roleCode === "manager" ? { type: "department_manager" } : { type: "role", value: roleCode }, })); } async function resolveDepartmentManagerAssignee({ tx, ownerId, logger }) { let currentId = ownerId; const visited = new Set(); for (let depth = 1; depth <= MAX_MANAGER_DEPTH; depth += 1) { if (visited.has(currentId)) { logger?.warn?.( { ownerId, currentId, reason: "cycle_detected" }, "Circular manager reference detected", ); return { assigneeId: null, reason: "cycle_detected", depthChecked: depth - 1, }; } visited.add(currentId); const employee = await tx.employee.findUnique({ where: { id: currentId }, select: { id: true, direct_manager_id: true, }, }); if (!employee) { return { assigneeId: null, reason: "chain_broken", depthChecked: depth, }; } const managerId = employee.direct_manager_id; if (!managerId) { return { assigneeId: null, reason: "no_manager_found", depthChecked: depth, }; } if (managerId === currentId) { logger?.warn?.( { ownerId, currentId, reason: "self_loop" }, "Self-loop manager reference detected", ); return { assigneeId: null, reason: "self_loop", depthChecked: depth, }; } if (managerId === ownerId) { currentId = managerId; continue; } if (visited.has(managerId)) { logger?.warn?.( { ownerId, currentId: managerId, reason: "cycle_detected" }, "Circular manager reference detected", ); return { assigneeId: null, reason: "cycle_detected", depthChecked: depth, }; } return { assigneeId: managerId, reason: null, depthChecked: depth, }; } logger?.warn?.( { ownerId, currentId, reason: "max_depth_exceeded" }, "Manager resolution reached max depth", ); return { assigneeId: null, reason: "max_depth_exceeded", depthChecked: MAX_MANAGER_DEPTH, }; } async function resolveAssigneeFromRule(rule, options = {}) { if (!rule || typeof rule !== "object") { throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 400, details: { workflow: "invalid_assignee_rule" }, }); } if (rule.type === "role") { if (typeof rule.value !== "string" || !rule.value) { throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 400, details: { workflow: "invalid_role_assignee" }, }); } return { assigneeId: null, assigneeRole: rule.value, skipReason: null, depthChecked: 0, }; } if (rule.type === "specific_user") { if (typeof rule.value !== "string" || !UUID_REGEX.test(rule.value)) { throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 400, details: { workflow: "invalid_specific_user_assignee" }, }); } return { assigneeId: rule.value, assigneeRole: null, skipReason: null, depthChecked: 0, }; } if (rule.type === "department_manager") { const ownerId = options.ownerId; const tx = options.tx; if (!ownerId || !UUID_REGEX.test(ownerId) || !tx) { throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 400, details: { workflow: "invalid_department_manager_context" }, }); } const managerResult = await resolveDepartmentManagerAssignee({ tx, ownerId, logger: options.logger, }); if (!managerResult.assigneeId) { return { assigneeId: null, assigneeRole: null, skipReason: managerResult.reason, depthChecked: managerResult.depthChecked, }; } return { assigneeId: managerResult.assigneeId, assigneeRole: null, skipReason: null, depthChecked: managerResult.depthChecked, }; } throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 400, details: { workflow: "unsupported_assignee_rule_type" }, }); } async function resolveActionableStep({ tx, steps, startIndex, ownerId, logger }) { const skipped = []; for (let index = startIndex; index < steps.length; index += 1) { const step = steps[index]; const resolved = await resolveAssigneeFromRule(step.assignee_rule, { tx, ownerId, logger, }); if (resolved.assigneeId || resolved.assigneeRole) { return { actionable: { index, step, assigneeId: resolved.assigneeId, assigneeRole: resolved.assigneeRole, }, skipped, }; } if (resolved.skipReason) { skipped.push({ index, step, reason: resolved.skipReason, depthChecked: resolved.depthChecked, }); continue; } throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 400, details: { workflow: "invalid_resolved_assignee" }, }); } return { actionable: null, skipped, }; } function getWorkflowStepsSnapshot(workflowInstance) { const snapshot = workflowInstance?.context_data?.workflow_steps_snapshot; if (Array.isArray(snapshot) && snapshot.length > 0) { return parseWorkflowSteps({ steps: snapshot }); } return parseWorkflowSteps(workflowInstance?.workflow_definition?.definition_json); } function toMoneyString(value) { if (value == null) return null; return typeof value === "string" ? value : value.toString(); } module.exports = { buildWorkflowStepsFromApprovalRule, getWorkflowStepsSnapshot, parseWorkflowSteps, resolveActionableStep, resolveAssigneeFromRule, resolveDepartmentManagerAssignee, toMoneyString, };