diff --git a/src/app/api/rental-media/[id]/route.ts b/src/app/api/rental-media/[id]/route.ts index 3c6bd93..8420d8c 100644 --- a/src/app/api/rental-media/[id]/route.ts +++ b/src/app/api/rental-media/[id]/route.ts @@ -23,6 +23,7 @@ export async function DELETE(_req: Request, ctx: { params: Promise<{ id: string session.user.id, session.user.role, media.item.providerId, + session.user.organizationId, ); if (!allowed) return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); diff --git a/src/app/api/rental-media/reorder/route.ts b/src/app/api/rental-media/reorder/route.ts index a8cc26a..d375aa0 100644 --- a/src/app/api/rental-media/reorder/route.ts +++ b/src/app/api/rental-media/reorder/route.ts @@ -34,6 +34,7 @@ export async function POST(req: Request) { session.user.id, session.user.role, item.providerId, + session.user.organizationId, ); if (!allowed) return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); diff --git a/src/app/api/uploads/rental-finalize/route.ts b/src/app/api/uploads/rental-finalize/route.ts index 4f1f9b9..befc16f 100644 --- a/src/app/api/uploads/rental-finalize/route.ts +++ b/src/app/api/uploads/rental-finalize/route.ts @@ -43,6 +43,7 @@ export async function POST(req: Request) { session.user.id, session.user.role, item.providerId, + session.user.organizationId, ); if (!allowed) { return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); diff --git a/src/app/api/uploads/rental-presign/route.ts b/src/app/api/uploads/rental-presign/route.ts index f3b26e2..73244e0 100644 --- a/src/app/api/uploads/rental-presign/route.ts +++ b/src/app/api/uploads/rental-presign/route.ts @@ -45,6 +45,7 @@ export async function POST(req: Request) { session.user.id, session.user.role, item.providerId, + session.user.organizationId, ); if (!allowed) { return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); diff --git a/src/app/espace-ce/materiel/actions.ts b/src/app/espace-ce/materiel/actions.ts new file mode 100644 index 0000000..959d9f3 --- /dev/null +++ b/src/app/espace-ce/materiel/actions.ts @@ -0,0 +1,66 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; + +import { auth } from "@/auth"; +import { UserRole } from "@/generated/prisma/enums"; +import { recordAudit } from "@/lib/admin/audit"; +import { getCurrentCeOrganization } from "@/lib/ce-access"; +import { prisma } from "@/lib/prisma"; + +/** + * Active la location matériel pour un CE : crée le RentalProvider lié à son + * organizationId. Approuvé automatiquement si l'org elle-même est approuvée. + * - Si un provider existe déjà pour cette org : redirige sans rien créer. + * - Bloque si l'org n'est pas validée (la création doit attendre l'approval). + */ +export async function activateRentalProviderForCeAction(): Promise { + const session = await auth(); + if (!session?.user?.id) redirect("/connexion?next=/espace-ce/materiel"); + if (session.user.role !== UserRole.CE_MANAGER && session.user.role !== UserRole.ADMIN) { + redirect("/"); + } + const org = await getCurrentCeOrganization(); + if (!org) redirect("/espace-ce"); + if (!org.approved) { + // L'org doit être validée avant activation. La page affichera la bannière. + redirect("/espace-ce/materiel?activateError=pending"); + } + + const existing = await prisma.rentalProvider.findFirst({ + where: { organizationId: org.id }, + select: { id: true }, + }); + if (existing) { + redirect("/espace-ce/materiel"); + } + + const created = await prisma.rentalProvider.create({ + data: { + name: `Matériel — ${org.name}`, + isSystemD: false, + managedByUserId: session.user.id, + organizationId: org.id, + contactEmail: org.contactEmail, + rivers: [], + commissionPct: 10, + active: true, + approved: true, + approvedAt: new Date(), + approvedBy: session.user.email ?? "system", + }, + select: { id: true, name: true }, + }); + + await recordAudit({ + scope: "ce", + event: "ce.rental_provider.activate", + target: created.id, + actorEmail: session.user.email ?? null, + details: { organizationId: org.id, name: created.name }, + }); + + revalidatePath("/espace-ce/materiel"); + redirect("/espace-ce/materiel"); +} diff --git a/src/app/espace-ce/materiel/items/[itemId]/page.tsx b/src/app/espace-ce/materiel/items/[itemId]/page.tsx new file mode 100644 index 0000000..2d406a9 --- /dev/null +++ b/src/app/espace-ce/materiel/items/[itemId]/page.tsx @@ -0,0 +1,122 @@ +import Link from "next/link"; +import { notFound, redirect } from "next/navigation"; + +import { MediaUploader } from "@/components/MediaUploader"; +import { + getCurrentRentalProvider, + requireRentalProviderSession, +} from "@/lib/rental-access"; +import { RENTAL_CATEGORY_LABEL } from "@/lib/rental-category-labels"; +import { getHostItem } from "@/lib/rental-host"; + +import { + addItemBlockAction, + deleteHostItemAction, + removeItemBlockAction, + updateHostItemAction, +} from "../../../../espace-prestataire/actions"; +import { HostItemForm } from "../../../../espace-prestataire/items/_components/ItemForm"; +import { ItemBlocksManager } from "../../../../espace-prestataire/items/[itemId]/_components/ItemBlocksManager"; +import { ItemInlineDelete } from "../../../../espace-prestataire/items/[itemId]/_components/ItemInlineDelete"; + +export const dynamic = "force-dynamic"; + +type PageProps = { params: Promise<{ itemId: string }> }; + +export default async function EditCeItemPage({ params }: PageProps) { + await requireRentalProviderSession(); + const provider = await getCurrentRentalProvider(); + if (!provider) redirect("/espace-ce/materiel"); + const { itemId } = await params; + const item = await getHostItem(provider.id, itemId); + if (!item) notFound(); + + const updateThis = async (fd: FormData) => { + "use server"; + return await updateHostItemAction(itemId, fd); + }; + const deleteThis = async () => { + "use server"; + return await deleteHostItemAction(itemId); + }; + const addBlockThis = async (fd: FormData) => { + "use server"; + return await addItemBlockAction(itemId, fd); + }; + const removeBlockThis = async (blockId: string) => { + "use server"; + return await removeItemBlockAction(blockId); + }; + + return ( +
+
+
+ + ← Mes items + +

{item.name}

+

+ {RENTAL_CATEGORY_LABEL[item.category]} · Stock : {item.totalQty} · {item._count.lines}{" "} + location(s) historique +

+
+ +
+ +
+

+ Photos & vidéos +

+ +
+ +
+ +
+ +
+

+ Calendrier de disponibilité +

+

+ Bloquez ici des dates pour maintenance, indisponibilité personnelle, etc. Les réservations + confirmées sont gérées automatiquement. +

+ ({ + id: a.id, + startDate: a.startDate.toISOString().slice(0, 10), + endDate: a.endDate.toISOString().slice(0, 10), + qty: a.qty, + reason: a.reason, + isBooking: Boolean(a.rentalBookingId), + }))} + addAction={addBlockThis} + removeAction={removeBlockThis} + totalQty={item.totalQty} + /> +
+
+ ); +} diff --git a/src/app/espace-ce/materiel/items/new/page.tsx b/src/app/espace-ce/materiel/items/new/page.tsx new file mode 100644 index 0000000..8a6d468 --- /dev/null +++ b/src/app/espace-ce/materiel/items/new/page.tsx @@ -0,0 +1,27 @@ +import Link from "next/link"; + +import { requireRentalProviderSession } from "@/lib/rental-access"; + +import { createHostItemAction } from "../../../../espace-prestataire/actions"; +import { HostItemForm } from "../../../../espace-prestataire/items/_components/ItemForm"; + +export const dynamic = "force-dynamic"; + +export default async function NewCeItemPage() { + await requireRentalProviderSession(); + return ( +
+ + ← Mes items + +

Nouvel item

+
+ +
+
+ ); +} diff --git a/src/app/espace-ce/materiel/items/page.tsx b/src/app/espace-ce/materiel/items/page.tsx new file mode 100644 index 0000000..68be691 --- /dev/null +++ b/src/app/espace-ce/materiel/items/page.tsx @@ -0,0 +1,109 @@ +import Link from "next/link"; +import { redirect } from "next/navigation"; + +import { RENTAL_CATEGORY_LABEL } from "@/lib/rental-category-labels"; +import { + getCurrentRentalProvider, + requireRentalProviderSession, +} from "@/lib/rental-access"; +import { listHostItems } from "@/lib/rental-host"; + +export const dynamic = "force-dynamic"; +export const metadata = { title: "Items rental CE — Karbé" }; + +export default async function CeMaterielItemsPage() { + await requireRentalProviderSession(); + const provider = await getCurrentRentalProvider(); + // Sans provider activé → renvoie sur l'onboarding /espace-ce/materiel + if (!provider) redirect("/espace-ce/materiel"); + + const items = await listHostItems(provider.id); + + return ( +
+
+
+ + ← Dashboard matériel CE + +

+ Items locables — {provider.name} +

+

+ {items.length} item{items.length > 1 ? "s" : ""} +

+
+ + + Nouvel item + +
+ + {items.length === 0 ? ( +
+ Pas encore d'item.{" "} + + Créer mon premier item + +
+ ) : ( +
+ + + + + + + + + + + + + + {items.map((i) => ( + + + + + + + + + + ))} + +
NomCatégorie€/jStockCautionRésaÉtat
+ + {i.name} + +
+ {i.withMotor ? "⚙️ moteur · " : ""} + {i.requiresLicense ? "🪪 permis · " : ""} + {i.fuelIncluded ? "⛽ essence " : ""} +
+
{RENTAL_CATEGORY_LABEL[i.category]} + {Number(i.pricePerDay).toFixed(0)} + {i.totalQty} + {Number(i.deposit).toFixed(0)} + {i._count.lines} + {i.active ? ( + + Actif + + ) : ( + + Inactif + + )} +
+
+ )} +
+ ); +} diff --git a/src/app/espace-ce/materiel/page.tsx b/src/app/espace-ce/materiel/page.tsx new file mode 100644 index 0000000..2c13ead --- /dev/null +++ b/src/app/espace-ce/materiel/page.tsx @@ -0,0 +1,152 @@ +import Link from "next/link"; +import { redirect } from "next/navigation"; + +import { getCurrentCeOrganization } from "@/lib/ce-access"; +import { isPluginEnabled } from "@/lib/plugins/server"; +import { + getCurrentRentalProviderForCe, +} from "@/lib/rental-access"; +import { getHostRentalKpis } from "@/lib/rental-host"; + +import { activateRentalProviderForCeAction } from "./actions"; + +export const dynamic = "force-dynamic"; +export const metadata = { title: "Matériel CE — Karbé" }; + +function fmtEur(amount: string | number): string { + return Number(amount).toLocaleString("fr-FR", { style: "currency", currency: "EUR" }); +} + +export default async function CeMaterielPage() { + // Soft dependency : si le plugin gear-rental est off, on masque /espace-ce/materiel + // (le bouton du dashboard a déjà été désactivé côté UX). + if (!(await isPluginEnabled("gear-rental"))) { + return ( +
+

Matériel rental

+

+ La marketplace location matériel n'est pas activée. Activez le plugin + gear-rental{" "} + dans /admin/plugins. +

+
+ ); + } + + const org = await getCurrentCeOrganization(); + if (!org) redirect("/admin/organizations"); + + const provider = await getCurrentRentalProviderForCe(org.id); + + // Onboarding : pas encore de provider activé + if (!provider) { + return ( +
+ + ← Tableau de bord CE + +

