R92 A2 修复发票 OCR provider 与二进制写库
这个提交包含在:
+25
-8
@@ -4,6 +4,16 @@ const { buildMissingRequiredOcrError, runOcrForInvoice } = require("../services/
|
||||
const { readFileBuffer } = require("../services/storage");
|
||||
|
||||
const OCR_DUPLICATE_ERROR = "duplicate_invoice_code_number";
|
||||
const SUPPORTED_OCR_PROVIDERS = new Set(["tencent", "mock"]);
|
||||
|
||||
function getEffectiveOcrProvider(config) {
|
||||
const provider = config?.ocrProvider;
|
||||
if (!SUPPORTED_OCR_PROVIDERS.has(provider)) {
|
||||
throw new Error(`Unsupported OCR provider: ${provider}`);
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
function trimErrorMessage(error) {
|
||||
const code = typeof error?.code === "string" ? error.code : null;
|
||||
@@ -59,7 +69,7 @@ async function claimPendingInvoices(prisma, config, workerId) {
|
||||
ocr_lock_owner = ${workerId}
|
||||
FROM candidates
|
||||
WHERE i.id = candidates.id
|
||||
RETURNING i.id, i.file_sha256, i.ocr_type, i.storage_path, i.mime_type, i.ocr_attempts
|
||||
RETURNING i.id, i.file_sha256, i.ocr_provider, i.ocr_type, i.storage_path, i.mime_type, i.ocr_attempts
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -89,10 +99,11 @@ async function findDuplicateInvoice(prisma, invoiceId, invoiceCode, invoiceNumbe
|
||||
}
|
||||
|
||||
async function getOrCreateOcrResult(prisma, config, claimedInvoice, imageBase64) {
|
||||
const ocrProvider = getEffectiveOcrProvider(config);
|
||||
const where = {
|
||||
file_sha256_ocr_provider_ocr_type: {
|
||||
file_sha256: claimedInvoice.file_sha256,
|
||||
ocr_provider: "tencent",
|
||||
ocr_provider: ocrProvider,
|
||||
ocr_type: claimedInvoice.ocr_type,
|
||||
},
|
||||
};
|
||||
@@ -123,7 +134,7 @@ async function getOrCreateOcrResult(prisma, config, claimedInvoice, imageBase64)
|
||||
const created = await prisma.ocrResult.create({
|
||||
data: {
|
||||
file_sha256: claimedInvoice.file_sha256,
|
||||
ocr_provider: "tencent",
|
||||
ocr_provider: ocrProvider,
|
||||
ocr_type: claimedInvoice.ocr_type,
|
||||
result_json: normalized,
|
||||
},
|
||||
@@ -160,12 +171,13 @@ async function getOrCreateOcrResult(prisma, config, claimedInvoice, imageBase64)
|
||||
}
|
||||
}
|
||||
|
||||
async function markInvoiceAsDuplicate(prisma, invoiceId, normalized, ocrResultId) {
|
||||
async function markInvoiceAsDuplicate(prisma, config, invoiceId, normalized, ocrResultId) {
|
||||
const now = new Date();
|
||||
await prisma.invoicePool.update({
|
||||
where: { id: invoiceId },
|
||||
data: {
|
||||
status: "ocr_failed",
|
||||
ocr_provider: getEffectiveOcrProvider(config),
|
||||
ocr_result_id: ocrResultId,
|
||||
invoice_code: normalized.invoiceCode ?? null,
|
||||
invoice_number: normalized.invoiceNumber ?? null,
|
||||
@@ -184,12 +196,13 @@ async function markInvoiceAsDuplicate(prisma, invoiceId, normalized, ocrResultId
|
||||
});
|
||||
}
|
||||
|
||||
async function markInvoiceAsAvailable(prisma, invoiceId, normalized, ocrResultId) {
|
||||
async function markInvoiceAsAvailable(prisma, config, invoiceId, normalized, ocrResultId) {
|
||||
const now = new Date();
|
||||
await prisma.invoicePool.update({
|
||||
where: { id: invoiceId },
|
||||
data: {
|
||||
status: "available",
|
||||
ocr_provider: getEffectiveOcrProvider(config),
|
||||
ocr_result_id: ocrResultId,
|
||||
invoice_code: normalized.invoiceCode ?? null,
|
||||
invoice_number: normalized.invoiceNumber ?? null,
|
||||
@@ -208,12 +221,13 @@ async function markInvoiceAsAvailable(prisma, invoiceId, normalized, ocrResultId
|
||||
});
|
||||
}
|
||||
|
||||
async function markInvoiceAsQualityFailed(prisma, invoiceId, normalized, ocrResultId, errorMessage) {
|
||||
async function markInvoiceAsQualityFailed(prisma, config, invoiceId, normalized, ocrResultId, errorMessage) {
|
||||
const now = new Date();
|
||||
await prisma.invoicePool.update({
|
||||
where: { id: invoiceId },
|
||||
data: {
|
||||
status: "ocr_failed",
|
||||
ocr_provider: getEffectiveOcrProvider(config),
|
||||
ocr_result_id: ocrResultId,
|
||||
invoice_code: normalized.invoiceCode ?? null,
|
||||
invoice_number: normalized.invoiceNumber ?? null,
|
||||
@@ -241,6 +255,7 @@ async function markInvoiceAsFailed(prisma, claimedInvoice, errorMessage, config)
|
||||
where: { id: claimedInvoice.id },
|
||||
data: {
|
||||
status: isTerminal ? "ocr_failed" : "pending_ocr",
|
||||
ocr_provider: getEffectiveOcrProvider(config),
|
||||
ocr_attempts: nextAttempts,
|
||||
ocr_last_error: errorMessage,
|
||||
ocr_completed_at: isTerminal ? now : null,
|
||||
@@ -269,6 +284,7 @@ async function processClaimedInvoice(prisma, config, claimedInvoice, workerId, l
|
||||
if (qualityError) {
|
||||
await markInvoiceAsQualityFailed(
|
||||
prisma,
|
||||
config,
|
||||
claimedInvoice.id,
|
||||
normalized,
|
||||
ocrResultId,
|
||||
@@ -295,7 +311,7 @@ async function processClaimedInvoice(prisma, config, claimedInvoice, workerId, l
|
||||
);
|
||||
|
||||
if (duplicate) {
|
||||
await markInvoiceAsDuplicate(prisma, claimedInvoice.id, normalized, ocrResultId);
|
||||
await markInvoiceAsDuplicate(prisma, config, claimedInvoice.id, normalized, ocrResultId);
|
||||
logger.warn(
|
||||
{
|
||||
invoiceId: claimedInvoice.id,
|
||||
@@ -308,7 +324,7 @@ async function processClaimedInvoice(prisma, config, claimedInvoice, workerId, l
|
||||
return { outcome: "duplicate" };
|
||||
}
|
||||
|
||||
await markInvoiceAsAvailable(prisma, claimedInvoice.id, normalized, ocrResultId);
|
||||
await markInvoiceAsAvailable(prisma, config, claimedInvoice.id, normalized, ocrResultId);
|
||||
return { outcome: "available" };
|
||||
} catch (error) {
|
||||
const message = trimErrorMessage(error);
|
||||
@@ -427,6 +443,7 @@ function startOcrJob(fastify) {
|
||||
module.exports = {
|
||||
OCR_DUPLICATE_ERROR,
|
||||
claimPendingInvoices,
|
||||
getEffectiveOcrProvider,
|
||||
markInvoiceAsQualityFailed,
|
||||
runOcrOnce,
|
||||
startOcrJob,
|
||||
|
||||
在新工单中引用
屏蔽一个用户