Docs menu

Phase 3 — Mode ÉTABLISSEMENT mobile (brief d'implémentation)#

⚠️ Document historique#

Brief d'implémentation du prototype, conservé pour la trace des décisions. Il ne décrit pas la plateforme actuelle. Pour l'état réel, voir ARCHITECTURE.md et API.md.

Brief pour Claude Code (modèle fable). Implémente la Phase 3 du SPEC Staff : app mobile, mode ÉTABLISSEMENT, sans casser Phase 1 (prestataire) ni Phase 2 (web).

Principes obligatoires#

  • NE CASSER RIEN : ne modifie ni app/(tabs)/*, ni app/onboarding/* (prestataire), ni app/role.tsx au-delà du branchement du bouton établissement, ni apps/web/*, ni packages/ui/src/tokens.ts (les valeurs existantes).
  • Design system unique : apps/mobile/src/lib/theme.ts + packages/ui (tokens sémantiques). Aucune couleur brute dans les écrans. Vert #059669.
  • Textes légaux EXACTS : SEPA (SPEC §6.1) et engagement annuel (SPEC §6.2) — recopiés au caractère près, non modifiables.
  • Français partout, TypeScript strict, tabular-nums pour tous les montants/ distances/horaires/chronos.
  • Zéro texte blanc-sur-blanc, zéro bouton/carte sans fond. Chaque écran COMPLET et peuplé de données mockées crédibles Martinique.
  • Données mockées cohérentes avec le web : mêmes établissements, mêmes prestataires, mêmes missions (voir apps/web/src/data/*).

1. Types partagés (packages/ui/src/types.ts — AJOUTS seulement, ne rien casser)#

Ajouter en fin de fichier :

ts
/** Régions DOM-TOM et leur taux de TVA (SPEC §8). */
export interface RegionTva {
  id: string;
  label: string;
  /** Taux de TVA applicable, en pourcentage (8.5 ou 0). */
  tva: 8.5 | 0;
}

/** Formule d'abonnement établissement (SPEC §6.2). */
export type FormuleAbonnement = "1_site" | "3_sites" | "5_plus";

/** Établissement connecté (espace mobile + backup web). */
export interface Etablissement {
  id: string;
  nom: string;
  enseigne: string;
  adresse: string;
  ville: string;
  siret: string;          // SIRET du site
  secteur: SectorId;
  region: string;          // id region (martinique, ...)
  responsable: string;
  email: string;           // email de facturation
  telephone: string;
  abonnement: {
    formule: FormuleAbonnement;
    montantHT: number;     // €/mois HT
  };
  sepa: {
    iban: string;
    bic: string;
    titulaire: string;
    mandateSigned: boolean;
    signatureHorodatage?: string; // ISO, simulé
  };
  missionsTotal: number;
  depensesMois: number;        // € HT dépensés en missions ce mois
  prochaineEcheance: string;   // ISO
  cguAccepted: boolean;
  engagementSigned: boolean;
  engagementSignatureHorodatage?: string; // ISO, simulé
}

/** Candidat à une mission (côté établissement). Liste capée 5–8. */
export interface CandidatMission {
  id: string;
  prenom: string;
  nom: string;
  noteGlobale: number;     // 1–5
  niveau: "standard" | "or" | "platine" | "diamant";
  missionsTotal: number;
  distanceKm: number;
}

/** Statut de pointage d'une mission (côté établissement, suivi). */
export type PointageEtabState = "en_attente" | "en_cours" | "termine";

export interface PointageEtab {
  state: PointageEtabState;
  startedAt?: string;      // "HH:mm"
  endedAt?: string;        // "HH:mm"
  dureeH?: number;         // heures validées
  gpsConfirme: boolean;
}

/** Mission enrichie côté établissement (suivi + notation). */
export interface MissionEtab {
  id: string;
  etablissementId: string; // rattachée à l'établissement connecté
  secteur: SectorId;
  fonction: string;
  date: string;             // ISO
  heureDebut: string;       // "HH:mm"
  dureeH: number;
  remuneration: number;     // €/h
  rayonKm: number;          // 10 ou 25 (extension simulée)
  ville: string;
  candidats: CandidatMission[];      // capé 5–8
  prestataireRetenuId?: string;
  pointage: PointageEtab;
  statut: "a_venir" | "en_cours" | "terminee";
  prestataireNote?: { noteGlobale: number; critères: NotationEtab; commentaire: string; notePrive?: string };
  notifiees?: number;       // nb prestataires notifiés autour (écran de confirmation)
}

/** Notation d'un prestataire par un établissement (SPEC §7). 4 critères + 5★. */
export interface NotationEtab {
  ponctualite: number;    // 1–5
  presentation: number;  // 1–5
  attitude: number;       // 1–5
  competences: number;    // 1–5
}

/** Facture d'abonnement établissement (PDF simulé). */
export interface FactureEtab {
  id: string;
  date: string;           // ISO
  montantHT: number;
  tva: number;            // 8.5 ou 0 selon région
  montantTTC: number;
  statut: "payee" | "a_payer";
  url: string;            // URL simulée du PDF
}

Ajouter aussi un export de la liste des régions avec TVA dans tokens.ts (à la suite de l'export regions existant — ne pas modifier regions) :

ts
/** Régions DOM-TOM avec leur taux de TVA (SPEC §8 TVA). */
export const regionsTva: RegionTva[] = [
  { id: "martinique", label: "Martinique", tva: 8.5 },
  { id: "guadeloupe", label: "Guadeloupe", tva: 8.5 },
  { id: "reunion", label: "La Réunion", tva: 8.5 },
  { id: "guyane", label: "Guyane", tva: 0 },
  { id: "saint-martin", label: "Saint-Martin", tva: 0 },
  { id: "saint-barthelemy", label: "Saint-Barthélemy", tva: 0 },
];

/** Calcule le taux de TVA d'une région (par id). 8,5 % DOM / 0 % Guyane & collectivités. */
export function tvaPourRegion(regionId: string): 8.5 | 0 {
  for (const r of regionsTva) {
    if (r.id === regionId) return r.tva;
  }
  return 0;
}

Et exporter regionsTva et tvaPourRegion depuis packages/ui/src/index.ts.

2. Textes légaux EXACTS — module partagé#

Créer packages/ui/src/legal.ts (nouveau) :

ts
/** Mandat SEPA — SPEC §6.1 (texte EXACT, non modifiable). */
export const MANDAT_SEPA_TEXTE =
  "En confirmant ce mandat, vous autorisez (A) Staff SAS à envoyer des instructions à votre banque " +
  "pour débiter votre compte, et (B) votre banque à débiter votre compte conformément aux " +
  "instructions de Staff SAS. Vous bénéficiez d'un droit à remboursement par votre banque selon les " +
  "conditions décrites dans la convention que vous avez passée avec elle. Toute demande de " +
  "remboursement doit être présentée dans les 8 semaines suivant la date de débit de votre compte. " +
  "Pour les débits non autorisés : 13 mois maximum.";

/** Engagement annuel — SPEC §6.2 (texte EXACT, case à cocher, non modifiable). */
export const ENGAGEMENT_ANNUEL_TEXTE =
  "En cochant cette case, vous souscrivez un abonnement Staff d'une durée minimale de 12 mois " +
  "(valeur totale : 358,80 € HT pour la formule 1 site, soit 29,90 € × 12 mois). À l'issue de cette " +
  "période, l'abonnement se renouvelle automatiquement par tacite reconduction pour des périodes " +
  "successives de 12 mois. Vous serez informé du renouvellement 3 mois avant l'échéance. En cas de " +
  "résiliation anticipée, le solde des mois restants sera facturé à hauteur de 50 %. ";

Exporter depuis index.ts.

3. Données mockées mobile (apps/mobile/src/data/)#

etablissement.ts (nouveau)#

L'établissement connecté = etablissements[0] du web (Station Total Energies — Génipa, responsable fictif, Ducos, Martinique, formule 3 sites à 79,90 € HT, SEPA signé). Reprendre exactement les valeurs du web (apps/web/src/data/etablissements.ts élément e1) pour l'objet Etablissement (mobile). Exporter etablissementCourant: Etablissement.

missions-etab.ts (nouveau)#

Missions de l'établissement connecté (e1 = Génipa + Aimé Césaire, ce sont e1 et e7 du web). Reprendre ~6 missions à partir du web (apps/web/src/data/missions.tsetablissement contient "Génipa" ou "Aimé Césaire"), mappées en MissionEtab avec etablissementId: "e1", candidats, pointage, statut. Inclure : 1 en cours, 1 a_venir, 2 terminees, 1 en attente de notation (terminee sans prestataireNote), 1 a_venir à publier. Cohérent avec le web (mêmes prestataires p1..p12, mêmes notes/niveaux).

factures-etab.ts (nouveau)#

4–6 factures d'abonnement (mensuelles) de l'établissement connecté, montantHT=79.90, tva=8.5 (Martinique), TTC=86,69 €, statut payee/a_payer, URL simulée https://staff.jaikin.dev/factures/F-XXXX.pdf.

4. TVA — test automatisé#

Créer apps/mobile/tests/tva.test.ts (vitest) testant tvaPourRegion :

  • Martinique/Guadeloupe/Réunion → 8.5
  • Guyane/Saint-Martin/Saint-Barthélemy → 0
  • région inconnue → 0 Et un test calculant le TTC d'une facture 79,90 € HT en Martinique → 86,69 € TTC (arrondi 2).

5. Branchement du rôle (app/role.tsx)#

Le bouton « Je suis établissement » doit pousser vers /etablissement/onboarding/step-1 au lieu de /onboarding/step-1. Le bouton prestataire reste sur /onboarding/step-1. Un seul changement de onPress sur la carte établissement.

6. Onboarding établissement (3 étapes)#

app/etablissement/onboarding/_layout.tsx (Stack, headerShown: false)#

Group etablissement/onboarding avec un layout Stack neutre.

app/etablissement/onboarding/step-1.tsx — Établissement#

Réutiliser le gabarit onboarding (progress 1/3, retour, eyebrow, title, footer primaire). Champs : Nom de l'établissement, Adresse, SIRET du site, Secteur (picker parmi les secteurs actifs : Station-service, Netttoyage — réutiliser sectors filtré active), Région (picker parmi 6 — réutiliser regions), TVA calculée dynamiquement affichée en lecture seule sous la région (8,5 % ou 0 % — utiliser tvaPourRegion), Email de facturation, Responsable. Validation : tous champs renseignés + SIRET 14 chiffres + email valide → push step-2.

app/etablissement/onboarding/step-2.tsx — Abonnement + SEPA + Engagement#

  • Choix formule : 3 cartes (1 site 29,90 € HT/mois · 3 sites 79,90 € HT/mois · 5+ sites « Devis »). La 3 sites est présélectionnée ( cohérent avec l'établissement connecté mocké).
  • Bloc Mandat SEPA : champs Titulaire, IBAN, BIC (BIC auto-déduit de l'IBAN, lecture seule, simulé — afficher un BIC mocké). Puis le texte légal SEPA EXACT (MANDAT_SEPA_TEXTE) dans un encadré surfaceSubtle, en caption, textSecondary (texte long lisible, jamais blanc sur blanc). Case à cocher « Je signe ce mandat SEPA » + bouton « Signer » = clic horodaté (simulé : affiche « Signé le JJ/MM/AAAA à HH:mm »).
  • Bloc Engagement 12 mois : case à cocher avec le texte EXACT (ENGAGEMENT_ANNUEL_TEXTE) en regard. Signature = clic horodaté simulé.
  • Validation : formule choisie + SEPA signé + engagement coché+signé → push step-3.

app/etablissement/onboarding/step-3.tsx — Activation#

Récap complet : établissement (nom, adresse, SIRET, secteur, région + TVA, email, responsable), abonnement (formule + montant HT/mois), SEPA (titulaire, IBAN masqué **** dernière partie, signé le …), engagement (signé le …). Case CGU obligatoire. Bouton « Accéder au tableau de bord » → router.replace("/etablissement/missions") (tab bar établissement, onboarding consommé).

7. Tab bar établissement#

app/(etablissement)/_layout.tsx (nouveau)#

Group route (etablissement) avec Tabs. 4 onglets :

  • missions → « Missions » (dashboard) — icône LayoutGrid
  • publier → « Publier » — icône Plus (ou SquarePen)
  • suivi → « Suivi » — icône ClipboardList (ou ListChecks)
  • profil → « Profil » — icône User

Mêmes screenOptions que le prestataire (glass, activeTintColor primary, height 68) — réutiliser le style exact de app/(tabs)/_layout.tsx.

app/(etablissement)/missions.tsx — Dashboard#

  • En-tête Screen « Missions ».
  • Hero card (dégradé hero) : « Bonjour {responsable} », missions actives (nb), prestataires en mission ce mois (nb), dépenses du mois (€ HT, tabular-nums), prochaine échéance (date).
  • Section « Missions actives » : cartes MissionEtab (fonction, secteur chip, date/heure, durée, rémunération, rayon, nb candidats) → tap ouvre /etablissement/suivi/[id].
  • Section « À venir » : missions a_venir.
  • États : loading skeleton (300ms), empty « Aucune mission active — publiez-en une ».
  • Données : etablissementCourant + missionsEtab.

app/(etablissement)/publier.tsx — Publier une mission#

Form : secteur (picker actifs), fonction (input), date (input JJ/MM/AAAA ou picker natif DatePicker simulé — input texte suffit), heure début (HH:mm), durée h (stepper 2–8, min 2 h), rémunération €/h (input numérique, défaut 12), rayon (segmented 10 km / 25 km extension simulée). Bouton « Publier la mission » → écran de confirmation publier-confirmation.tsx « X prestataires notifiés autour » (X simulé selon rayon : 10 km → 12, 25 km → 28). Bouton « Voir le suivi » → /(etablissement)/suivi/[id] ou retour missions.

app/(etablissement)/suivi.tsx — Liste de suivi#

Toutes les missions (actives + passées) avec statut pointage (en attente → en cours → terminé). Tap → /etablissement/suivi/[id].

app/(etablissement)/suivi/[id].tsx — Suivi mission détail#

  • En-tête : fonction + établissement + statut.
  • Bloc candidats : liste capée 5–8 (afficher mission.candidats), chaque candidat = carte (prénom nom, niveau badge Or/Platine/Diamant, note 5★, missions total, distance) + bouton « Sélectionner » (état pressed). Le prestataire retenu est mis en avant (liseré vert + check).
  • Bloc QR code à afficher sur place : généré (faux QR — réutiliser le composant web FauxQR s'il existe, sinon un grid SVG/View de 21×21 modules deterministe depuis l'id mission). Bouton « Afficher le QR en grand » (plein écran modal). Légende « À scanner par le prestataire sur place ».
  • Bloc pointage : statut (en attente → en cours → terminé), heures (startedAt → endedAt, dureeH). Badge GPS « Position confirmée » si gpsConfirme.
  • Si terminée et non notée → bouton « Noter le prestataire » → /etablissement/noter/[id].
  • Si terminée et notée → récap note (5★ + 4 critères + commentaire + note privée masquée « Note privée enregistrée — interne Staff »).

app/(etablissement)/noter.tsx — Liste missions à noter#

Missions terminees sans note → tap /etablissement/noter/[id].

app/(etablissement)/noter/[id].tsx — Noter le prestataire#

  • 5★ global (tap étoiles 1–5, grande taille).
  • 4 critères : Ponctualité / Présentation / Attitude / Compétences — chacun 5★ (plus petit).
  • Commentaire public (≤ 200 car, compteur live, textarea).
  • Note privée établissement (interne Staff, jamais publique) — textarea séparé, label « Note privée — visible par Staff uniquement, jamais par le prestataire. »
  • Bouton « Publier la note » → confirmation + retour suivi.
  • États : disabled tant que note globale < 1 ou commentaire vide.

app/(etablissement)/profil.tsx — Abonnement / SEPA / factures#

  • Carte établissement (nom, SIRET, secteur, région, responsable).
  • Bloc abonnement : formule + montant HT/mois + prochaine échéance.
  • Bloc SEPA : titulaire, IBAN masqué, BIC, « Mandat signé le … ».
  • Bloc Factures : liste facturesEtab (date, montant HT, TVA, TTC, statut). Tap → ouvre facture/[id].
  • Lien « Modifier mon établissement » (simulé, alerte).

app/(etablissement)/facture/[id].tsx — Facture PDF simulé#

Récap facture + « Télécharger le PDF » (simulé, alerte/lien url). Bouton retour.

8. Composants partagés à réutiliser / créer#

  • Réutiliser @/components/screen, @/components/ui (Card, Chip, PrimaryButton, SectionTitle, FieldLabel), @/components/press-scale, @/components/app-header, @/components/onboarding-shell.
  • Créer @/components/faux-qr.tsx : génère un QR visuel (grid 21×21 de View carrés, déterministe depuis un hash de l'id mission). Noir/blanc, bord arrondi 2xl, liseré blanc.
  • Créer @/components/etoiles.tsx : composant 5★ tappable (props: value, onChange, size, disabled). Étoile pleine = colors.primary (ou gold pour les badges niveau), vide = bord semantic.textTertiary.
  • Créer @/components/badge-niveau.tsx : badge Or/Platine/Diamant avec dégradé noble (gradients.reward pour Or, slate pour Platine, diamond pour Diamant) — petit chip.
  • Créer @/components/checkbox.tsx : case à cocher (carré arrondi sm, check Check lucide, état pressed). Couleur check = colors.primary.

9. Validation (DoD)#

  • cd apps/mobile && npx tsc --noEmit : 0 erreur.
  • cd apps/mobile && npx expo export --platform web : réussit, routes (etablissement)/* présentes dans dist.
  • pnpm --filter @staff/mobile test : vert (onboarding + tva tests).
  • pnpm --filter @staff/web build : toujours OK (rien cassé côté web — ne pas le modifier).
  • git add -A && git commit -m "feat(mobile): mode ÉTABLISSEMENT Phase 3 ..." (français). Branch DEV. NE TOUCHE PAS PREVIEW/PRODUCTION. JAMAIS vercel deploy.

10. Garde-fous (rappels)#

  • Zéro texte blanc-sur-blanc. Sur fond vert/dégradé → blanc ; sur surface blanche → encre.
  • Chaque écran COMPLET et peuplé (données mockées, états loading/empty couverts).
  • Ne change pas les textes légaux/exacts. Ne casse pas Phase 1/2.
  • Les types @staff/ui ne sont qu'en AJOUTS (ne rien supprimer/renommer).
Last updated Aug 13, 2026Powered by GitDoc — CleverAI