Finanzen-API: Projektkonten, Auftragsbestätigungen (samt Projekt zuordnen), Rechnungsvorschau und abgerechnete Arbeitsblätter (Pfade rein und getestet)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-07 14:23:39 +00:00
1 parent eaf9fce1bc
commit e116e804e3
4 files changed
+164

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
// Pfade und Methoden der neuen Portal-Aufrufe für Projektkonten, Auftragsbestätigungen (AB) und die Rechnungsvorschau
// (V5/V8 des Vorhabens „Aufträge und Rechnungen“). Rein (ohne Appwrite-Import): `request` kommt aus createFinanceRequest
// (financeRequest.js) und wird injiziert, damit Pfade, Methoden und Körper unter Node mit einer Fetch-Attrappe testbar sind.
// financeApi.js verdrahtet die Funktionen mit dem Appwrite-JWT. Alle Pfade gelten relativ zu /api/admin/finanzen.
const enc = encodeURIComponent
/**
* Pfad der abgerechneten Arbeitsblätter eines Tickets, relativ zu /api/team (teamApi.js: getAbgerechnet).
* Antwort: { abgerechnet: { <worksheetId>: '<Rechnungsnummer>' | 'Entwurf' } }
*/
export const abgerechnetPfad = (ticketId) => `/arbeitsblaetter/abgerechnet?ticketId=${enc(ticketId)}`
/** createAuftraegeEndpoints(request) -> die 14 Funktionen von V8 (jede Kennung wird URL-kodiert). */
export function createAuftraegeEndpoints(request) {
const post = (path, body) => request(path, { method: 'POST', ...(body === undefined ? {} : { body }) })
return {
// --- Projektkonten ---
/** { auftraegeAktiv, kunde, konten, ohneProjekt, stripeOk } */
getProjektkonten: (kundeId) => request(`/projektkonten?kunde=${enc(kundeId)}`),
/** { konto, auftraege, rechnungen, offeneArbeitsblaetter, stripeOk } */
getProjektkonto: (projektId) => request(`/projektkonten/${enc(projektId)}`),
// --- Rechnungsvorschau (ohne Schreibzugriff, ohne Schlüssel) ---
/** { positionen, summeCents, leistungszeitraum, customFields } */
previewInvoice: (body) => post('/rechnungen/vorschau', body),
// --- Auftragsbestätigungen ---
/** { auftraege } (404, wenn der Schalter AUFTRAEGE_ENABLED für den Kunden aus ist) */
listAuftraege: (kundeId) => request(`/auftraege?kunde=${enc(kundeId)}`),
getAuftrag: (id) => request(`/auftraege/${enc(id)}`),
/** { auftrag } */
createAuftrag: (body) => post('/auftraege', body),
/** { auftrag } */
patchAuftrag: (id, body) => request(`/auftraege/${enc(id)}`, { method: 'PATCH', body }),
/** { ok: true } */
deleteAuftrag: (id) => request(`/auftraege/${enc(id)}`, { method: 'DELETE' }),
/** { auftrag, mailFehler } */
sendAuftrag: (id) => post(`/auftraege/${enc(id)}/senden`),
/** { auftrag, mailFehler } */
erinnereAuftrag: (id) => post(`/auftraege/${enc(id)}/erinnern`),
/** { auftrag } (neuer Entwurf) */
neueFassungAuftrag: (id) => post(`/auftraege/${enc(id)}/neue-fassung`),
/** { auftrag } */
storniereAuftrag: (id) => post(`/auftraege/${enc(id)}/stornieren`),
/** { auftrag }; 409, wenn die AB schon ein Projekt hat oder storniert, ersetzt oder widerrufen ist */
ordneAuftragProjektZu: (id, projektId) => post(`/auftraege/${enc(id)}/projekt`, { projektId }),
/** PDF der AB (gesendet: gespeicherte Datei, Entwurf: Vorschau) als Blob */
getAuftragPdf: (id) => request(`/auftraege/${enc(id)}/pdf`, { as: 'blob' }),
}
}
+21
View File
@@ -1,5 +1,6 @@
import { account } from './appwrite'
import { createFinanceRequest } from './financeRequest'
import { createAuftraegeEndpoints } from './auftraegeEndpoints'
/**
* Client fuer die Finanzen-API des Kundenportals (/api/admin/finanzen).
@@ -26,6 +27,8 @@ export function getInvoices({ status, kunde } = {}) {
}
// Jeder Aufruf bekommt einen neuen Schluessel, damit ein Doppelklick keine zweite Rechnung erzeugt.
// Koerper: { customerId, art, projektId, auftragId, items, arbeitsblattIds, anzahlung, zeilentexte, leistungszeitraum,
// daysUntilDue, memo, senden } (wird unveraendert durchgereicht)
export const createInvoice = (body) =>
request('/rechnungen', { method: 'POST', body: { ...body, idempotencyKey: crypto.randomUUID() } })
export const sendInvoice = (id) => request(`/rechnungen/${enc(id)}/senden`, { method: 'POST' })
@@ -34,6 +37,24 @@ export const resendInvoice = sendInvoice
export const voidInvoice = (id) => request(`/rechnungen/${enc(id)}/stornieren`, { method: 'POST' })
export const deleteInvoice = (id) => request(`/rechnungen/${enc(id)}`, { method: 'DELETE' })
// --- Projektkonten, Auftraege und Rechnungsvorschau (Pfade und Methoden: auftraegeEndpoints.js, getestet) ---
export const {
getProjektkonten,
getProjektkonto,
previewInvoice,
listAuftraege,
getAuftrag,
createAuftrag,
patchAuftrag,
deleteAuftrag,
sendAuftrag,
erinnereAuftrag,
neueFassungAuftrag,
storniereAuftrag,
ordneAuftragProjektZu,
getAuftragPdf,
} = createAuftraegeEndpoints(request)
// --- Buchungen ---
export const getTransactions = () => request('/buchungen')
// target: genau eins von { invoiceId } | { payoutId } | { fixedCostId } | { category }, optional note
+5
View File
@@ -1,6 +1,7 @@
import { account } from './appwrite'
import { createFinanceRequest } from './financeRequest'
import { createJwtCache } from './serverpulsAuth'
import { abgerechnetPfad } from './auftraegeEndpoints'
import { anleitungPfad, featureBody, entscheidungBody } from './funktionenView'
import { ersatzPfad, zeitenTeamPfad } from './zeitenView'
@@ -119,6 +120,10 @@ export const getZeitenErsatz = (customerId, projectId, plan) => request(ersatzPf
/** Team-Einstellungen (Rumpf: teamBody in zeitenView): { team, teamVersion }; 400 ungueltig, 409 veraltet (err.code ZEITEN_VERALTET). PATCH, weil das Ticketsystem kein PUT durchlaesst. */
export const patchZeitenTeam = (customerId, body) => request(zeitenTeamPfad(customerId), { method: 'PATCH', body })
// --- Abgerechnete Arbeitsblaetter (alle Mitarbeiter, ohne Betraege) ---
/** { abgerechnet: { <worksheetId>: '<Rechnungsnummer>' | 'Entwurf' } }; unbekanntes Ticket: { abgerechnet: {} } */
export const getAbgerechnet = (ticketId) => request(abgerechnetPfad(ticketId))
// --- Abo-Status (alle Mitarbeiter) ---
/** { productKey, active, status, storageUnits, startedAt, serviceStart, currentPeriodEnd, cancelAt } */