import { account } from './appwrite' /** * Client fuer das Integrations-Backend (InvoiceNinja + Paperless). * Same-Origin unter /api/integrations, authentifiziert per Appwrite-JWT. */ const BASE = '/api/integrations' async function authHeaders(extra = {}) { const { jwt } = await account.createJWT() return { 'X-Appwrite-JWT': jwt, ...extra } } async function call(path, { method = 'GET', body } = {}) { const headers = await authHeaders(body ? { 'Content-Type': 'application/json' } : {}) const res = await fetch(`${BASE}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined, }) const data = await res.json().catch(() => ({})) if (!res.ok) { throw new Error(data.error || `Integrations-Fehler ${res.status}`) } return data } async function callBlob(path) { const headers = await authHeaders() const res = await fetch(`${BASE}${path}`, { headers }) if (!res.ok) throw new Error(`Download fehlgeschlagen (${res.status})`) return res.blob() } function qs(params = {}) { const sp = new URLSearchParams() Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== '') sp.set(k, v) }) const s = sp.toString() return s ? `?${s}` : '' } async function openBlobInNewTab(blob) { const url = URL.createObjectURL(blob) window.open(url, '_blank', 'noopener') setTimeout(() => URL.revokeObjectURL(url), 60_000) } export const integrationsApi = { // ---- health ---- health: () => call('/health'), // ---- InvoiceNinja: clients ---- listClients: (search = '') => call(`/clients${qs({ search })}`), createClient: (payload) => call('/clients', { method: 'POST', body: payload }), updateClient: (id, payload) => call(`/clients/${encodeURIComponent(id)}`, { method: 'PUT', body: payload }), // ---- InvoiceNinja: invoices ---- listInvoices: (clientId, perPage) => call(`/invoices${qs({ clientId, per_page: perPage })}`), invoicesSummary: () => call('/invoices/summary'), createInvoice: (payload) => call('/invoices', { method: 'POST', body: payload }), async openInvoicePdf(id) { const blob = await callBlob(`/invoices/${encodeURIComponent(id)}/pdf`) await openBlobInNewTab(blob) }, // ---- Paperless ---- listDocuments: (params = {}) => call(`/paperless/documents${qs(params)}`), ensureCorrespondent: (name) => call('/paperless/ensure-correspondent', { method: 'POST', body: { name } }), ensureTag: (name) => call('/paperless/ensure-tag', { method: 'POST', body: { name } }), uploadToPaperless: (payload) => call('/paperless/upload', { method: 'POST', body: payload }), async openDocument(id, kind = 'preview') { const blob = await callBlob(`/paperless/documents/${encodeURIComponent(id)}/${kind}`) await openBlobInNewTab(blob) }, } // Oeffentliche InvoiceNinja-URL (fuer Deeplinks ins Portal) export const INVOICE_NINJA_PUBLIC_URL = 'https://invoice.webklar.com' // InvoiceNinja status_id -> deutsche Bezeichnung + Farbe export const INVOICE_STATUS = { 1: { label: 'Entwurf', color: '#a0aec0' }, 2: { label: 'Versendet', color: '#3b82f6' }, 3: { label: 'Teilzahlung', color: '#f59e0b' }, 4: { label: 'Bezahlt', color: '#10b981' }, 5: { label: 'Storniert', color: '#ef4444' }, '-1': { label: 'Storniert', color: '#ef4444' }, } export function formatMoney(amount, currency = 'EUR') { const n = Number(amount || 0) try { return new Intl.NumberFormat('de-DE', { style: 'currency', currency }).format(n) } catch { return `${n.toFixed(2)} ${currency}` } }