实现腾讯通用票据OCR安全适配(07-28-travel-reimbursement-guide)
这个提交包含在:
+39
-12
@@ -1,6 +1,11 @@
|
||||
const cron = require("node-cron");
|
||||
|
||||
const { buildMissingRequiredOcrError, runOcrForInvoice } = require("../services/ocr");
|
||||
const {
|
||||
buildMissingRequiredOcrError,
|
||||
runOcrForInvoice,
|
||||
sanitizeOcrResultForPersistence,
|
||||
shouldDeduplicateByInvoiceNumberOnly,
|
||||
} = require("../services/ocr");
|
||||
const { publishInvoiceUpdated } = require("../services/events");
|
||||
const { resolveInvoiceDedupBlocker } = require("../services/invoiceGovernance");
|
||||
const { readFileBuffer } = require("../services/storage");
|
||||
@@ -117,18 +122,29 @@ async function getOrCreateOcrResult(prisma, config, claimedInvoice, imageBase64)
|
||||
});
|
||||
|
||||
if (cached) {
|
||||
const normalized = sanitizeOcrResultForPersistence(ocrProvider, cached.result_json);
|
||||
if (ocrProvider === "tencent") {
|
||||
await prisma.ocrResult.update({
|
||||
where: { id: cached.id },
|
||||
data: { result_json: normalized },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
ocrResultId: cached.id,
|
||||
normalized: cached.result_json,
|
||||
normalized,
|
||||
};
|
||||
}
|
||||
|
||||
const normalized = await runOcrForInvoice({
|
||||
config,
|
||||
ocrType: claimedInvoice.ocr_type,
|
||||
imageBase64,
|
||||
sourceMimeType: claimedInvoice.mime_type,
|
||||
});
|
||||
const normalized = sanitizeOcrResultForPersistence(
|
||||
ocrProvider,
|
||||
await runOcrForInvoice({
|
||||
config,
|
||||
ocrType: claimedInvoice.ocr_type,
|
||||
imageBase64,
|
||||
sourceMimeType: claimedInvoice.mime_type,
|
||||
}),
|
||||
);
|
||||
|
||||
try {
|
||||
const created = await prisma.ocrResult.create({
|
||||
@@ -164,9 +180,17 @@ async function getOrCreateOcrResult(prisma, config, claimedInvoice, imageBase64)
|
||||
throw error;
|
||||
}
|
||||
|
||||
const racedNormalized = sanitizeOcrResultForPersistence(ocrProvider, raced.result_json);
|
||||
if (ocrProvider === "tencent") {
|
||||
await prisma.ocrResult.update({
|
||||
where: { id: raced.id },
|
||||
data: { result_json: racedNormalized },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
ocrResultId: raced.id,
|
||||
normalized: raced.result_json,
|
||||
normalized: racedNormalized,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -246,10 +270,10 @@ async function markInvoiceAsQualityFailed(prisma, config, invoiceId, normalized,
|
||||
});
|
||||
}
|
||||
|
||||
async function markInvoiceAsFailed(prisma, claimedInvoice, errorMessage, config) {
|
||||
async function markInvoiceAsFailed(prisma, claimedInvoice, errorMessage, config, { terminal = false } = {}) {
|
||||
const now = new Date();
|
||||
const nextAttempts = Number(claimedInvoice.ocr_attempts || 0) + 1;
|
||||
const isTerminal = nextAttempts >= config.ocrMaxAttempts;
|
||||
const isTerminal = terminal || nextAttempts >= config.ocrMaxAttempts;
|
||||
const nextStatus = isTerminal ? "ocr_failed" : "pending_ocr";
|
||||
|
||||
await prisma.invoicePool.update({
|
||||
@@ -313,6 +337,7 @@ async function processClaimedInvoice(prisma, config, claimedInvoice, workerId, l
|
||||
fileSha256: claimedInvoice.file_sha256,
|
||||
invoiceCode: normalized.invoiceCode,
|
||||
invoiceNumber: normalized.invoiceNumber,
|
||||
deduplicateByInvoiceNumberOnly: shouldDeduplicateByInvoiceNumberOnly(normalized),
|
||||
ownerEmployeeId: claimedInvoice.owner_employee_id,
|
||||
},
|
||||
);
|
||||
@@ -336,7 +361,9 @@ async function processClaimedInvoice(prisma, config, claimedInvoice, workerId, l
|
||||
return { outcome: "available", status: "available" };
|
||||
} catch (error) {
|
||||
const message = trimErrorMessage(error);
|
||||
const status = await markInvoiceAsFailed(prisma, claimedInvoice, message, config);
|
||||
const status = await markInvoiceAsFailed(prisma, claimedInvoice, message, config, {
|
||||
terminal: error?.terminal === true,
|
||||
});
|
||||
logger.error(
|
||||
{
|
||||
err: error,
|
||||
|
||||
在新工单中引用
屏蔽一个用户