feat(mcp): add invoice_date_override to create_supplier_invoice_from_inbox (#848)

Unblocks inbox items where OCR missed the invoice date: priority is override, then extracted invoiceDate, then the existing hard error. Both date overrides now reject non-ISO input at staging time instead of failing opaquely at booking. Includes dry_run and validation tests.
This commit is contained in:
Jonas Flodén
2026-07-06 11:24:05 +02:00
committed by GitHub
parent ac1529e413
commit b700108107
2 changed files with 53 additions and 1 deletions
@@ -491,6 +491,51 @@ describe('gnubok_create_supplier_invoice_from_inbox: execute', () => {
expect('dimensions' in params.items[0]).toBe(false)
})
it('invoice_date_override rescues an inbox item with no extracted invoiceDate', async () => {
const extractedNoDate = {
...baseExtracted,
invoice: { ...baseExtracted.invoice, invoiceDate: null },
}
const supabase = makeMock({
inbox: {
id: 'inbox-10',
status: 'received',
extracted_data: extractedNoDate,
matched_supplier_id: 'supplier-1',
created_supplier_invoice_id: null,
document_id: 'doc-10',
},
})
const tool = tools.find((t) => t.name === 'gnubok_create_supplier_invoice_from_inbox')!
const result = (await tool.execute(
{ inbox_item_id: 'inbox-10', dry_run: true, invoice_date_override: '2025-07-15' },
'company-1', 'user-1', supabase,
)) as { dry_run: boolean; preview: { invoice_date: string } }
expect(result.dry_run).toBe(true)
expect(result.preview.invoice_date).toBe('2025-07-15')
})
it('rejects a non-ISO invoice_date_override before staging', async () => {
const supabase = makeMock({
inbox: {
id: 'inbox-11',
status: 'received',
extracted_data: baseExtracted,
matched_supplier_id: 'supplier-1',
created_supplier_invoice_id: null,
document_id: 'doc-11',
},
})
const tool = tools.find((t) => t.name === 'gnubok_create_supplier_invoice_from_inbox')!
await expect(
tool.execute(
{ inbox_item_id: 'inbox-11', dry_run: true, invoice_date_override: '15/07/2025' },
'company-1', 'user-1', supabase,
),
).rejects.toThrow(/invoice_date_override must be an ISO date/)
})
it('throws when extracted_data is missing', async () => {
const supabase = makeMock({
inbox: {
+8 -1
View File
@@ -7572,6 +7572,7 @@ export const tools: McpTool[] = [
inbox_item_id: { type: 'string', description: 'UUID of the inbox item to convert' },
supplier_id_override: { type: 'string', description: 'Force this supplier UUID instead of the matched/extracted one' },
vat_treatment_override: { type: 'string', enum: ['standard_25', 'reduced_12', 'reduced_6', 'reverse_charge', 'export', 'exempt'], description: 'Override extracted VAT treatment' },
invoice_date_override: { type: 'string', description: 'Override extracted invoice date (YYYY-MM-DD). Use when OCR misses the date.' },
due_date_override: { type: 'string', description: 'Override extracted due date (YYYY-MM-DD)' },
line_overrides: {
type: 'array',
@@ -7756,7 +7757,13 @@ export const tools: McpTool[] = [
// Assemble core invoice fields
const currency = (invoiceExt?.currency as string) || 'SEK'
const invoiceDate = (invoiceExt?.invoiceDate as string) || null
for (const key of ['invoice_date_override', 'due_date_override'] as const) {
const value = args[key] as string | undefined
if (value !== undefined && !ISO_DATE_RE.test(value)) {
throw new Error(`${key} must be an ISO date (YYYY-MM-DD), got "${value}"`)
}
}
const invoiceDate = (args.invoice_date_override as string | undefined) ?? (invoiceExt?.invoiceDate as string) ?? null
const dueDate = (args.due_date_override as string | undefined) ?? (invoiceExt?.dueDate as string | undefined) ?? null
const supplierInvoiceNumber = (invoiceExt?.invoiceNumber as string) || ''
if (!invoiceDate) throw new Error('Extracted invoice has no invoice date')