diff --git a/extensions/general/mcp-server/__tests__/create-supplier-invoice-from-inbox.test.ts b/extensions/general/mcp-server/__tests__/create-supplier-invoice-from-inbox.test.ts index cbd1c969..9d004098 100644 --- a/extensions/general/mcp-server/__tests__/create-supplier-invoice-from-inbox.test.ts +++ b/extensions/general/mcp-server/__tests__/create-supplier-invoice-from-inbox.test.ts @@ -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: { diff --git a/extensions/general/mcp-server/server.ts b/extensions/general/mcp-server/server.ts index 18c8dab0..d50789c6 100644 --- a/extensions/general/mcp-server/server.ts +++ b/extensions/general/mcp-server/server.ts @@ -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')