feat(05-03): extend getProjectFullDetail + add offer assignment server actions
- Add offer_micros, project_offers imports to admin-queries.ts - Add ProjectOfferWithMicro type to admin-queries.ts - Extend ProjectFullDetail type with projectOffers + availableMicros fields - Extend getProjectFullDetail() to query project offers and available micros in parallel - Add z import and project_offers import to project-actions.ts - Add assignOfferToProject, removeProjectOffer, updateProjectOfferTotal server actions - All three actions call requireAdmin() and revalidate /admin/forecast
This commit is contained in:
@@ -4,8 +4,9 @@ import { revalidatePath } from "next/cache";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { db } from "@/db";
|
||||
import { projects, clients, payments } from "@/db/schema";
|
||||
import { projects, clients, payments, project_offers } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
async function requireAdmin() {
|
||||
@@ -107,4 +108,55 @@ export async function splitPayment(
|
||||
});
|
||||
|
||||
revalidatePath(`/admin/projects/${projectId}`);
|
||||
}
|
||||
|
||||
// ── Offer assignment actions ──────────────────────────────────────────────────
|
||||
|
||||
const assignOfferSchema = z.object({
|
||||
project_id: z.string().min(1),
|
||||
micro_id: z.string().min(1, "Seleziona una micro-offerta"),
|
||||
accepted_total: z.coerce.number().min(0).optional(),
|
||||
});
|
||||
|
||||
export async function assignOfferToProject(formData: FormData) {
|
||||
await requireAdmin();
|
||||
const parsed = assignOfferSchema.safeParse({
|
||||
project_id: formData.get("project_id"),
|
||||
micro_id: formData.get("micro_id"),
|
||||
accepted_total: formData.get("accepted_total") || undefined,
|
||||
});
|
||||
if (!parsed.success) throw new Error(parsed.error.issues[0].message);
|
||||
await db.insert(project_offers).values({
|
||||
project_id: parsed.data.project_id,
|
||||
micro_id: parsed.data.micro_id,
|
||||
accepted_total:
|
||||
parsed.data.accepted_total !== undefined
|
||||
? parsed.data.accepted_total.toFixed(2)
|
||||
: null,
|
||||
});
|
||||
revalidatePath(`/admin/projects/${parsed.data.project_id}`);
|
||||
revalidatePath("/admin/forecast");
|
||||
}
|
||||
|
||||
export async function removeProjectOffer(projectOfferId: string, projectId: string) {
|
||||
await requireAdmin();
|
||||
await db.delete(project_offers).where(eq(project_offers.id, projectOfferId));
|
||||
revalidatePath(`/admin/projects/${projectId}`);
|
||||
revalidatePath("/admin/forecast");
|
||||
}
|
||||
|
||||
export async function updateProjectOfferTotal(
|
||||
projectOfferId: string,
|
||||
projectId: string,
|
||||
accepted_total: string
|
||||
) {
|
||||
await requireAdmin();
|
||||
const amount = parseFloat(accepted_total);
|
||||
if (isNaN(amount) || amount < 0) throw new Error("Importo non valido");
|
||||
await db
|
||||
.update(project_offers)
|
||||
.set({ accepted_total: amount.toFixed(2) })
|
||||
.where(eq(project_offers.id, projectOfferId));
|
||||
revalidatePath(`/admin/projects/${projectId}`);
|
||||
revalidatePath("/admin/forecast");
|
||||
}
|
||||
Reference in New Issue
Block a user