315 lines
8.3 KiB
JavaScript
315 lines
8.3 KiB
JavaScript
// API Service für alle Backend-Calls
|
|
// Docker: Frontend (nginx:80) -> Backend (3001) via nginx proxy
|
|
const API_URL = '/api';
|
|
|
|
// Helper für API Calls
|
|
async function apiCall(endpoint, options = {}) {
|
|
const url = `${API_URL}${endpoint}`;
|
|
const config = {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...options.headers,
|
|
},
|
|
...options,
|
|
};
|
|
|
|
if (options.body && typeof options.body === 'object') {
|
|
config.body = JSON.stringify(options.body);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(url, config);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
throw new Error(error.error || `HTTP ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error(`API Error (${endpoint}):`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Kredite API
|
|
export const krediteAPI = {
|
|
getAll: (params = {}) => {
|
|
const queryParams = new URLSearchParams(params).toString();
|
|
return apiCall(`/kredite${queryParams ? '?' + queryParams : ''}`);
|
|
},
|
|
|
|
create: (data) => apiCall('/kredite', {
|
|
method: 'POST',
|
|
body: data,
|
|
}),
|
|
|
|
update: (id, data) => apiCall(`/kredite/${id}`, {
|
|
method: 'PUT',
|
|
body: data,
|
|
}),
|
|
|
|
// PATCH für partielle Updates (z.B. nur Restschuld)
|
|
patch: (id, data) => apiCall(`/kredite/${id}`, {
|
|
method: 'PATCH',
|
|
body: data,
|
|
}),
|
|
|
|
delete: (id) => apiCall(`/kredite/${id}`, {
|
|
method: 'DELETE',
|
|
}),
|
|
|
|
getBuchungen: (id) => apiCall(`/kredite/${id}/buchungen`),
|
|
|
|
addBuchung: (id, data) => apiCall(`/kredite/${id}/buchungen`, {
|
|
method: 'POST',
|
|
body: data,
|
|
}),
|
|
|
|
deleteBuchung: (id, buchungId) => apiCall(`/kredite/${id}/buchungen/${buchungId}`, {
|
|
method: 'DELETE',
|
|
}),
|
|
|
|
// Zahlungen API (Alias für Buchungen)
|
|
getZahlungen: (id) => apiCall(`/kredite/${id}/zahlungen`),
|
|
|
|
addZahlung: (id, data) => apiCall(`/kredite/${id}/zahlungen`, {
|
|
method: 'POST',
|
|
body: data,
|
|
}),
|
|
|
|
deleteZahlung: (id, zahlungId) => apiCall(`/kredite/${id}/zahlungen/${zahlungId}`, {
|
|
method: 'DELETE',
|
|
}),
|
|
};
|
|
|
|
// Stunden API
|
|
export const stundenAPI = {
|
|
getAll: (params = {}) => {
|
|
const queryParams = new URLSearchParams(params).toString();
|
|
return apiCall(`/stunden${queryParams ? '?' + queryParams : ''}`);
|
|
},
|
|
|
|
create: (data) => apiCall('/stunden', {
|
|
method: 'POST',
|
|
body: data,
|
|
}),
|
|
|
|
update: (id, data) => apiCall(`/stunden/${id}`, {
|
|
method: 'PUT',
|
|
body: data,
|
|
}),
|
|
|
|
delete: (id) => apiCall(`/stunden/${id}`, {
|
|
method: 'DELETE',
|
|
}),
|
|
};
|
|
|
|
// Nebenkosten API
|
|
export const nebenkostenAPI = {
|
|
getAll: (params = {}) => {
|
|
const queryParams = new URLSearchParams(params).toString();
|
|
return apiCall(`/nebenkosten${queryParams ? '?' + queryParams : ''}`);
|
|
},
|
|
|
|
create: (data) => apiCall('/nebenkosten', {
|
|
method: 'POST',
|
|
body: data,
|
|
}),
|
|
|
|
update: (id, data) => apiCall(`/nebenkosten/${id}`, {
|
|
method: 'PUT',
|
|
body: data,
|
|
}),
|
|
|
|
delete: (id) => apiCall(`/nebenkosten/${id}`, {
|
|
method: 'DELETE',
|
|
}),
|
|
};
|
|
|
|
// Kostenplanung API
|
|
export const kostenplanungAPI = {
|
|
getAll: (params = {}) => {
|
|
const queryParams = new URLSearchParams(params).toString();
|
|
return apiCall(`/kostenplanung${queryParams ? '?' + queryParams : ''}`);
|
|
},
|
|
|
|
create: (data) => apiCall('/kostenplanung', {
|
|
method: 'POST',
|
|
body: data,
|
|
}),
|
|
|
|
update: (id, data) => apiCall(`/kostenplanung/${id}`, {
|
|
method: 'PUT',
|
|
body: data,
|
|
}),
|
|
|
|
delete: (id) => apiCall(`/kostenplanung/${id}`, {
|
|
method: 'DELETE',
|
|
}),
|
|
};
|
|
|
|
// Geschäftsplanung API (neu)
|
|
export const geschaeftsplanungAPI = {
|
|
// Monatliche Übersicht mit Ergebnis und Cashflow
|
|
getMonatlich: (jahr) => apiCall(`/geschaeftsplanung/monatlich?jahr=${jahr}`),
|
|
|
|
// Kategorie-Übersicht
|
|
getKategorien: (jahr) => apiCall(`/geschaeftsplanung/kategorien?jahr=${jahr}`),
|
|
|
|
// Miete vs Nebenkosten pro Objekt
|
|
getObjektVergleich: (objekt, jahr) => apiCall(`/geschaeftsplanung/objektvergleich/${objekt}?jahr=${jahr}`),
|
|
|
|
// Umsatzvorschau (nur Einnahmen)
|
|
getUmsatzVorschau: (jahr) => apiCall(`/geschaeftsplanung/umsatzvorschau?jahr=${jahr}`),
|
|
};
|
|
|
|
// Kunden API (für Auftragsnachweise)
|
|
export const kundenAPI = {
|
|
getAll: () => apiCall('/kunden'),
|
|
|
|
create: (data) => apiCall('/kunden', {
|
|
method: 'POST',
|
|
body: data,
|
|
}),
|
|
|
|
update: (id, data) => apiCall(`/kunden/${id}`, {
|
|
method: 'PUT',
|
|
body: data,
|
|
}),
|
|
|
|
delete: (id) => apiCall(`/kunden/${id}`, {
|
|
method: 'DELETE',
|
|
}),
|
|
};
|
|
|
|
// Auftragsnachweise API
|
|
export const auftragsnachweisAPI = {
|
|
getAll: (params = {}) => {
|
|
const queryParams = new URLSearchParams(params).toString();
|
|
return apiCall(`/auftragsnachweise${queryParams ? '?' + queryParams : ''}`);
|
|
},
|
|
|
|
getById: (id) => apiCall(`/auftragsnachweise/${id}`),
|
|
|
|
create: (data) => {
|
|
// Stelle sicher, dass art_der_arbeit ein Array ist
|
|
const sanitizedData = {
|
|
...data,
|
|
art_der_arbeit: Array.isArray(data.art_der_arbeit)
|
|
? data.art_der_arbeit
|
|
: (data.art_der_arbeit ? [data.art_der_arbeit] : []),
|
|
stunden_ids: Array.isArray(data.stunden_ids)
|
|
? data.stunden_ids
|
|
: (data.stunden_ids ? [data.stunden_ids] : []),
|
|
};
|
|
return apiCall('/auftragsnachweise', {
|
|
method: 'POST',
|
|
body: sanitizedData,
|
|
});
|
|
},
|
|
|
|
delete: (id) => apiCall(`/auftragsnachweise/${id}`, {
|
|
method: 'DELETE',
|
|
}),
|
|
|
|
// PDF generieren und herunterladen
|
|
generatePDF: async (id, firmenname) => {
|
|
const response = await fetch(`${API_URL}/auftragsnachweise/${id}/pdf`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ firmenname }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
// Bei Fehler erst als Text versuchen, dann als JSON
|
|
const errorText = await response.text().catch(() => 'Unknown error');
|
|
let errorMessage = errorText;
|
|
try {
|
|
const errorJson = JSON.parse(errorText);
|
|
errorMessage = errorJson.error || errorText;
|
|
} catch (e) {
|
|
// Nicht JSON, Text verwenden
|
|
}
|
|
throw new Error(errorMessage || `HTTP ${response.status}`);
|
|
}
|
|
|
|
// Blob für Download
|
|
const blob = await response.blob();
|
|
if (blob.size === 0) {
|
|
throw new Error('PDF ist leer');
|
|
}
|
|
|
|
// Download-Dateiname aus Header extrahieren oder Default verwenden
|
|
const disposition = response.headers.get('Content-Disposition');
|
|
let filename = `auftragsnachweis-${id}.pdf`;
|
|
if (disposition && disposition.includes('filename=')) {
|
|
const match = disposition.match(/filename="([^"]+)"/);
|
|
if (match) filename = match[1];
|
|
}
|
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
|
|
return true;
|
|
},
|
|
|
|
// Vorhandenes PDF herunterladen
|
|
downloadPDF: async (id) => {
|
|
const response = await fetch(`${API_URL}/auftragsnachweise/${id}/pdf`);
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text().catch(() => 'Unknown error');
|
|
let errorMessage = errorText;
|
|
try {
|
|
const errorJson = JSON.parse(errorText);
|
|
errorMessage = errorJson.error || errorText;
|
|
} catch (e) {
|
|
// Nicht JSON, Text verwenden
|
|
}
|
|
throw new Error(errorMessage || `HTTP ${response.status}`);
|
|
}
|
|
|
|
const blob = await response.blob();
|
|
if (blob.size === 0) {
|
|
throw new Error('PDF ist leer');
|
|
}
|
|
|
|
const disposition = response.headers.get('Content-Disposition');
|
|
let filename = `auftragsnachweis-${id}.pdf`;
|
|
if (disposition && disposition.includes('filename=')) {
|
|
const match = disposition.match(/filename="([^"]+)"/);
|
|
if (match) filename = match[1];
|
|
}
|
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
|
|
return true;
|
|
},
|
|
};
|
|
|
|
// Health Check
|
|
export const healthCheck = () => apiCall('/health');
|
|
|
|
export default {
|
|
kredite: krediteAPI,
|
|
stunden: stundenAPI,
|
|
nebenkosten: nebenkostenAPI,
|
|
kostenplanung: kostenplanungAPI,
|
|
geschaeftsplanung: geschaeftsplanungAPI,
|
|
health: healthCheck,
|
|
};
|