L14 Abrechnungsübersicht: Backoffice-Übersicht, Detail, Druckansicht, Meilensteine im Auftrag und mobil, API
/billing mit Tabs Offen/Abgerechnet/Storniert, Filtern und Mehrfachdruck; /billing/[id] mit Abrechnungsblatt und Aktionen (abgerechnet mit Rechnungsnummer, PDF, Rechnungsnummer ändern, stornieren); /billing/print; Auftrags-Tab „Meilensteine & Abrechnung“; mobiler Abschnitt „Erreicht melden“ (offline); Dashboard-Kachel „Bereit zur Abrechnung“; /api/v1/billing*, Meilenstein-Endpunkte und OpenAPI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import { requireApiContext } from "@/server/api/context";
|
||||
import { json, readJsonObject, withApi } from "@/server/api/respond";
|
||||
import { markBilled } from "@/server/services/billing/records";
|
||||
|
||||
/**
|
||||
* POST /api/v1/billing/:id/billed — mark as billed (freeze statement, assign positions, PDF job).
|
||||
* Body (optional): { invoiceNumber?: string, confirmPendingExcluded?: boolean }.
|
||||
*/
|
||||
export const POST = withApi(async (req: Request, { params }: { params: Promise<{ id: string }> }) => {
|
||||
const ctx = await requireApiContext("billing", "billing:write");
|
||||
const { id } = await params;
|
||||
const body = await readJsonObject(req, { allowEmpty: true });
|
||||
const record = await markBilled(ctx, {
|
||||
recordId: id,
|
||||
invoiceNumber: typeof body.invoiceNumber === "string" ? body.invoiceNumber : null,
|
||||
confirmPendingExcluded: body.confirmPendingExcluded === true,
|
||||
});
|
||||
return json({ id: record.id, status: record.status, invoiceNumber: record.invoiceNumber, billedAt: record.billedAt });
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { requireApiContext } from "@/server/api/context";
|
||||
import { json, withApi } from "@/server/api/respond";
|
||||
import { getBillingRecordDetail } from "@/server/services/billing/queries";
|
||||
|
||||
/** GET /api/v1/billing/:id — billing record with its statement (frozen snapshot once billed). */
|
||||
export const GET = withApi(async (_req: Request, { params }: { params: Promise<{ id: string }> }) => {
|
||||
const ctx = await requireApiContext("billing", "billing:read");
|
||||
const { id } = await params;
|
||||
return json(await getBillingRecordDetail(ctx, id));
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { requireApiContext } from "@/server/api/context";
|
||||
import { json, readJsonObject, withApi } from "@/server/api/respond";
|
||||
import { voidBilling } from "@/server/services/billing/records";
|
||||
|
||||
/** POST /api/v1/billing/:id/void — void a billed record. Body: { reason: string } (required). */
|
||||
export const POST = withApi(async (req: Request, { params }: { params: Promise<{ id: string }> }) => {
|
||||
const ctx = await requireApiContext("billing", "billing:write");
|
||||
const { id } = await params;
|
||||
const body = await readJsonObject(req);
|
||||
const res = await voidBilling(ctx, { recordId: id, reason: typeof body.reason === "string" ? body.reason : "" });
|
||||
return json({ id: res.voided.id, status: res.voided.status, reopenedId: res.reopened?.id ?? null });
|
||||
});
|
||||
Reference in New Issue
Block a user