a6c0f3de-eeb0-4d76-a57f-f600dd133c06

This commit is contained in:
Z User committed 2026-10-28 15:39:30 +00:00
1 parent 2aa3496a90
commit 762f3d7373
141 files changed
+4485 -69

No files matched your search

@@ -0,0 +1,90 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { z } from 'zod'
export const dynamic = 'force-dynamic'
const schema = z.object({
discountPct: z.number().int().min(0).max(20),
reviewedBy: z.string().optional(),
})
// PATCH /api/aos/agency/[id]/approve
// Admin approves an agency application. Sets Company.agencyStatus = "approved",
// Company.agencyDiscountPct = discountPct, and marks the application approved.
// In production an email would be sent to the applicant with their discount.
export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
try {
const { id } = await params
const parsed = schema.safeParse(await req.json())
if (!parsed.success) {
return NextResponse.json({ error: 'discountPct (0-20) required', details: parsed.error.flatten() }, { status: 400 })
}
const { discountPct, reviewedBy } = parsed.data
const application = await db.agencyApplication.findUnique({
where: { id },
include: { company: true },
})
if (!application) {
return NextResponse.json({ error: 'application not found' }, { status: 404 })
}
if (application.status === 'approved') {
return NextResponse.json({ error: 'already approved' }, { status: 409 })
}
const now = new Date()
await db.$transaction([
db.agencyApplication.update({
where: { id },
data: {
status: 'approved',
discountPct,
reviewedBy: reviewedBy ?? 'admin:ui',
reviewedAt: now,
},
}),
db.company.update({
where: { id: application.companyId },
data: {
isAgency: true,
agencyStatus: 'approved',
agencyApprovedAt: now,
agencyDiscountPct: discountPct,
agencyRejectedAt: null,
agencyRejectReason: null,
},
}),
])
await db.auditLog.create({
data: {
actor: reviewedBy ?? 'admin:ui',
action: 'agency.approve',
entity: 'AgencyApplication',
entityId: id,
after: JSON.stringify({
company: application.company.name,
discountPct,
approvedAt: now,
}),
hash: 'sha256:' + Math.random().toString(36).slice(2),
},
})
// TODO (production): send email to company.email with subject
// "Ihre Bayi-Bewerbung wurde genehmigt — {discountPct}% Rabatt"
// containing the discount and a link to the agency portal.
return NextResponse.json({
ok: true,
status: 'approved',
discountPct,
email: application.company.email,
message: `Genehmigt mit ${discountPct}% Rabatt. E-Mail an ${application.company.email ?? '(keine E-Mail hinterlegt)'} würde versendet.`,
})
} catch (err) {
const msg = err instanceof Error ? err.message : 'internal server error'
return NextResponse.json({ error: msg }, { status: 500 })
}
}
@@ -0,0 +1,68 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { z } from 'zod'
export const dynamic = 'force-dynamic'
const schema = z.object({
reason: z.string().min(1).max(500),
reviewedBy: z.string().optional(),
})
// PATCH /api/aos/agency/[id]/reject
// Admin rejects an agency application. Sets Company.agencyStatus = "rejected".
export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
try {
const { id } = await params
const parsed = schema.safeParse(await req.json())
if (!parsed.success) {
return NextResponse.json({ error: 'reason required', details: parsed.error.flatten() }, { status: 400 })
}
const { reason, reviewedBy } = parsed.data
const application = await db.agencyApplication.findUnique({
where: { id },
include: { company: true },
})
if (!application) {
return NextResponse.json({ error: 'application not found' }, { status: 404 })
}
const now = new Date()
await db.$transaction([
db.agencyApplication.update({
where: { id },
data: {
status: 'rejected',
rejectReason: reason,
reviewedBy: reviewedBy ?? 'admin:ui',
reviewedAt: now,
},
}),
db.company.update({
where: { id: application.companyId },
data: {
agencyStatus: 'rejected',
agencyRejectedAt: now,
agencyRejectReason: reason,
},
}),
])
await db.auditLog.create({
data: {
actor: reviewedBy ?? 'admin:ui',
action: 'agency.reject',
entity: 'AgencyApplication',
entityId: id,
after: JSON.stringify({ company: application.company.name, reason }),
hash: 'sha256:' + Math.random().toString(36).slice(2),
},
})
return NextResponse.json({ ok: true, status: 'rejected', reason })
} catch (err) {
const msg = err instanceof Error ? err.message : 'internal server error'
return NextResponse.json({ error: msg }, { status: 500 })
}
}
+80
View File
@@ -0,0 +1,80 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { z } from 'zod'
export const dynamic = 'force-dynamic'
const schema = z.object({
companyId: z.string().min(1),
businessModel: z.string().max(200).optional(),
expectedClientVolume: z.string().max(100).optional(),
referredBy: z.string().max(200).optional(),
})
// POST /api/aos/agency/apply
// A company applies to become an agency (reseller). Sets Company.agencyStatus
// to "pending" and creates an AgencyApplication record. Admin must approve.
export async function POST(req: Request) {
try {
const parsed = schema.safeParse(await req.json())
if (!parsed.success) {
return NextResponse.json({ error: 'validation failed', details: parsed.error.flatten() }, { status: 400 })
}
const { companyId, businessModel, expectedClientVolume, referredBy } = parsed.data
const company = await db.company.findUnique({ where: { id: companyId } })
if (!company) {
return NextResponse.json({ error: 'company not found' }, { status: 404 })
}
if (company.isAgency && company.agencyStatus === 'approved') {
return NextResponse.json({ error: 'already an approved agency' }, { status: 409 })
}
// Upsert application (one per company — @unique on companyId)
const application = await db.agencyApplication.upsert({
where: { companyId },
create: {
companyId,
status: 'pending',
businessModel,
expectedClientVolume,
referredBy,
},
update: {
status: 'pending',
businessModel,
expectedClientVolume,
referredBy,
rejectReason: null,
reviewedBy: null,
reviewedAt: null,
},
})
// Mark company as pending-agency
await db.company.update({
where: { id: companyId },
data: {
isAgency: true,
agencyStatus: 'pending',
agencyAppliedAt: new Date(),
},
})
await db.auditLog.create({
data: {
actor: `company:${companyId}`,
action: 'agency.apply',
entity: 'AgencyApplication',
entityId: application.id,
after: JSON.stringify({ company: company.name, businessModel, expectedClientVolume }),
hash: 'sha256:' + Math.random().toString(36).slice(2),
},
})
return NextResponse.json({ ok: true, applicationId: application.id, status: 'pending' }, { status: 201 })
} catch (err) {
const msg = err instanceof Error ? err.message : 'internal server error'
return NextResponse.json({ error: msg }, { status: 500 })
}
}
+42
View File
@@ -0,0 +1,42 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
export const dynamic = 'force-dynamic'
// GET /api/aos/agency
// Returns all agency applications + approved agencies. Admin-only in practice
// (no scope check here because AOS is single-tenant dev mode).
export async function GET() {
try {
const [applications, agencies] = await Promise.all([
db.agencyApplication.findMany({
orderBy: { appliedAt: 'desc' },
include: { company: true },
}),
db.company.findMany({
where: { isAgency: true, agencyStatus: 'approved' },
orderBy: { agencyApprovedAt: 'desc' },
include: {
agencyClients: { select: { id: true, name: true, email: true } },
_count: { select: { agencyClients: true, licenses: true } },
},
}),
])
const pending = applications.filter((a) => a.status === 'pending')
const reviewed = applications.filter((a) => a.status !== 'pending')
return NextResponse.json({
applications: { pending, reviewed },
agencies,
stats: {
pending: pending.length,
approved: agencies.length,
totalApplications: applications.length,
},
})
} catch (err) {
const msg = err instanceof Error ? err.message : 'internal server error'
return NextResponse.json({ error: msg }, { status: 500 })
}
}