OAFLOW1 支持负责人本人报销兜底审批
这个提交包含在:
@@ -7,6 +7,7 @@ const { ROLE_PERMISSIONS } = require("../constants/permissions");
|
||||
|
||||
const ROLE_CODES = Object.keys(ROLE_PERMISSIONS);
|
||||
const MONEY_STRING_SCHEMA = z.string().regex(/^\d+(\.\d{1,2})?$/);
|
||||
const OWNER_SELF_FALLBACK_ROLE_CODES = ["finance_mgr"];
|
||||
|
||||
const CREATE_BODY_SCHEMA = z.object({
|
||||
name: z.string().trim().min(1).max(100),
|
||||
@@ -15,6 +16,7 @@ const CREATE_BODY_SCHEMA = z.object({
|
||||
level1Role: z.enum(ROLE_CODES),
|
||||
level2Role: z.enum(ROLE_CODES).nullable().optional(),
|
||||
level3Role: z.enum(ROLE_CODES).nullable().optional(),
|
||||
ownerSelfFallbackRole: z.enum(OWNER_SELF_FALLBACK_ROLE_CODES).nullable().optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
priority: z.number().int().min(0).max(10000).optional(),
|
||||
});
|
||||
@@ -75,6 +77,21 @@ function ensureAmountRangeValid(minAmount, maxAmount) {
|
||||
}
|
||||
}
|
||||
|
||||
function ensureOwnerSelfFallbackRoleValid(roleCode) {
|
||||
if (roleCode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!OWNER_SELF_FALLBACK_ROLE_CODES.includes(roleCode)) {
|
||||
throw new AppError({
|
||||
code: "VALID_001",
|
||||
message: "参数校验失败",
|
||||
statusCode: 400,
|
||||
details: { ownerSelfFallbackRole: "unsupported_business_approval_role" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mapApprovalRule(item) {
|
||||
return {
|
||||
id: item.id,
|
||||
@@ -87,6 +104,7 @@ function mapApprovalRule(item) {
|
||||
level1Role: item.level1_role,
|
||||
level2Role: item.level2_role ?? null,
|
||||
level3Role: item.level3_role ?? null,
|
||||
ownerSelfFallbackRole: item.owner_self_fallback_role ?? null,
|
||||
enabled: item.enabled,
|
||||
priority: item.priority,
|
||||
createdAt: item.created_at?.toISOString?.() ?? null,
|
||||
@@ -104,6 +122,7 @@ async function approvalRulesRoutes(fastify) {
|
||||
const body = parseBody(CREATE_BODY_SCHEMA, request.body);
|
||||
const maxAmount = Object.prototype.hasOwnProperty.call(body, "maxAmount") ? body.maxAmount : null;
|
||||
ensureAmountRangeValid(body.minAmount, maxAmount);
|
||||
ensureOwnerSelfFallbackRoleValid(body.ownerSelfFallbackRole);
|
||||
|
||||
const result = await fastify.prisma.$transaction(async (tx) => {
|
||||
let priority = body.priority;
|
||||
@@ -120,6 +139,9 @@ async function approvalRulesRoutes(fastify) {
|
||||
level1_role: body.level1Role,
|
||||
level2_role: body.level2Role ?? null,
|
||||
level3_role: body.level3Role ?? null,
|
||||
owner_self_fallback_role: Object.prototype.hasOwnProperty.call(body, "ownerSelfFallbackRole")
|
||||
? body.ownerSelfFallbackRole ?? null
|
||||
: "finance_mgr",
|
||||
enabled: body.enabled ?? true,
|
||||
priority,
|
||||
created_by: request.employee.id,
|
||||
@@ -133,6 +155,7 @@ async function approvalRulesRoutes(fastify) {
|
||||
level1_role: true,
|
||||
level2_role: true,
|
||||
level3_role: true,
|
||||
owner_self_fallback_role: true,
|
||||
enabled: true,
|
||||
priority: true,
|
||||
created_at: true,
|
||||
@@ -156,6 +179,7 @@ async function approvalRulesRoutes(fastify) {
|
||||
level1Role: created.level1_role,
|
||||
level2Role: created.level2_role ?? null,
|
||||
level3Role: created.level3_role ?? null,
|
||||
ownerSelfFallbackRole: created.owner_self_fallback_role ?? null,
|
||||
enabled: created.enabled,
|
||||
priority: created.priority,
|
||||
},
|
||||
@@ -186,6 +210,9 @@ async function approvalRulesRoutes(fastify) {
|
||||
details: { body: "empty_patch" },
|
||||
});
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(body, "ownerSelfFallbackRole")) {
|
||||
ensureOwnerSelfFallbackRoleValid(body.ownerSelfFallbackRole);
|
||||
}
|
||||
|
||||
const result = await fastify.prisma.$transaction(async (tx) => {
|
||||
const locked = await tx.$queryRaw`
|
||||
@@ -212,6 +239,7 @@ async function approvalRulesRoutes(fastify) {
|
||||
level1_role: true,
|
||||
level2_role: true,
|
||||
level3_role: true,
|
||||
owner_self_fallback_role: true,
|
||||
enabled: true,
|
||||
priority: true,
|
||||
created_at: true,
|
||||
@@ -251,6 +279,9 @@ async function approvalRulesRoutes(fastify) {
|
||||
...(Object.prototype.hasOwnProperty.call(body, "level3Role")
|
||||
? { level3_role: body.level3Role ?? null }
|
||||
: {}),
|
||||
...(Object.prototype.hasOwnProperty.call(body, "ownerSelfFallbackRole")
|
||||
? { owner_self_fallback_role: body.ownerSelfFallbackRole ?? null }
|
||||
: {}),
|
||||
...(body.enabled != null ? { enabled: body.enabled } : {}),
|
||||
...(body.priority != null ? { priority: body.priority } : {}),
|
||||
updated_by: request.employee.id,
|
||||
@@ -263,6 +294,7 @@ async function approvalRulesRoutes(fastify) {
|
||||
level1_role: true,
|
||||
level2_role: true,
|
||||
level3_role: true,
|
||||
owner_self_fallback_role: true,
|
||||
enabled: true,
|
||||
priority: true,
|
||||
created_at: true,
|
||||
@@ -291,4 +323,3 @@ async function approvalRulesRoutes(fastify) {
|
||||
}
|
||||
|
||||
module.exports = approvalRulesRoutes;
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ function mapApprovalRule(item) {
|
||||
level1Role: item.level1_role,
|
||||
level2Role: item.level2_role ?? null,
|
||||
level3Role: item.level3_role ?? null,
|
||||
ownerSelfFallbackRole: item.owner_self_fallback_role ?? null,
|
||||
enabled: item.enabled,
|
||||
priority: item.priority,
|
||||
createdAt: item.created_at?.toISOString?.() ?? null,
|
||||
@@ -115,6 +116,7 @@ async function configReadRoutes(fastify) {
|
||||
level1_role: true,
|
||||
level2_role: true,
|
||||
level3_role: true,
|
||||
owner_self_fallback_role: true,
|
||||
enabled: true,
|
||||
priority: true,
|
||||
created_at: true,
|
||||
|
||||
@@ -32,7 +32,7 @@ const {
|
||||
const { issueDocumentNumber } = require("../services/documentSequence");
|
||||
const { openReadStream, StorageNotFoundError, writeBuffer } = require("../services/storage");
|
||||
const {
|
||||
buildWorkflowStepsFromApprovalRule,
|
||||
buildApprovalWorkflowSnapshot,
|
||||
getWorkflowStepsSnapshot,
|
||||
resolveActionableStep,
|
||||
toMoneyString,
|
||||
@@ -1024,6 +1024,7 @@ async function loadApprovalRuleForAmount(tx, totalAmount) {
|
||||
level1_role: true,
|
||||
level2_role: true,
|
||||
level3_role: true,
|
||||
owner_self_fallback_role: true,
|
||||
priority: true,
|
||||
},
|
||||
});
|
||||
@@ -3231,7 +3232,12 @@ async function reimbursementRoutes(fastify) {
|
||||
}
|
||||
|
||||
const approvalRule = await loadApprovalRuleForAmount(tx, reimbursement.total_amount);
|
||||
const workflowSteps = buildWorkflowStepsFromApprovalRule(approvalRule);
|
||||
const workflowSteps = await buildApprovalWorkflowSnapshot({
|
||||
tx,
|
||||
approvalRule,
|
||||
ownerId: reimbursement.owner_id,
|
||||
logger: fastify.log,
|
||||
});
|
||||
const { actionable, skipped } = await resolveActionableStep({
|
||||
tx,
|
||||
steps: workflowSteps,
|
||||
|
||||
在新工单中引用
屏蔽一个用户