/** * DKV Fleet Module API client. * Consumes the /dkv/* REST surface built in Plan 04. * All calls use credentials: 'include' for cookie-based auth. * * Security (T-07-12): DkvConfig never exposes the password — * only hasPassword (boolean) is returned by the server. * The password field is only sent when the user types a new one. */ const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'; // --- Types mirroring the Plan 04 backend contract --- /** * DKV module inbox configuration. * Note: password is NEVER returned (T-07-12). Only hasPassword is exposed. */ export interface DkvConfig { protocol: 'imap' | 'exchange'; host: string; port: number; encryption: 'none' | 'starttls' | 'ssl-tls'; folder: string; senderFilter?: string | null; pollIntervalMin: number; isActive: boolean; exportRecipient: string; vehicleFormatString: string; username?: string | null; domain?: string | null; /** true if a password is stored server-side — never the actual secret */ hasPassword: boolean; } /** * A single entry in the DKV invoice processing history. * Mirrors DkvInvoiceHistory Prisma model. */ export interface DkvHistoryEntry { id: string; datumZeit: string; rechnungsnummer: string; anzahlFahrzeuge: number; anzahlTransaktionen: number; status: 'Verarbeitet' | 'Fehler' | 'Versand fehlgeschlagen'; errorMessage?: string | null; exportFilename?: string | null; } /** * Vehicle master record for DKV→driver mapping (DKV-03). */ export interface DkvVehicle { id: string; kennzeichen: string; marke: string; modell: string; fahrer: string; } /** * Payload for creating a new vehicle. */ export interface CreateVehiclePayload { kennzeichen: string; marke: string; modell: string; fahrer: string; } /** * Payload for updating an existing vehicle. All fields optional. */ export interface UpdateVehiclePayload { kennzeichen?: string; marke?: string; modell?: string; fahrer?: string; } /** * Paginated history response from GET /dkv/history. */ export interface DkvHistoryPage { items: DkvHistoryEntry[]; total: number; page: number; limit: number; } // --- API functions --- /** * Fetch the current DKV module inbox configuration. * GET /dkv/config */ export async function fetchConfig(): Promise { const res = await fetch(`${API_URL}/dkv/config`, { credentials: 'include', }); if (res.status === 404) return null; if (!res.ok) throw new Error('Failed to fetch DKV config'); return res.json(); } /** * Save the DKV module inbox configuration. * PUT /dkv/config * Only include `password` in payload when the user has entered a new one. */ export async function saveConfig( payload: Partial & { password?: string }, ): Promise { const res = await fetch(`${API_URL}/dkv/config`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(payload), }); if (!res.ok) throw new Error('Failed to save DKV config'); return res.json(); } /** * Test the inbox connection with the current (or pending) config. * POST /dkv/test-connection * Returns { success: boolean, message?: string } */ export async function testConnection( payload: Partial & { password?: string }, ): Promise<{ success: boolean; message?: string }> { const res = await fetch(`${API_URL}/dkv/test-connection`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(payload), }); if (!res.ok) throw new Error('Failed to test DKV connection'); return res.json(); } /** * Trigger an immediate inbox check (manual poll). * POST /dkv/check-now */ export async function checkNow(): Promise<{ status: string; message: string; checkedAt: string }> { const res = await fetch(`${API_URL}/dkv/check-now`, { method: 'POST', credentials: 'include', }); if (!res.ok) throw new Error('Failed to trigger DKV inbox check'); return res.json(); } /** * Fetch paginated invoice processing history. * GET /dkv/history?page=&limit= */ export async function fetchHistory( page = 1, limit = 25, ): Promise { const params = new URLSearchParams(); params.set('page', String(page)); params.set('limit', String(limit)); const res = await fetch(`${API_URL}/dkv/history?${params.toString()}`, { credentials: 'include', }); if (!res.ok) throw new Error('Failed to fetch DKV history'); return res.json(); } /** * Fetch all vehicles from the DKV vehicle master list. * GET /dkv/vehicles */ export async function fetchVehicles(): Promise { const res = await fetch(`${API_URL}/dkv/vehicles`, { credentials: 'include', }); if (!res.ok) throw new Error('Failed to fetch DKV vehicles'); return res.json(); } /** * Create a new vehicle entry. * POST /dkv/vehicles */ export async function createVehicle( payload: CreateVehiclePayload, ): Promise { const res = await fetch(`${API_URL}/dkv/vehicles`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(payload), }); if (!res.ok) throw new Error('Failed to create DKV vehicle'); return res.json(); } /** * Update an existing vehicle. * PUT /dkv/vehicles/:id */ export async function updateVehicle( id: string, payload: UpdateVehiclePayload, ): Promise { const res = await fetch(`${API_URL}/dkv/vehicles/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(payload), }); if (!res.ok) throw new Error('Failed to update DKV vehicle'); return res.json(); } /** * Delete a vehicle from the master list. * DELETE /dkv/vehicles/:id */ export async function deleteVehicle(id: string): Promise { const res = await fetch(`${API_URL}/dkv/vehicles/${id}`, { method: 'DELETE', credentials: 'include', }); if (!res.ok) throw new Error('Failed to delete DKV vehicle'); } /** * Import vehicles from a CSV file. * POST /dkv/vehicles/import (multipart/form-data) * mode: 'merge' | 'replace' * Do NOT set Content-Type manually — browser sets it with boundary. */ export async function importVehiclesCsv( file: File, mode: 'merge' | 'replace', ): Promise<{ count: number; mode: string }> { const form = new FormData(); form.append('file', file); form.append('mode', mode); const res = await fetch(`${API_URL}/dkv/vehicles/import`, { method: 'POST', credentials: 'include', body: form, // Content-Type is NOT set manually — fetch sets multipart/form-data with boundary }); if (!res.ok) throw new Error('Failed to import DKV vehicles CSV'); return res.json(); } /** * Build the full download URL for an export file. * Pure helper — no fetch call. Server enforces the DKV_*.xlsx whitelist (T-07-13). */ export function exportFileUrl(filename: string): string { return `${API_URL}/dkv/exports/${encodeURIComponent(filename)}`; }