Phase2: Invoicecomputation/AdminpricingUI/Ainvoicemgnt
Some checks failed
Build and Push / build (push) Failing after 28s
Some checks failed
Build and Push / build (push) Failing after 28s
This commit is contained in:
33
src/app/api/admin/billing/skill-pricing/[skill]/route.ts
Normal file
33
src/app/api/admin/billing/skill-pricing/[skill]/route.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { requirePlatformRole } from "@/lib/session";
|
||||
import { removeSkillPricing } from "@/lib/db";
|
||||
import { safeError } from "@/lib/errors";
|
||||
|
||||
/**
|
||||
* DELETE /api/admin/billing/skill-pricing/[skill]
|
||||
* Remove pricing for a skill. Toggle events continue to be
|
||||
* recorded; the skill simply becomes free starting from the next
|
||||
* generated invoice. Historical invoices already issued are
|
||||
* unaffected (they carry frozen line amounts).
|
||||
*/
|
||||
export async function DELETE(
|
||||
_request: Request,
|
||||
{ params }: { params: Promise<{ skill: string }> }
|
||||
) {
|
||||
try {
|
||||
await requirePlatformRole();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
const { skill } = await params;
|
||||
try {
|
||||
await removeSkillPricing(skill);
|
||||
return NextResponse.json({ message: "Removed." });
|
||||
} catch (e) {
|
||||
console.error("Failed to remove skill pricing:", e);
|
||||
return NextResponse.json(
|
||||
{ error: safeError(e, "Remove failed") },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
76
src/app/api/admin/billing/skill-pricing/route.ts
Normal file
76
src/app/api/admin/billing/skill-pricing/route.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { requirePlatformRole } from "@/lib/session";
|
||||
import { listSkillPricing, setSkillPricing } from "@/lib/db";
|
||||
import { getPackageDef } from "@/lib/packages";
|
||||
import { safeError } from "@/lib/errors";
|
||||
|
||||
/**
|
||||
* GET /api/admin/billing/skill-pricing
|
||||
* List all configured skill prices.
|
||||
*
|
||||
* PUT /api/admin/billing/skill-pricing
|
||||
* Upsert a daily price for a single skill. Body:
|
||||
* { skillId: string, dailyPriceChf: number }
|
||||
*
|
||||
* Both endpoints are platform-only.
|
||||
*
|
||||
* Note on skillId validation: we accept any package id that exists
|
||||
* in PACKAGE_CATALOG. The PIN to "skills only" is enforced at the
|
||||
* UI layer, not here, so admins can price a non-skill package in
|
||||
* an emergency without code changes.
|
||||
*/
|
||||
|
||||
const upsertSchema = z.object({
|
||||
skillId: z.string().min(1).max(100),
|
||||
dailyPriceChf: z.number().min(0).max(1_000_000),
|
||||
});
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
await requirePlatformRole();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
const rows = await listSkillPricing();
|
||||
return NextResponse.json(rows);
|
||||
}
|
||||
|
||||
export async function PUT(request: Request) {
|
||||
try {
|
||||
await requirePlatformRole();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const parsed = upsertSchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid payload", details: parsed.error.flatten() },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
// Validate the skill id exists in PACKAGE_CATALOG. Returns null
|
||||
// for unknown ids; we reject those rather than persist a row that
|
||||
// would never match a real toggle event.
|
||||
const pkg = getPackageDef(parsed.data.skillId);
|
||||
if (!pkg) {
|
||||
return NextResponse.json(
|
||||
{ error: `Unknown package id: ${parsed.data.skillId}` },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
try {
|
||||
const row = await setSkillPricing(
|
||||
parsed.data.skillId,
|
||||
parsed.data.dailyPriceChf
|
||||
);
|
||||
return NextResponse.json(row);
|
||||
} catch (e) {
|
||||
console.error("Failed to upsert skill pricing:", e);
|
||||
return NextResponse.json(
|
||||
{ error: safeError(e, "Upsert failed") },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user