Commit graph

21 commits

Author SHA1 Message Date
Ubuntu
5be62f012f feat(rental): Sprint O — reversements prestataires (payouts)
All checks were successful
CI / test (pull_request) Successful in 2m40s
Marketplace encaisse centralisé sur System D → besoin de tracer les
virements mensuels aux prestataires tiers. Migration appliquée prod.

Schema :
- Modèle RentalPayoutMark { id, providerId, periodMonth, amount,
  reference, paidAt, paidByEmail }. Unique (providerId, periodMonth)
  → 1 mark = 1 mois = 1 virement par provider.

Lib src/lib/payouts.ts :
- monthKey(d) → 1er du mois minuit UTC (clé de période).
- listProviderPayouts({monthsBack=6}) → grid provider × mois avec
  bookingsCount + grossAmount (itemsTotal) + commission + netAmount
  (gross-commission) + statut paid via RentalPayoutMark. Exclut
  System D (commission 0%, géré par l'asso). Statut « payé » lu
  depuis les marks. Tri : mois desc puis providerName.
- createPayoutMark (idempotent via findUnique avant insert) +
  deletePayoutMark.

Politique : net dû = itemsTotal - commissionAmount (depositTotal
hors flux, collecté par le provider auprès du client). Politique
documentée dans le commentaire en tête de payouts.ts.

/admin/payouts/page.tsx :
- 3 KPIs (À payer / Déjà payé / Mois affichés).
- Une section par mois (6 derniers), tableau provider × CA brut +
  commission + net dû + statut.
- MarkPaidForm : bouton « Marquer payé » → form inline (amount
  pré-rempli avec net dû, reference optionnelle) → action
  markPayoutPaidAction. Statut payé montre amount + ref + bouton
  « Annuler marquage ».

Server actions :
- markPayoutPaidAction (admin only, idempotent, audit
  admin.payouts/payout.mark + payout.already_marked) → envoie
  sendPayoutSent au contactEmail du provider (best-effort).
- unmarkPayoutPaidAction → delete + audit payout.unmark.

Email sendPayoutSent : notification au provider quand un virement est
marqué payé. Inclut amount + reference + lien dashboard.

Sidebar admin gagne entrée « Reversements » sous Activité.

Tests vitest tests/lib/payouts.test.ts (4 cas) : monthKey
normalisation UTC + idempotence + janvier sans bug, formatMonth fr-FR.
Total : 74/74 ✓.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 02:59:16 +00:00
Ubuntu
ea0e606735 feat(ce): Sprint K — public badge + invites CE_MEMBER + tests
All checks were successful
CI / test (pull_request) Successful in 2m45s
Public badge sur fiche carbet :
- carbet-public.ts charge les OrganizationCarbetMembership (org
  approuvée uniquement) + expose `organizations: {id,name,slug}[]`.
- /carbets/[slug] affiche « Géré par le CE <name> » sous le header
  si au moins 1 org liée.

Invites CE_MEMBER :
- Migration 20260603300000_org_invite_token : OrgInviteToken
  (tokenHash, organizationId, email?, createdByUserId, expiresAt,
  usedAt). Cascade sur Organization. Index expiresAt + organizationId.
- src/lib/ce-invites.ts : createOrgInviteToken (TTL 14j),
  listOrgInviteTokens, getOrgInviteByToken (validité + expiry),
  markOrgInviteConsumed, revokeOrgInviteToken. Token = 24 bytes
  base64url, hash sha256.
- /espace-ce/membres : liste membres (CE_MANAGER + CE_MEMBER actifs)
  + form de génération de lien (email optionnel = lock email côté
  signup) + liste des invitations avec statut actif/consommé/expiré +
  bouton révoquer.
- /espace-ce/membres/actions.ts : createInviteAction +
  revokeInviteAction. Audit log scope=ce.invite.
- API /api/signup étendue : zod accepte inviteToken, branche dédiée
  qui crée User CE_MEMBER + organizationId du token + marquage
  usedAt. Vérif email match si email fourni dans le token.
- /inscription?invite=TOKEN : récupère l'invite, pré-affiche org name,
  lock email si fourni, masque les fieldsets type de compte (forcé
  CE_MEMBER).

CTA marketing :
- /pour-comites-entreprise : section CTA « Créer mon espace CE » sous
  le rendu content-pages, conditionnée par plugin ce-management.

Tests vitest (tests/lib/ce-access.test.ts) :
- canManageCarbet : admin always, owner direct, CE_MANAGER via org
  match, refus si autre org / pas d'org / TOURIST / pas de membership.
- 9 tests, mocks next-auth + @/auth + @/lib/authorization pour éviter
  next/server (incompatible vitest sans setup).
- Total tests projet : 62/62 ✓.

Dashboard /espace-ce : lien vers /espace-ce/membres en bas.

Migration prod appliquée.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 00:03:03 +00:00
Ubuntu
946dd8d5d2 feat(ce): Sprint G — data model + admin validation
All checks were successful
CI / test (pull_request) Successful in 2m38s
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>
2026-06-02 22:55:54 +00:00
Ubuntu
9da58288dc feat(rental): Sprint F — photos & vidéos items rental
All checks were successful
CI / test (pull_request) Successful in 2m18s
Nouveau modèle `RentalItemMedia` parallèle de `Media` (carbet) :
- s3Key / s3Url / sortOrder / type (PHOTO|VIDEO), cascade sur RentalItem
- Migration `20260603100000_rental_item_media` appliquée

Endpoints upload dédiés (mêmes conventions que carbet) :
- POST /api/uploads/rental-presign + POST /api/uploads/rental-finalize
  → auth par canManageRentalProvider (admin OR provider manager)
  → s3Key préfixé `rental-items/<itemId>/`
  → finalize hydrate `RentalItem.imageUrl` avec la première PHOTO
  → générateur de variantes (320/800/1600 via sharp) en best-effort
- DELETE /api/rental-media/[id] + POST /api/rental-media/reorder
  → reorder rafraîchit imageUrl (cover = sortOrder 0)

`MediaUploader` rendu générique :
- Nouveau prop `scope: {kind: "carbet" | "rental-item", id}` ; conserve
  rétro-compat `carbetId` (deprecated)
- Endpoints + payload key (`carbetId` ↔ `itemId`) calculés via
  `endpointsFor()`. Aucun changement de comportement côté carbet.

UI branchée :
- /admin/rental-items/[id] : section « Photos & vidéos » au-dessus du
  form, alimentée par `item.media` chargé par `getRentalItemForAdmin`
- /espace-prestataire/items/[itemId] : idem, charge via `getHostItem`
- /materiel/[itemId] : nouveau `<ItemGallery />` (thumbs cliquables +
  support vidéo). Fallback : ancien `item.imageUrl` si pas de média
  dédié (compat seed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 09:34:09 +00:00
Claude Integration
e2f3f070fa feat(rental): Sprint A — modèle Prisma + admin CRUD + seed 13 items
Some checks failed
CI / test (pull_request) Failing after 1m52s
2026-06-02 03:26:04 +00:00
Claude Integration
dc2b07507f feat: 4 critères opérationnels (route/capacité/électricité/GSM) + presets profils + badges
All checks were successful
CI / test (pull_request) Successful in 2m23s
2026-06-02 02:26:02 +00:00
Claude Integration
2545a5e1a8 feat: « Au fil de l'eau » — Reels mobile + uploader pro + favoris
All checks were successful
CI / test (pull_request) Successful in 2m18s
2026-06-02 00:27:16 +00:00
Claude Integration
a6df96db7e feat: reset password + page mon-compte (RGPD) + facettes recherche (prix max, équipements)
All checks were successful
CI / test (pull_request) Successful in 2m19s
2026-06-01 10:16:37 +00:00
Claude Integration
e79b6dd141 feat(p0): prix/nuit + booking form public + /inscription + /reservations/[id] 2026-06-01 01:34:00 +00:00
Claude Integration
a9fcd18022 feat(admin): /admin/home — éditeur des textes de la page d'accueil (FR+EN, override DB) 2026-06-01 01:10:49 +00:00
Claude Integration
79ddcd23f5 feat(admin): Sprint 5 — Audit log + Settings (gouvernance) 2026-06-01 00:13:49 +00:00
Claude Integration
87c3e7a581 feat: ContentPage bilingue (PK composite slug+lang) + seed pages EN
Migration : ContentPage.id devient PK composite (slug, lang) au lieu de slug
seul, pour stocker une version FR et une version EN du même slug. Index sur
slug seul pour les lookups.

Schema Prisma : @@id([slug, lang]).

Helpers :
- getContentPage(slug, lang) avec fallback FR si la version dans la langue
  demandée n'existe pas
- listContentPages(category?, lang?) accepte un filtre lang
- upsertContentPage : utilise le composite key

Pages publiques (a-propos, faq, comment-ca-marche, pour-comites-entreprise,
devenir-loueur, cgv, mentions-legales, politique-de-confidentialite) :
ajoutent un appel à getLocale() et le passent à getContentPage.

Seeds :
- src/lib/plugins/seeds/content-pages-en.ts : 8 pages traduites en anglais
- hook onEnable du plugin i18n-fr-en : seed EN pages au toggle on. Désactiver
  i18n n'efface pas les EN pages (elles dorment, fallback FR reprend).

Résultat : quand l'utilisateur switche vers EN, /a-propos, /faq, /cgv, etc.
basculent en anglais. Le contenu hors-DB (composants UI) bascule déjà via les
dictionnaires de la PR i18n-fr-en initiale.
2026-05-31 11:45:47 +00:00
Claude Integration
a174f99eba feat(plugin): pirogue-providers (Phase 3.3)
Modèle PirogueProvider (id, name, contacts, fleuves, tarif, description)
+ enum TransportMode (OWNER_PROVIDES, SELF_ARRANGE, PARTNER_PROVIDER) sur Carbet
+ relation Carbet → PirogueProvider (nullable, ondelete:SetNull)

Composants :
- PirogueTransportBlock (server, gated par plugin) sur fiche carbet :
  affiche le mode + provider partenaire avec contacts/tarif/description
- Page publique /partenaires-pirogue : liste des partenaires actifs

Seed onEnable :
- 3 partenaires démo (Pirogues du Maroni, Approuague Aventures, Oyapock Frontière)
  avec tarifs estimatifs et fleuves desservis réels
- Attribution aux 6 carbets démo :
  · Awara (Maroni), Maripa (Approuague), Paripou (Oyapock) → PARTNER_PROVIDER
  · Wapa (Comté), Mahury CE → OWNER_PROVIDES
  · Kourou Couleuvre → SELF_ARRANGE

onDisable désactive les partenaires démo et détache les carbets démo.
2026-05-31 11:29:29 +00:00
Claude Integration
68f37f554f feat(plugins): content-pages + legal-pages (Phase 4.1 + 4.3)
Plugin content-pages :
- Modèle Prisma ContentPage (slug PK, title, body markdown, category, published)
- lib/content-pages.ts : helpers upsert/get/list/unpublish
- lib/markdown.ts : mini-renderer markdown server-side sans deps externe
  (h1-h3, paragraphes, gras/italique, liens, listes ul/ol, hr, blockquote,
  échappement HTML)
- ContentPageRenderer server component, applique le theme Guyane (font-serif)
- 5 pages seedées : /a-propos, /faq, /comment-ca-marche,
  /pour-comites-entreprise, /devenir-loueur
- Routes publiques + force-dynamic + guard requirePluginOr404

Plugin legal-pages :
- Réutilise le même modèle ContentPage, catégorie 'legal'
- 3 pages seedées : /cgv, /mentions-legales, /politique-de-confidentialite
  (contenu de base, à valider par avocat avant prod réelle)

Admin :
- /admin/content-pages : table par catégorie, statut publié/dépublié
- /admin/content-pages/[slug] : éditeur markdown + toggle publié
- PATCH /api/admin/content-pages/[slug]

Hooks plugin :
- onEnable seed + republish toutes les pages
- onDisable dépublie toute la catégorie sans la supprimer (preserve les edits)
2026-05-31 10:12:13 +00:00
Claude Integration
3405f00476 chore(prisma): ajoute minStayNights/maxStayNights/minCapacity/seasonalConstraints au modèle Carbet (oubli PR#30) 2026-05-31 08:52:46 +00:00
Claude Integration
bc571b38d1 chore(prisma): déclare enum AccessType (oublié dans PR#27) 2026-05-31 03:00:52 +00:00
Claude Integration
5e59202505 feat(plugins): access-type + demo-carbets-seed (Phase 3.1 + 2.5)
Plugin access-type :
- Migration : enum AccessType (ROAD_AND_RIVER, RIVER_ONLY), champ accessType
  sur Carbet avec default ROAD_AND_RIVER, roadAccessNote optionnel,
  pirogueDurationMin rendu nullable + index sur accessType
- Schema Prisma mis à jour
- Composant <AccessTypeBadge> client, gated par le plugin
- Carbet card et fiche enrichies : badge + texte adapté (Pirogue vs Route+pirogue
  vs Route directe), section Accès enrichie avec roadAccessNote
- formatPirogueDuration accepte null

Plugin demo-carbets-seed :
- Hook onEnable : 3 propriétaires demo (Yann/Émilie/CE Hôpital) + 6 carbets
  variés (Maroni, Approuague, Comté, Oyapock, Mahury, Kourou) avec mix
  3 RIVER_ONLY + 3 ROAD_AND_RIVER, GPS plausibles, descriptions naturelles
- Hook onDisable : archive (status=ARCHIVED) les carbets demo via slug prefix
- Toutes les fixtures idempotentes (upsert via slug + email)
2026-05-31 02:56:25 +00:00
Claude Integration
62cc464738 feat(plugins): foundation système Plugin Karbé
- Modèle Prisma Plugin (key, name, description, category, version, enabled,
  config JSONB, migrationsApplied, timestamps) + migration SQL
- PluginRegistry (src/lib/plugins/registry.ts) avec 12 plugins déclarés :
  visuels (theme-guyane, landing-hero, landing-sections, image-gallery-seed,
  demo-carbets-seed), métier (access-type, seasonality, pirogue-providers,
  min-stay), contenus (content-pages, legal-pages), i18n (i18n-fr-en)
- Server helpers (server.ts) : sync, isEnabled, getEnabledKeys, toggle avec
  hooks onEnable/onDisable, updateConfig, cache 5s
- Client bridge (client.tsx) : PluginProvider + useIsPluginEnabled
- Composant <IfPluginEnabled plugin=... fallback=...>
- Guard requirePluginOr404 pour pages et routes
- Page admin /admin/plugins avec table toggle par catégorie + édition config
- Route PATCH /api/admin/plugins/[key] + GET
- Layout async qui sync registry + passe enabledKeys au PluginProvider

Tous plugins en enabled=false par défaut, activation pilotée depuis l'admin.
2026-05-30 22:17:10 +00:00
fcc2749d1d feat(carbet): add lastBookedAt and endpoint 2026-05-30 17:52:41 +00:00
54f406053b feat(prisma): add full Karbé schema and initial migration
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-29 16:59:35 +00:00
e464b86cb2 chore: scaffold Next.js + Prisma + Tailwind
Initialise le projet Karbé :
- Next.js 16 (App Router, TypeScript) + Tailwind CSS v4 + ESLint
- Prisma avec datasource PostgreSQL, schema minimal et client généré
  dans src/generated/prisma (postinstall: prisma generate)
- Page d'accueil placeholder (titre + mission)
- .env.example (DATABASE_URL, NEXTAUTH_SECRET)
- README avec instructions de setup

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-29 16:59:11 +00:00