feat(G2-1,G2-2,G2-3): 扩展预算编号与工作流跨域桥接

这个提交包含在:
2026-03-07 18:59:44 +08:00
父节点 09b6f4fbb0
当前提交 52558f22c3
共修改 12 个文件,包含 1211 行新增和 175 行删除
+55 -46
查看文件
@@ -2,11 +2,19 @@ const { z } = require("zod");
const { AppError } = require("../utils/AppError");
const { sendData } = require("../utils/reply");
const {
normalizeDocumentType,
resolveDocumentSequenceConfig,
} = require("../services/documentSequence");
const ENABLED_QUERY_SCHEMA = z.object({
enabled: z.enum(["true", "false", "1", "0"]).optional(),
});
const DOCUMENT_TYPE_PARAM_SCHEMA = z.object({
documentType: z.string().trim().min(1).max(64).regex(/^[a-z_]+$/),
});
function parseEnabledQuery(query) {
const parsed = ENABLED_QUERY_SCHEMA.safeParse(query ?? {});
if (!parsed.success) {
@@ -31,6 +39,20 @@ function parseEnabledQuery(query) {
return { enabled };
}
function parseDocumentTypeParams(params) {
const parsed = DOCUMENT_TYPE_PARAM_SCHEMA.safeParse(params ?? {});
if (!parsed.success) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 400,
details: parsed.error.flatten(),
});
}
return normalizeDocumentType(parsed.data.documentType);
}
function mapApprovalRule(item) {
return {
id: item.id,
@@ -47,39 +69,30 @@ function mapApprovalRule(item) {
};
}
function mapDocumentSequence(item, exists) {
const fallback = {
documentType: "reimbursement",
prefix: "BX",
dateFormat: "YYYYMMDD",
separator: "-",
numberLength: 4,
resetPeriod: "none",
currentNumber: 0,
lastResetDate: null,
updatedAt: null,
updatedBy: null,
};
if (!item) {
return { exists, ...fallback };
}
function mapDocumentSequence(config) {
return {
exists,
documentType: item.document_type,
prefix: item.prefix,
dateFormat: item.date_format,
separator: item.separator,
numberLength: item.number_length,
resetPeriod: item.reset_period,
currentNumber: item.current_number,
lastResetDate: item.last_reset_date?.toISOString?.()?.slice(0, 10) ?? null,
updatedAt: item.updated_at?.toISOString?.() ?? null,
updatedBy: item.updated_by ?? null,
exists: Boolean(config.exists),
source: config.source,
documentType: config.documentType,
documentCategoryCode: config.documentCategoryCode ?? null,
prefix: config.prefix,
dateFormat: config.dateFormat,
separator: config.separator,
numberLength: config.numberLength,
resetPeriod: config.resetPeriod,
currentNumber: config.currentNumber,
lastResetDate: config.lastResetDate ?? null,
updatedAt: config.updatedAt ?? null,
updatedBy: config.updatedBy ?? null,
};
}
async function loadDocumentSequenceConfig(fastify, documentType) {
return resolveDocumentSequenceConfig(fastify.prisma, {
documentType,
});
}
async function configReadRoutes(fastify) {
fastify.get(
"/approval-rules",
@@ -122,26 +135,22 @@ async function configReadRoutes(fastify) {
preHandler: fastify.requirePermission("system.settings"),
},
async (request, reply) => {
const sequence = await fastify.prisma.documentSequence.findUnique({
where: { document_type: "reimbursement" },
select: {
document_type: true,
prefix: true,
date_format: true,
separator: true,
number_length: true,
reset_period: true,
current_number: true,
last_reset_date: true,
updated_at: true,
updated_by: true,
},
});
const config = await loadDocumentSequenceConfig(fastify, "reimbursement");
return sendData(reply, request, mapDocumentSequence(config));
},
);
return sendData(reply, request, mapDocumentSequence(sequence, Boolean(sequence)));
fastify.get(
"/document-sequences/:documentType",
{
preHandler: fastify.requirePermission("system.settings"),
},
async (request, reply) => {
const documentType = parseDocumentTypeParams(request.params);
const config = await loadDocumentSequenceConfig(fastify, documentType);
return sendData(reply, request, mapDocumentSequence(config));
},
);
}
module.exports = configReadRoutes;