R71-A4 澄清报销缺票提示并修复发票上传挂起

这个提交包含在:
2026-04-28 19:24:51 +08:00
父节点 a5eac04a06
当前提交 1c2aaedd78
共修改 5 个文件,包含 235 行新增和 44 行删除
+49 -44
查看文件
@@ -203,13 +203,16 @@ async function invoicePoolRoutes(fastify) {
preHandler: fastify.requirePermission("invoice.upload"),
},
async (request, reply) => {
let filePart = null;
let fileBuffer = null;
let fileName = null;
let fileMimeType = null;
let fileSize = 0;
const fields = {};
try {
for await (const part of request.parts()) {
if (part.type === "file") {
if (filePart) {
if (fileBuffer) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
@@ -217,10 +220,46 @@ async function invoicePoolRoutes(fastify) {
details: { file: "only_single_file_supported" },
});
}
filePart = part;
} else {
fields[part.fieldname] = part.value;
fileName = part.filename ?? "upload";
fileMimeType = part.mimetype;
if (!MIME_ALLOWLIST.has(fileMimeType)) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 400,
details: { file: "unsupported_mime_type" },
});
}
const chunks = [];
for await (const chunk of part.file) {
fileSize += chunk.length;
if (fileSize > fastify.config.maxUploadSizeBytes) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 400,
details: { file: "file_too_large" },
});
}
chunks.push(chunk);
}
if (fileSize === 0) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 400,
details: { file: "empty_file" },
});
}
fileBuffer = Buffer.concat(chunks);
continue;
}
fields[part.fieldname] = part.value;
}
} catch (error) {
if (error?.code === "FST_REQ_FILE_TOO_LARGE") {
@@ -234,7 +273,7 @@ async function invoicePoolRoutes(fastify) {
throw error;
}
if (!filePart) {
if (!fileBuffer) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
@@ -243,15 +282,6 @@ async function invoicePoolRoutes(fastify) {
});
}
if (!MIME_ALLOWLIST.has(filePart.mimetype)) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 400,
details: { file: "unsupported_mime_type" },
});
}
const ownerEmployeeId =
typeof fields.owner_employee_id === "string" && fields.owner_employee_id.trim()
? fields.owner_employee_id.trim()
@@ -315,40 +345,15 @@ async function invoicePoolRoutes(fastify) {
});
}
const chunks = [];
let fileSize = 0;
for await (const chunk of filePart.file) {
fileSize += chunk.length;
if (fileSize > fastify.config.maxUploadSizeBytes) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 400,
details: { file: "file_too_large" },
});
}
chunks.push(chunk);
}
if (fileSize === 0) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 400,
details: { file: "empty_file" },
});
}
const fileBuffer = Buffer.concat(chunks);
const fileSha256 = sha256Hex(fileBuffer);
const datePrefix = compactDateString(fastify.config.appTimezone);
const extension = path.extname(filePart.filename ?? "").toLowerCase();
const extension = path.extname(fileName ?? "").toLowerCase();
const objectKey = path.posix.join(datePrefix, `${crypto.randomUUID()}${extension}`);
const storagePath = await writeBuffer({
config: fastify.config,
key: objectKey,
buffer: fileBuffer,
contentType: filePart.mimetype,
contentType: fileMimeType,
});
const status = isOcrEnabledForType(fastify.config, ocrType) ? "pending_ocr" : "available";
@@ -360,8 +365,8 @@ async function invoicePoolRoutes(fastify) {
owner_employee_id: ownerEmployeeId,
uploaded_by_employee_id: request.employee.id,
storage_path: storagePath,
original_name: filePart.filename ?? "upload",
mime_type: filePart.mimetype,
original_name: fileName ?? "upload",
mime_type: fileMimeType,
file_size: fileSize,
file_sha256: fileSha256,
ocr_type: ocrType,