src/lib/ce-access.ts (NEW) :
- requireCeManagerSession (redirect connexion ou / si rôle insuffisant)
- getCurrentCeOrganization (CE_MANAGER → son org via organizationId,
ADMIN → org ciblée par paramètre ou null)
- canManageCarbetForCe (owner direct OU membre d'une org liée)
- requireApprovedOrg (redirect /espace-ce?pending=1 si non validée)
Emails best-effort :
- sendNewCeRequest → admin (contact@karbe) avec lien filtré
/admin/organizations?status=pending
- sendCeApproved → CE_MANAGERs actifs de l'org après validation
- Branchement dans approveOrganizationAction : envoie le mail à tous
les CE_MANAGERs actifs de l'org en best-effort.
Signup CE public :
- SignupForm 4e tuile « Comité d'Entreprise » avec champ orgName.
Layout grid 4 colonnes sur lg, 2 sur sm.
- /api/signup étendu :
- zod accepte CE_MANAGER + orgName
- transaction $tx atomique : Organization (approved=false, slug
auto-unique via slugify + suffix) + User (role=CE_MANAGER,
organizationId lié)
- sendNewCeRequest best-effort
- réponse étendue avec organizationId
- Pattern slug : retry avec suffix -2, -3… jusqu'à libre
Dashboard /espace-ce :
- layout.tsx : requirePluginOr404("ce-management") +
requireCeManagerSession
- page.tsx : 4 KPIs (carbets co-gérés, items rental, bookings 30j,
revenu 30j), bannière « En attente de validation » si pending,
2 ActionCards (Mes carbets, Matériel rental) marquées « Bientôt »
jusqu'aux sprints I et J
- ce-dashboard.ts : getCeOrgKpis (agrège bookings carbets via
membership + rentalBookings via provider.organizationId) +
listCeCarbets pour Sprint I
SiteHeader : lien « Espace CE » conditionné par role + plugin
(mirror du lien Espace prestataire).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schema:
- Organization gagne le workflow d'approbation (approved + approvedAt +
approvedBy + contactEmail). Backfill : toutes les orgs existantes
(CMCK) → approved=true via migration.
- Nouveau OrganizationCarbetMembership (manyToMany Org↔Carbet) pour la
co-gestion CE : un Carbet a un ownerId (créateur initial) + 0..n
memberships ; chaque CE_MANAGER d'une org liée peut gérer le carbet
en plus de l'owner. Pour un hôte individuel = pas de membership.
- RentalProvider.organizationId (nullable, SetNull on delete) : un CE
peut posséder son provider ; les CE_MANAGERs membres de l'org y ont
accès en plus du manager nominal.
Plugin ce-management ajouté au registry (catégorie business, off par
défaut). Quand off : signup CE caché + dashboard /espace-ce 404.
Admin organizations :
- Tab statut (Toutes / À valider [count] / Validées) avec compteur des
organisations pending dans l'en-tête.
- Badge statut sur la liste et la page détail.
- Bouton « Valider l'organisation » sur le détail (action
approveOrganizationAction → flip approved=true + approvedAt + audit
log organization.approve). Idempotent : un re-appel sur une org déjà
validée ne re-loggue pas.
- Détail montre les compteurs carbetMemberships + rentalProviders.
Migration appliquée à la DB prod (CMCK backfill validé).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ESLint react-hooks/set-state-in-effect bloque le CI. On déplace la
re-hydratation depuis le cookie dans le lazy initializer de useState
(qui ne court qu'une fois côté client). Conserve la cohérence si un
autre onglet a modifié le panier entre le render serveur et l'hydration,
sans déclencher de re-render en cascade.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cart lib + cookie persistence (karbe-rental-cart, 30j) avec context React
useCart(). Provider wrappé dans layout pour hydratation server→client.
Page /panier :
- Récap regroupé par prestataire (sous-totaux, caution)
- Édition lignes (dates, qté), suppression, vider panier
- Bouton « Valider et payer » → POST /api/rentals/checkout
- Badge 🛒 dans SiteHeader avec total items
Composant <AddToCart /> sur /materiel/[itemId] avec date picker + qté.
API POST /api/rentals/checkout :
- Validation auth + items actifs + provider approved + qté/dates
- Transaction Prisma : recheck stock par fenêtre + crée 1 RentalBooking
par prestataire + RentalLines (snapshot prix) + RentalItemAvailability
(blocage des dispos)
- Calcul commissionAmount selon provider.commissionPct
- Si Stripe activé : Checkout Session unique avec 1 line_item par
RentalBooking, metadata {type:"rental-bundle", rentalBookingIds:[]}
- Sinon : crée en PENDING, retourne rentalBookingIds
- Vide le cookie panier après création
- Audit log rental.checkout.created
Webhook Stripe étendu :
- checkout.session.completed type=rental-bundle → CONFIRMED+SUCCEEDED
sur toutes les RentalBookings du bundle
- payment_intent.payment_failed metadata.rentalBookingIds → CANCELLED
+ supprime les RentalItemAvailability (libère le stock)
Intégration carbet :
- /carbets/[slug] : panneau « Compléter votre séjour » avec items des
prestataires de la même rivière + System D (recommandation contextuelle)
- /reservations/[id] : section « Matériel associé » listant les
RentalBookings liées
- /mes-locations : page récap toutes les locations (System D + tiers,
liées carbet ou standalone)
- Lien « Mes locations » dans SiteHeader
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>