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) { throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 400, details: parsed.error.flatten(), }); } const enabledRaw = parsed.data.enabled; const enabled = enabledRaw == null ? null : ["true", "1"].includes(enabledRaw) ? true : ["false", "0"].includes(enabledRaw) ? false : null; 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, name: item.name, minAmount: item.min_amount?.toString?.() ?? String(item.min_amount ?? "0"), maxAmount: item.max_amount == null ? null : item.max_amount?.toString?.() ?? String(item.max_amount), level1Role: item.level1_role, level2Role: item.level2_role ?? null, level3Role: item.level3_role ?? null, enabled: item.enabled, priority: item.priority, createdAt: item.created_at?.toISOString?.() ?? null, updatedAt: item.updated_at?.toISOString?.() ?? null, }; } function mapDocumentSequence(config) { return { 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", { preHandler: fastify.authenticate, }, async (request, reply) => { const query = parseEnabledQuery(request.query); const items = await fastify.prisma.approvalRule.findMany({ where: { ...(query.enabled == null ? {} : { enabled: query.enabled }), }, orderBy: [{ priority: "asc" }, { min_amount: "asc" }], select: { id: true, name: true, min_amount: true, max_amount: true, level1_role: true, level2_role: true, level3_role: true, enabled: true, priority: true, created_at: true, updated_at: true, }, }); return sendData(reply, request, { enabled: query.enabled, items: items.map(mapApprovalRule), }); }, ); fastify.get( "/document-sequences/reimbursement", { preHandler: fastify.requirePermission("system.settings"), }, async (request, reply) => { const config = await loadDocumentSequenceConfig(fastify, "reimbursement"); return sendData(reply, request, mapDocumentSequence(config)); }, ); 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;