154 行
4.1 KiB
JavaScript
154 行
4.1 KiB
JavaScript
const { runTencentGeneralInvoiceOcr } = require("./tencent");
|
|
const { runMockOcr } = require("./mock");
|
|
|
|
const OCR_PDF_MAX_BYTES = 7 * 1024 * 1024;
|
|
|
|
const REQUIRED_OCR_FIELDS_BY_TYPE = {
|
|
vat: ["invoiceCode", "invoiceNumber", "invoiceDate", "totalAmount", "sellerName"],
|
|
};
|
|
|
|
const REQUIRED_OCR_FIELDS_BY_DOCUMENT_CATEGORY = {
|
|
vat_invoice: ["invoiceCode", "invoiceNumber", "invoiceDate", "totalAmount", "sellerName"],
|
|
full_vat_invoice: ["invoiceNumber", "invoiceDate", "totalAmount", "sellerName"],
|
|
electronic_train_ticket: ["invoiceNumber", "invoiceDate", "totalAmount", "buyerName"],
|
|
};
|
|
|
|
const NUMBER_ONLY_DEDUP_DOCUMENT_CATEGORIES = new Set([
|
|
"full_vat_invoice",
|
|
"electronic_train_ticket",
|
|
]);
|
|
|
|
const SAFE_DOCUMENT_CATEGORIES = new Set([
|
|
"vat_invoice",
|
|
"full_vat_invoice",
|
|
"electronic_train_ticket",
|
|
]);
|
|
|
|
const SAFE_QUALITY_ERRORS = new Set([
|
|
"invoice_not_detected",
|
|
"multiple_invoices_not_supported",
|
|
"invoice_recognition_failed",
|
|
"unsupported_invoice_subtype",
|
|
]);
|
|
|
|
const SAFE_NORMALIZED_FIELDS = [
|
|
"invoiceCode",
|
|
"invoiceNumber",
|
|
"invoiceDate",
|
|
"amount",
|
|
"taxAmount",
|
|
"totalAmount",
|
|
"sellerName",
|
|
"buyerName",
|
|
];
|
|
|
|
function isOcrEnabledForType(config, ocrType) {
|
|
if (config.ocrProvider === "mock") {
|
|
return ocrType === "vat";
|
|
}
|
|
|
|
return config.ocrProvider === "tencent" && ocrType === "vat";
|
|
}
|
|
|
|
function getMissingRequiredOcrFields(ocrType, normalized) {
|
|
const requiredFields =
|
|
REQUIRED_OCR_FIELDS_BY_DOCUMENT_CATEGORY[normalized?.documentCategory] ??
|
|
REQUIRED_OCR_FIELDS_BY_TYPE[ocrType] ??
|
|
[];
|
|
if (!requiredFields.length) {
|
|
return [];
|
|
}
|
|
|
|
return requiredFields.filter((field) => {
|
|
const value = normalized?.[field];
|
|
return value == null || value === "";
|
|
});
|
|
}
|
|
|
|
function buildMissingRequiredOcrError(ocrType, normalized) {
|
|
if (typeof normalized?.qualityError === "string" && normalized.qualityError) {
|
|
return normalized.qualityError;
|
|
}
|
|
|
|
const missingFields = getMissingRequiredOcrFields(ocrType, normalized);
|
|
if (!missingFields.length) {
|
|
return null;
|
|
}
|
|
|
|
return `missing_required_fields:${missingFields.join(",")}`;
|
|
}
|
|
|
|
async function runOcrForInvoice({ config, ocrType, imageBase64, sourceMimeType }) {
|
|
if (config.ocrProvider === "mock") {
|
|
return runMockOcr({ ocrType, imageBase64 });
|
|
}
|
|
|
|
if (config.ocrProvider !== "tencent") {
|
|
throw new Error(`Unsupported OCR provider: ${config.ocrProvider}`);
|
|
}
|
|
|
|
if (ocrType !== "vat") {
|
|
throw new Error(`Unsupported OCR type: ${ocrType}`);
|
|
}
|
|
|
|
return runTencentGeneralInvoiceOcr(config, { imageBase64, sourceMimeType });
|
|
}
|
|
|
|
function shouldDeduplicateByInvoiceNumberOnly(normalized) {
|
|
return Boolean(
|
|
normalized?.invoiceNumber &&
|
|
!normalized?.invoiceCode &&
|
|
NUMBER_ONLY_DEDUP_DOCUMENT_CATEGORIES.has(normalized?.documentCategory),
|
|
);
|
|
}
|
|
|
|
function normalizeSafeString(value, maxLength) {
|
|
if (typeof value !== "string") {
|
|
return null;
|
|
}
|
|
|
|
const normalized = value.trim();
|
|
return normalized ? normalized.slice(0, maxLength) : null;
|
|
}
|
|
|
|
function sanitizeOcrResultForPersistence(ocrProvider, normalized) {
|
|
if (ocrProvider !== "tencent") {
|
|
return normalized;
|
|
}
|
|
|
|
const safe = {
|
|
documentSubtype: normalizeSafeString(normalized?.documentSubtype, 64),
|
|
documentCategory: SAFE_DOCUMENT_CATEGORIES.has(normalized?.documentCategory)
|
|
? normalized.documentCategory
|
|
: null,
|
|
documentCount:
|
|
Number.isInteger(normalized?.documentCount) && normalized.documentCount >= 0
|
|
? Math.min(normalized.documentCount, 100)
|
|
: 1,
|
|
};
|
|
|
|
for (const field of SAFE_NORMALIZED_FIELDS) {
|
|
safe[field] = normalizeSafeString(normalized?.[field], field.endsWith("Name") ? 256 : 64);
|
|
}
|
|
|
|
if (SAFE_QUALITY_ERRORS.has(normalized?.qualityError)) {
|
|
safe.qualityError = normalized.qualityError;
|
|
}
|
|
|
|
if (normalized?.transportMode === "pdf_native") {
|
|
safe.transportMode = "pdf_native";
|
|
}
|
|
|
|
return safe;
|
|
}
|
|
|
|
module.exports = {
|
|
OCR_PDF_MAX_BYTES,
|
|
buildMissingRequiredOcrError,
|
|
getMissingRequiredOcrFields,
|
|
isOcrEnabledForType,
|
|
runOcrForInvoice,
|
|
sanitizeOcrResultForPersistence,
|
|
shouldDeduplicateByInvoiceNumberOnly,
|
|
};
|