feat: invoice inbox extension — conversion, workspace UI, Gmail UX (#255)
* feat: invoice inbox extension — conversion, workspace UI, Gmail UX Complete the invoice-inbox extension with full end-to-end flow: - Add POST /items/:id/convert route to create supplier invoices from classified inbox items, with accrual journal entry and document linking - Add PATCH /items/:id/reject route to dismiss non-relevant items - Add workspace UI at /e/general/invoice-inbox with items table, status filtering, convert dialog, and match confirmation - Add Gmail connection banner (connect/disconnect/status) in workspace - Add one-click supplier creation from AI-extracted data - Add transaction auto-matching with fuzzy name + currency-aware amount - Add event emission (received, extracted, confirmed) on classification - Redirect OAuth callback to workspace instead of /settings/banking - Fix extension catch-all body clone for POST routes with path params - Fix duplicate Löner nav entry from salary module merge - Remove summary cards from expenses and supplier invoices pages - Fix supplier-invoices/new amount input (valueAsNumber → Controller) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Greptile review — company_id filters, currency guard, skipAuth clone - Add company_id filter to reject route update (defense in depth) - Add company_id filter to document_attachments journal entry link - Guard sekMatch with tx.currency === 'SEK' to prevent false matches - Clone request in skipAuth branch for consistency with auth branch Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
bb0db7a588
commit
4fbfadb2b7
@@ -98,10 +98,11 @@ async function handleRequest(
|
||||
for (const [key, value] of Object.entries(extractedParams)) {
|
||||
url.searchParams.set(`_${key}`, value)
|
||||
}
|
||||
const cloned = request.clone()
|
||||
handlerRequest = new Request(url.toString(), {
|
||||
method: request.method,
|
||||
headers: request.headers,
|
||||
body: request.body,
|
||||
method: cloned.method,
|
||||
headers: cloned.headers,
|
||||
body: cloned.body,
|
||||
// @ts-expect-error -- duplex needed for streaming body
|
||||
duplex: 'half',
|
||||
})
|
||||
@@ -126,10 +127,12 @@ async function handleRequest(
|
||||
for (const [key, value] of Object.entries(extractedParams)) {
|
||||
url.searchParams.set(`_${key}`, value)
|
||||
}
|
||||
// Clone first to avoid body stream locking issues when transferring to new Request
|
||||
const cloned = request.clone()
|
||||
handlerRequest = new Request(url.toString(), {
|
||||
method: request.method,
|
||||
headers: request.headers,
|
||||
body: request.body,
|
||||
method: cloned.method,
|
||||
headers: cloned.headers,
|
||||
body: cloned.body,
|
||||
// @ts-expect-error -- duplex needed for streaming body
|
||||
duplex: 'half',
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user