db395977-b1fb-4bf5-89ba-32fa96701360

This commit is contained in:
Z User committed 2026-10-29 23:22:03 +00:00
1 parent e2c17aea18
commit cd7591fd65
6 files changed
+264 -166

No files matched your search

+14 -78
View File
@@ -1,84 +1,20 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
export const dynamic = 'force-dynamic'
// DELETE /api/aos/licenses/[id] — hard-delete a license.
// DELETE /api/aos/licenses/[id] — DISABLED (GoBD).
//
// Cascade policy: a license may only be hard-deleted when:
// - it has NO activations, AND
// - it has NO linked instance (License 1:1 Instance), AND
// - it has NO child module licenses (parentId === this id), AND
// - it is in status 'cancelled' (cancelled licenses that have reached their
// effective date are safe to remove from active bookkeeping).
//
// Otherwise the call returns 409 with the dependency counts.
//
// GoBD: if the license has been issued, activated, or used to bill a
// customer, do NOT delete — cancel (cancel/route.ts) keeps the audit record.
// This endpoint exists for genuine cleanup (e.g. demo licenses that never
// started a subscription, or test fixtures).
export async function DELETE(
_req: Request,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const existing = await db.license.findUnique({ where: { id } })
if (!existing) {
return NextResponse.json({ error: 'license not found' }, { status: 404 })
}
const [activations, instances, modules] = await Promise.all([
db.licenseActivation.count({ where: { licenseId: id } }),
db.instance.count({ where: { licenseId: id } }),
db.license.count({ where: { parentId: id } }),
])
const dependencies: Record<string, number> = { activations, instances, modules }
const totalDeps = Object.values(dependencies).reduce((s, n) => s + n, 0)
if (totalDeps > 0) {
return NextResponse.json(
{
error: 'license has related records — cancel the license instead of deleting it',
dependencies,
total: totalDeps,
},
{ status: 409 }
)
}
// Only allow deletion when the license is already cancelled or never started.
// Active/trial/suspended licenses must go through the cancel flow.
if (
existing.status !== 'cancelled' &&
existing.status !== 'revoked' &&
existing.status !== 'expired'
) {
return NextResponse.json(
{ error: `license is ${existing.status} — cancel before deleting (GoBD retention)` },
{ status: 409 }
)
}
await db.license.delete({ where: { id } })
await db.auditLog.create({
data: {
actor: 'admin:ui',
action: 'license:delete',
entity: 'License',
entityId: id,
before: JSON.stringify({ key: existing.key, status: existing.status, product: existing.product }),
after: JSON.stringify({ deleted: true }),
hash: 'sha256:' + Math.random().toString(36).slice(2),
},
})
return NextResponse.json({ ok: true, deleted: true, id })
} catch (err) {
const msg = err instanceof Error ? err.message : 'internal server error'
return NextResponse.json({ error: msg }, { status: 500 })
}
// Licenses carry billing/subscription data and are accounting records.
// Per GoBD they may be CANCELLED but never deleted. Use the dedicated
// cancel flow at PATCH /api/aos/licenses/[id]/cancel instead — it sets
// status='cancelled', records cancellationEffectiveAt, and writes an
// AuditLog entry. The license row is retained indefinitely.
export async function DELETE() {
return NextResponse.json(
{
error: 'Lizenzen können nicht gelöscht werden (GoBD). Bitte über „Kündigen“ stornieren.',
useInstead: 'PATCH /api/aos/licenses/[id]/cancel',
},
{ status: 405 }
)
}