H1-H10 优化OCR实时体验闭环
这个提交包含在:
@@ -2,12 +2,14 @@ const fs = require("node:fs/promises");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
|
||||
const { runOcrOnce } = require("../../../src/jobs/ocrJob");
|
||||
const { runOcrForInvoiceId, runOcrOnce } = require("../../../src/jobs/ocrJob");
|
||||
|
||||
function buildClaimedInvoice(overrides = {}) {
|
||||
return {
|
||||
id: "11111111-1111-4111-8111-111111111111",
|
||||
file_sha256: "sha256-1",
|
||||
owner_employee_id: "employee-owner-1",
|
||||
uploaded_by_employee_id: "employee-upload-1",
|
||||
ocr_provider: "mock",
|
||||
ocr_type: "vat",
|
||||
storage_path: "20260428/invoice.txt",
|
||||
@@ -91,6 +93,45 @@ describe("ocrJob quality gate", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("processes a directed invoice and emits processed metadata", async () => {
|
||||
await fs.writeFile(
|
||||
path.join(tempDir, "20260428/invoice.txt"),
|
||||
"MOCK_DIRECTED_SUCCESS_R73",
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const prisma = buildPrisma([buildClaimedInvoice()]);
|
||||
const logger = {
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
const onInvoiceProcessed = vi.fn();
|
||||
|
||||
const result = await runOcrForInvoiceId(
|
||||
prisma,
|
||||
{
|
||||
storageDir: tempDir,
|
||||
ocrBatchSize: 10,
|
||||
ocrLockTtlSeconds: 300,
|
||||
ocrMaxAttempts: 3,
|
||||
ocrProvider: "mock",
|
||||
},
|
||||
logger,
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
"ocr-directed-worker",
|
||||
{ onInvoiceProcessed },
|
||||
);
|
||||
|
||||
expect(result).toEqual({ claimed: 1, available: 1, duplicate: 0, failed: 0 });
|
||||
expect(onInvoiceProcessed).toHaveBeenCalledWith({
|
||||
invoiceId: "11111111-1111-4111-8111-111111111111",
|
||||
status: "available",
|
||||
outcome: "available",
|
||||
ownerEmployeeId: "employee-owner-1",
|
||||
uploadedByEmployeeId: "employee-upload-1",
|
||||
});
|
||||
});
|
||||
|
||||
it("marks invoice ocr_failed when required VAT fields are missing", async () => {
|
||||
await fs.writeFile(
|
||||
path.join(tempDir, "20260428/invoice.txt"),
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
const { enqueueOcrInvoice, JOB_OCR_INVOICE } = require("../../../src/jobs/pgBossJobs");
|
||||
|
||||
describe("pgBossJobs enqueueOcrInvoice", () => {
|
||||
it("queues a directed OCR invoice job when pg-boss is available", async () => {
|
||||
const send = vi.fn(async () => ({ id: "job-1" }));
|
||||
|
||||
const result = await enqueueOcrInvoice(
|
||||
{
|
||||
config: {
|
||||
jobDriver: "pgboss",
|
||||
ocrProvider: "mock",
|
||||
},
|
||||
boss: { send },
|
||||
},
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
);
|
||||
|
||||
expect(result).toEqual({ mode: "queued" });
|
||||
expect(send).toHaveBeenCalledWith(JOB_OCR_INVOICE, {
|
||||
invoiceId: "11111111-1111-4111-8111-111111111111",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back cleanly when pg-boss is unavailable", async () => {
|
||||
await expect(
|
||||
enqueueOcrInvoice(
|
||||
{
|
||||
config: {
|
||||
jobDriver: "pgboss",
|
||||
ocrProvider: "mock",
|
||||
},
|
||||
boss: null,
|
||||
},
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
),
|
||||
).resolves.toEqual({ mode: "fallback" });
|
||||
});
|
||||
|
||||
it("does not queue OCR when the OCR provider is disabled", async () => {
|
||||
const send = vi.fn();
|
||||
|
||||
const result = await enqueueOcrInvoice(
|
||||
{
|
||||
config: {
|
||||
jobDriver: "pgboss",
|
||||
ocrProvider: "off",
|
||||
},
|
||||
boss: { send },
|
||||
},
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
);
|
||||
|
||||
expect(result).toEqual({ mode: "fallback" });
|
||||
expect(send).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户