Matériel rental

+

+ Activez la location matériel pour proposer hamacs, kayaks, pirogues, etc. à vos + membres et au public touriste. Le provider sera créé au nom de votre CE. +

+ + {!org.approved ? ( +

+ 🕒 Votre organisation est en attente de validation. La location matériel sera + activable dès qu'un admin Karbé aura validé votre CE. +

+ ) : ( +
+ +

+ Vous pourrez ensuite ajouter vos items (hamac, pirogue, kayak…). Commission + par défaut : 10 % (ajustable par un admin Karbé). +

+
+ )} +
+ ); + } + + // Provider existant : dashboard + KPIs + const kpis = await getHostRentalKpis(provider.id); + + return ( +
+
+ + ← Tableau de bord CE + +

+ Matériel rental — {provider.name} +

+

+ Commission Karbé : {Number(provider.commissionPct).toFixed(1)} % · Géré par {org.name} +

+
+ +
+ + + + +
+ +
+ 0 + ? `${kpis.itemsActive} item${kpis.itemsActive > 1 ? "s" : ""} en location.` + : "Ajoutez votre premier item (hamac, kayak, pirogue…)." + } + /> + 0 + ? `${kpis.bookingsPending} demande${kpis.bookingsPending > 1 ? "s" : ""} à préparer.` + : "Suivez vos réservations en cours, à préparer et terminées." + } + /> +
+
+ ); +} + +function KpiCard({ label, value }: { label: string; value: string | number }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + +function ActionCard({ + href, + title, + description, +}: { + href: string; + title: string; + description: string; +}) { + return ( + +

{title}

+

{description}

+ + ); +} diff --git a/src/app/espace-ce/materiel/reservations/page.tsx b/src/app/espace-ce/materiel/reservations/page.tsx new file mode 100644 index 0000000..6a1bfdc --- /dev/null +++ b/src/app/espace-ce/materiel/reservations/page.tsx @@ -0,0 +1,150 @@ +import Link from "next/link"; +import { redirect } from "next/navigation"; + +import { RentalBookingStatus } from "@/generated/prisma/enums"; +import { RENTAL_STATUS_LABEL } from "@/lib/admin/rental-bookings"; +import { + getCurrentRentalProvider, + requireRentalProviderSession, +} from "@/lib/rental-access"; +import { listHostBookings } from "@/lib/rental-host"; + +import { BookingDecision } from "../../../espace-prestataire/reservations/_components/BookingDecision"; + +export const dynamic = "force-dynamic"; +export const metadata = { title: "Réservations matériel CE — Karbé" }; + +const STATUS_VALUES = new Set([ + RentalBookingStatus.PENDING, + RentalBookingStatus.CONFIRMED, + RentalBookingStatus.HANDED_OVER, + RentalBookingStatus.RETURNED, + RentalBookingStatus.CANCELLED, +]); + +type PageProps = { + searchParams: Promise<{ status?: string }>; +}; + +const dateFmt = new Intl.DateTimeFormat("fr-FR", { + day: "2-digit", + month: "short", + year: "2-digit", +}); + +export default async function CeReservationsPage({ searchParams }: PageProps) { + await requireRentalProviderSession(); + const provider = await getCurrentRentalProvider(); + if (!provider) redirect("/espace-ce/materiel"); + const sp = await searchParams; + const status = STATUS_VALUES.has(sp.status ?? "") + ? (sp.status as RentalBookingStatus) + : undefined; + + const bookings = await listHostBookings(provider.id, { status }); + + return ( +
+
+
+ + ← Dashboard matériel CE + +

Réservations

+

+ {bookings.length} résultat{bookings.length > 1 ? "s" : ""} +

+
+
+ + +
+
+ + {bookings.length === 0 ? ( +
+ Aucune réservation matériel. +
+ ) : ( +
    + {bookings.map((b) => ( +
  • +
    +
    +

    + {b.tenant.firstName} {b.tenant.lastName} +

    +

    + {b.tenant.email} + {b.tenant.phone ? ` · ${b.tenant.phone}` : ""} +

    + {b.booking ? ( +

    + 🏠 Lié à la résa carbet :{" "} + + {b.booking.carbet.title} + +

    + ) : ( +

    + Location standalone (sans carbet) +

    + )} +
    +
    +
    + {dateFmt.format(b.startDate)} → {dateFmt.format(b.endDate)} +
    +
    + {Number(b.amount).toFixed(2)} {b.currency} +
    +
    +
    + +
      + {b.lines.map((l) => ( +
    • + + {l.qty}× {l.item.name} + + + {Number(l.lineTotal).toFixed(2)} € + +
    • + ))} +
    + +
    +
    + + {RENTAL_STATUS_LABEL[b.status]} + + + {b.paymentStatus} + +
    + +
    +
  • + ))} +
+ )} +
+ ); +} diff --git a/src/app/espace-prestataire/actions.ts b/src/app/espace-prestataire/actions.ts index 2474e91..0d790a5 100644 --- a/src/app/espace-prestataire/actions.ts +++ b/src/app/espace-prestataire/actions.ts @@ -31,12 +31,25 @@ const itemSchema = z.object({ active: z.boolean(), }); -async function requireOwnedProvider(): Promise<{ providerId: string; actorEmail: string | null }> { +async function requireOwnedProvider(): Promise<{ + providerId: string; + actorEmail: string | null; + basePath: string; +}> { const session = await auth(); if (!session?.user?.id) throw new Error("Non authentifié"); const provider = await getCurrentRentalProvider(); if (!provider) throw new Error("Aucun provider associé"); - return { providerId: provider.id, actorEmail: session.user.email ?? null }; + // Un CE_MANAGER reste sous /espace-ce/materiel ; un RENTAL_PROVIDER/ADMIN + // reste sous /espace-prestataire. Les actions sont mutualisées et redirigent + // vers l'espace contextuel du user. + const basePath = + session.user.role === UserRole.CE_MANAGER ? "/espace-ce/materiel" : "/espace-prestataire"; + return { + providerId: provider.id, + actorEmail: session.user.email ?? null, + basePath, + }; } function parseItemFD(fd: FormData) { @@ -61,7 +74,7 @@ function parseItemFD(fd: FormData) { } export async function createHostItemAction(fd: FormData) { - const { providerId, actorEmail } = await requireOwnedProvider(); + const { providerId, actorEmail, basePath } = await requireOwnedProvider(); const parsed = itemSchema.safeParse(parseItemFD(fd)); if (!parsed.success) { return { ok: false as const, error: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(" · ") }; @@ -74,14 +87,14 @@ export async function createHostItemAction(fd: FormData) { actorEmail, details: { name: created.name, providerId }, }); - revalidatePath("/espace-prestataire/items"); - redirect(`/espace-prestataire/items/${created.id}`); + revalidatePath(`${basePath}/items`); + redirect(`${basePath}/items/${created.id}`); } export async function updateHostItemAction(itemId: string, fd: FormData) { - const { providerId, actorEmail } = await requireOwnedProvider(); + const { providerId, actorEmail, basePath } = await requireOwnedProvider(); const session = await auth(); - if (!(await canManageRentalProvider(session!.user.id, session?.user?.role, providerId))) { + if (!(await canManageRentalProvider(session!.user.id, session?.user?.role, providerId, session?.user?.organizationId))) { return { ok: false as const, error: "Accès refusé" }; } const existing = await prisma.rentalItem.findUnique({ where: { id: itemId }, select: { providerId: true } }); @@ -100,13 +113,13 @@ export async function updateHostItemAction(itemId: string, fd: FormData) { actorEmail, details: { name: parsed.data.name }, }); - revalidatePath("/espace-prestataire/items"); - revalidatePath(`/espace-prestataire/items/${itemId}`); + revalidatePath(`${basePath}/items`); + revalidatePath(`${basePath}/items/${itemId}`); return { ok: true as const }; } export async function deleteHostItemAction(itemId: string) { - const { providerId, actorEmail } = await requireOwnedProvider(); + const { providerId, actorEmail, basePath } = await requireOwnedProvider(); const existing = await prisma.rentalItem.findUnique({ where: { id: itemId }, select: { providerId: true, _count: { select: { lines: true } } }, @@ -125,8 +138,8 @@ export async function deleteHostItemAction(itemId: string) { actorEmail, details: {}, }); - revalidatePath("/espace-prestataire/items"); - redirect("/espace-prestataire/items"); + revalidatePath(`${basePath}/items`); + redirect(`${basePath}/items`); } const blockSchema = z.object({ @@ -137,7 +150,7 @@ const blockSchema = z.object({ }); export async function addItemBlockAction(itemId: string, fd: FormData) { - const { providerId, actorEmail } = await requireOwnedProvider(); + const { providerId, actorEmail, basePath } = await requireOwnedProvider(); const existing = await prisma.rentalItem.findUnique({ where: { id: itemId }, select: { providerId: true } }); if (!existing || existing.providerId !== providerId) { return { ok: false as const, error: "Item introuvable." }; @@ -171,12 +184,12 @@ export async function addItemBlockAction(itemId: string, fd: FormData) { actorEmail, details: { ...parsed.data }, }); - revalidatePath(`/espace-prestataire/items/${itemId}`); + revalidatePath(`${basePath}/items/${itemId}`); return { ok: true as const }; } export async function removeItemBlockAction(blockId: string) { - const { providerId, actorEmail } = await requireOwnedProvider(); + const { providerId, actorEmail, basePath } = await requireOwnedProvider(); const block = await prisma.rentalItemAvailability.findUnique({ where: { id: blockId }, select: { itemId: true, rentalBookingId: true, item: { select: { providerId: true } } }, @@ -195,7 +208,7 @@ export async function removeItemBlockAction(blockId: string) { actorEmail, details: { itemId: block.itemId }, }); - revalidatePath(`/espace-prestataire/items/${block.itemId}`); + revalidatePath(`${basePath}/items/${block.itemId}`); return { ok: true as const }; } @@ -208,7 +221,7 @@ const statusSchema = z.enum([ ]); export async function updateBookingStatusAction(bookingId: string, status: string) { - const { providerId, actorEmail } = await requireOwnedProvider(); + const { providerId, actorEmail, basePath } = await requireOwnedProvider(); const session = await auth(); const role = session?.user?.role; const parsed = statusSchema.safeParse(status); @@ -232,6 +245,6 @@ export async function updateBookingStatusAction(bookingId: string, status: strin actorEmail, details: { status: parsed.data }, }); - revalidatePath("/espace-prestataire/reservations"); + revalidatePath(`${basePath}/reservations`); return { ok: true as const }; } diff --git a/src/lib/rental-access.ts b/src/lib/rental-access.ts index 95ec6e6..8f5c9dc 100644 --- a/src/lib/rental-access.ts +++ b/src/lib/rental-access.ts @@ -6,22 +6,36 @@ import { auth } from "@/auth"; import { UserRole } from "@/generated/prisma/enums"; import { prisma } from "@/lib/prisma"; +/** + * Garde-fou pour /espace-prestataire ET /espace-ce/materiel : accepte RENTAL_PROVIDER, + * CE_MANAGER, ADMIN. Chacun voit ensuite SON provider : + * - RENTAL_PROVIDER → via managedByUserId + * - CE_MANAGER → via organizationId + * - ADMIN → null si pas de providerId fourni (force le choix) + */ export async function requireRentalProviderSession() { const session = await auth(); if (!session?.user?.id) { redirect("/connexion?next=/espace-prestataire"); } const role = session.user.role; - if (role !== UserRole.RENTAL_PROVIDER && role !== UserRole.ADMIN) { + if ( + role !== UserRole.RENTAL_PROVIDER && + role !== UserRole.CE_MANAGER && + role !== UserRole.ADMIN + ) { redirect("/"); } return session; } /** - * Récupère le RentalProvider géré par l'utilisateur. - * Si ADMIN, on retourne soit le provider explicitement ciblé (param `providerId`), - * soit null pour forcer le choix. + * Récupère le RentalProvider courant selon le rôle. + * + * - ADMIN avec `providerId` : retourne ce provider. + * - ADMIN sans `providerId` : retourne null (force le choix admin). + * - RENTAL_PROVIDER : retourne `findFirst({ managedByUserId })`. + * - CE_MANAGER : retourne `findFirst({ organizationId: session.user.organizationId })`. */ export async function getCurrentRentalProvider(opts: { providerId?: string } = {}) { const session = await auth(); @@ -34,21 +48,53 @@ export async function getCurrentRentalProvider(opts: { providerId?: string } = { if (role === UserRole.ADMIN && !opts.providerId) { return null; } - // RENTAL_PROVIDER : retourne le provider lié + if (role === UserRole.CE_MANAGER && session.user.organizationId) { + return prisma.rentalProvider.findFirst({ + where: { organizationId: session.user.organizationId }, + }); + } + // RENTAL_PROVIDER return prisma.rentalProvider.findFirst({ where: { managedByUserId: session.user.id }, }); } +/** + * Variante explicite pour le contexte CE : retourne le provider lié à `organizationId` + * (ou null s'il n'a pas encore été activé). Utile dans /espace-ce/materiel pour + * détecter l'onboarding nécessaire. + */ +export async function getCurrentRentalProviderForCe(organizationId: string) { + return prisma.rentalProvider.findFirst({ + where: { organizationId }, + }); +} + +/** + * Vrai si : + * - ADMIN + * - le user est le manager nominal du provider (RENTAL_PROVIDER) + * - le user est CE_MANAGER ET son organizationId == provider.organizationId + */ export async function canManageRentalProvider( userId: string, role: string | undefined, providerId: string, + userOrgId?: string | null, ): Promise { if (role === UserRole.ADMIN) return true; const provider = await prisma.rentalProvider.findUnique({ where: { id: providerId }, - select: { managedByUserId: true }, + select: { managedByUserId: true, organizationId: true }, }); - return provider?.managedByUserId === userId; + if (!provider) return false; + if (provider.managedByUserId === userId) return true; + if ( + role === UserRole.CE_MANAGER && + userOrgId && + provider.organizationId === userOrgId + ) { + return true; + } + return false; }