Commit graph

8 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
c564028ca9 feat(rental): Sprint M — refonds + annulations Stripe
All checks were successful
CI / test (pull_request) Successful in 2m37s
Politique de remboursement v1 :
- > 7 jours du début → FULL (location + caution)
- 1 à 7 jours → PARTIAL_50 (50% location + caution intégrale)
- < 24h ou passé → DEPOSIT_ONLY (caution seulement, pas de remboursement
  sur la location)

src/lib/rental-refund.ts (NEW) : computeRentalRefund({startDate,
itemsTotal, depositTotal}) → { itemsRefund, depositRefund, totalRefund,
policy, policyLabel }. Arrondi au centime, support de Decimal.

POST /api/rentals/[id]/cancel :
- Auth multi-rôle : tenant de la booking, RENTAL_PROVIDER nominal ou
  CE_MANAGER de l'org du provider, ADMIN. Détecte `cancelledBy` pour
  adapter l'email.
- Refuse si status ∉ {PENDING, CONFIRMED} (HANDED_OVER → non
  annulable, contacter Karbé).
- Calcule le refund selon la politique.
- Stripe refund best-effort si paymentStatus=SUCCEEDED + stripeSessionId
  existante + isStripeConfigured + totalRefund > 0. Retrieve session →
  payment_intent → refunds.create. Échec Stripe = audit-logged mais
  le flip status continue (l'asso pourra rembourser manuellement).
- Transaction : update RentalBooking (CANCELLED + paymentStatus
  REFUNDED si SUCCEEDED sinon FAILED) + delete RentalItemAvailability
  (libère stock).
- Audit log rental.cancel avec montants, policy, cancelledBy,
  stripeRefundId, stripeRefundError.
- Email best-effort : sendRentalCancelled à tenant + provider (sauf si
  provider est le canceller).

src/components/CancelRentalButton.tsx : composant client confirm dialog
inline avec textarea motif (max 500 chars). Branché sur :
- /mes-locations : « Annuler ma location » sur résa PENDING/CONFIRMED
- BookingDecision (utilisé par /espace-prestataire/reservations ET
  /espace-ce/materiel/reservations) : remplace l'ancienne mini-confirm
  qui flippait juste le status, désormais via la vraie API refund

sendRentalCancelled email : adapté selon cancelledBy ("Vous avez annulé"
/ "<Provider> a annulé" / "L'équipe Karbé a annulé").

tests/lib/rental-refund.test.ts : 8 cas (FULL @ 10+ et 7j, PARTIAL_50,
DEPOSIT_ONLY < 24h et passé, arrondi centime, zéro caution, policyLabel).
Total projet : 70/70 ✓.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 02:17:58 +00:00
Ubuntu
3a557b6de5 feat(ce): Sprint L — email auto invites + admin memberships UI
All checks were successful
CI / test (pull_request) Successful in 2m39s
Email automatique pour les invites CE_MEMBER :
- sendCeInviteEmail(to, orgName, inviteUrl, inviterName?) : template
  best-effort (dry-run sans Resend), bouton CTA + lien direct en plain
  text. Mentionne TTL 14j + warning si pas le destinataire attendu.
- createInviteAction branche l'envoi automatique quand un email est
  renseigné dans le formulaire. Audit log gagne emailedAutomatically.
- InviteForm UI : affiche « lien généré · email envoyé » quand un
  email était fourni. Texte d'aide mis à jour.
- Sans email → comportement inchangé : lien à copier manuellement.

Admin /admin/carbets/[id] gagne section memberships :
- src/lib/admin/carbets.ts : getCarbetForEdit inclut organizations +
  listOrganizationsForLink helper (toutes orgs triées approved desc).
- 2 actions admin : linkCarbetToOrganizationAction (idempotent) +
  unlinkCarbetFromOrganizationAction. Audit scope=admin.carbets,
  events carbet.org.link / carbet.org.unlink.
- CarbetMemberships client component : liste les orgs liées (badge
  pending si org non approuvée) + select des orgs disponibles + boutons
  Lier/Délier. Désactive le select quand toutes les orgs sont déjà
  liées.

Le link admin permet de :
- Lier rétroactivement un carbet existant à un CE (cas où l'orga
  intègre un carbet d'un hôte individuel).
- Délier un carbet quand un CE part ou que le carbet repasse en
  gestion individuelle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 01:59:18 +00:00
Ubuntu
63a29d9ade feat(ce): Sprint H — signup CE public + /espace-ce shell
All checks were successful
CI / test (pull_request) Successful in 2m33s
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>
2026-06-02 23:12:46 +00:00
Ubuntu
5607a51980 feat(rental): Sprint E — emails + plugin toggle + tests
Some checks failed
CI / test (pull_request) Failing after 1m10s
3 nouveaux templates email (best-effort, dry-run sans Resend) :
- sendRentalRequestedTenant : récap de demande au locataire (par RB)
- sendRentalRequestedProvider : nouvelle demande au prestataire
- sendRentalConfirmed : confirmation paiement reçu

Branchements :
- POST /api/rentals/checkout : envoie tenant + provider après création
  des RentalBooking (PENDING), catch global pour ne pas bloquer
- Webhook Stripe rental-bundle : envoie sendRentalConfirmed à chaque
  locataire après update CONFIRMED+SUCCEEDED

Plugin gear-rental :
- Ajout au registry (catégorie business)
- layout.tsx /materiel + /espace-prestataire avec requirePluginOr404
- requirePluginOr404 dans /panier et /mes-locations
- isPluginEnabled guard dans POST /api/rentals/checkout (404 si off)
- SiteHeader masque liens Matériel / Mes locations / Espace prestataire
  + CartBadge si plugin désactivé
- CompleteYourStay renvoie null si plugin désactivé
Décision admin → activable depuis /admin/plugins comme tous les autres.

Tests vitest (tests/lib/rentals.test.ts, 16 tests) :
- diffDays (mêmes dates, 1 nuit, 7 jours, négatif)
- parseCart (null/garbage/schéma invalide/valide/format date)
- serializeCart (updatedAt, roundtrip)
- commission formula (0%, 15%, arrondi centime)
- availability arithmetic (totalQty libre, soustractions, plancher 0)

53 tests pass total. Build OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 08:49:39 +00:00
Claude Integration
59786e5365 feat(rental): Sprint C — espace prestataire (signup+dashboard+items+calendrier+résa)
All checks were successful
CI / test (pull_request) Successful in 2m33s
2026-06-02 08:01:42 +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
b59b8a0af2 feat(p1): calendrier dispo + emails Resend + amount calculé + best-effort welcome/confirmation/refund 2026-06-01 02:20:38 +00:00