Promotions : période de validité (date de début / date de fin)

Une promotion peut désormais porter une date de début et une date de fin,
toutes deux facultatives et indépendantes. Sans date, rien n'est affiché.

- Schéma : `promotions.starts_on` / `ends_on` (`date`), migration 0015.
- `src/lib/promo-validity.ts` (pur, testé) : `formatValidity()` produit
  « Valable du 1er au 15 mars 2027 », « Valable jusqu'au … », « Valable à
  partir du … » en repliant mois et année quand ils se répètent ;
  `formatValidityShort()` pour les lignes de méta ; `isRangeInvalid()` pour
  la validation.
- Espace adhérent : deux champs date, aperçu en direct de la phrase et
  blocage de l'envoi si la fin précède le début.
- `publishPromo` relit et revalide les bornes côté serveur.
- Affichage : carte d'accueil, fiche adhérent, liste de l'adhérent, carte de
  modération — et le message Facebook / LinkedIn, via le même formateur.
- L'ancien texte libre `valid_until` reste comme repli d'affichage pour les
  promotions de démonstration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
This commit is contained in:
Claude committed 2026-09-06 11:00:44 +00:00
1 parent 81b99ed5fb
commit 02c5378a47
15 files changed
+1700 -6

No files matched your search

+6
View File
@@ -6,6 +6,7 @@ import {
text,
varchar,
boolean,
date,
timestamp,
index,
uniqueIndex,
@@ -107,6 +108,11 @@ export const promotions = pgTable("promotions", {
imageUrl: text("image_url"),
memberId: integer("member_id").references(() => members.id),
status: promoStatusEnum("status").notNull().default("pending"),
// Période de validité. Les deux bornes sont facultatives et indépendantes :
// une offre peut n'avoir qu'une date de fin, qu'une date de début, ou aucune.
startsOn: date("starts_on"),
endsOn: date("ends_on"),
/** @deprecated Texte libre des anciennes promos ; sert de repli à l'affichage. */
validUntil: varchar("valid_until", { length: 120 }),
// Réseaux demandés à la soumission. Le choix reste modifiable tant que la
// promo est « pending » ; la validation le fige ET déclenche la publication.