# Ressourcerie du Calaisis / FACE VALO

Application de gestion des collectes gratuites, des livraisons payantes et des
meubles vendus en attente de retrait. Laravel 13, PHP 8.3, MySQL 8.4.

## Environnement local

PHP n'est pas dans le PATH. Utiliser le binaire Laragon qui dispose d'un
`php.ini` complet (openssl, gd, exif, intl) :

    C:\laragon\bin\php\php-8.3.28-Win32-vs16-x64\php.exe

Les autres versions installées par Laragon n'ont pas de `php.ini` chargé et
échouent sur openssl. Composer : `C:\laragon\bin\composer\composer.phar`.

Base locale : `facevalo_app` (utilisateur `root`, sans mot de passe). Ne jamais
toucher à la base `facevalo`, qui appartient à l'application de suivi de tournée
située dans `C:\laragon\www\facevalo`.

E-mails de développement capturés par Mailpit, consultables sur
<http://localhost:8025>. Aucun envoi ne sort de la machine.

## Versions et mise en production

Le numéro vit dans le fichier `VERSION`, à la racine, lu par `config/version.php`
et affiché en pied de chaque écran. Un script de déploiement doit pouvoir le lire
sans démarrer PHP, d'où le fichier plutôt qu'une constante.

Forme : `MAJEUR.MINEUR.CORRECTIF`, suivi d'un canal tant que la version n'est pas
destinée au service. `alpha` en développement, `beta` en essai à la Ressourcerie,
rien du tout pour une version de service.

**Seule une version sans canal part en production.** L'interface affiche le canal
en clair : une préversion ne doit jamais être prise pour la version en service.

Poser une version, dans cet ordre :

1. Écrire le nouveau numéro dans `VERSION`.
2. Ajouter sa section en tête de `CHANGELOG.md`, par jalon et non par commit.
3. Valider les deux ensemble, puis `git tag -a vX.Y.Z -m "…"`.

Le journal des versions se lit par le client : il dit ce qui change pour lui,
pas ce qui change dans le code.

Les seeders de réglages, de messages et de types de journée n'écrasent jamais
une valeur déjà en base : ils ajoutent ce qui manque et rafraîchissent les
intitulés. Une mise à jour ne doit pas effacer ce que la Ressourcerie a saisi.
Un changement de données existantes passe par une migration, pas par un seeder.

## Règles métier à ne pas réinventer

Ces points ont été explicitement rappelés par le client. Les contredire casse la
recette.

**Droits d'accès.** Le contrôle se fait côté serveur, sur chaque route, chaque
requête et chaque fichier. Masquer un champ dans une vue ne suffit pas : les
données interdites à un rôle ne doivent jamais figurer dans la réponse HTTP,
même brute. Cela vaut aussi pour les liens directs, les photos, les PDF et les
exports.

**Fuseau horaire.** Europe/Paris partout, y compris pour tout calcul de délai.

**Délai de retrait d'un meuble vendu.** Trois jours d'ouverture après le jour
d'achat, le jour d'achat non compté, échéance à la fermeture du troisième jour
ouvert. Les jours de fermeture, y compris les fermetures exceptionnelles, ne
comptent pas. Ne jamais remplacer ce calcul par un raccourci du type « plus six
jours » ou « plus une semaine ».

**Tarif d'une livraison.** Forfait du secteur et de la prestation, plus quinze
euros par armoire ou penderie à remonter, jamais inclus d'office. Le forfait
couvre l'adresse entière, quel que soit le nombre de meubles ou de passages. Le
secteur est choisi explicitement, jamais déduit d'une distance. Le tarif d'un
dossier existant reste figé si la grille change ensuite.

**Bon signé.** Il reste interne. Il n'est jamais envoyé au client. Le client
reçoit un récapitulatif, sans notes internes ni données d'équipe.

## Conventions

Interface entièrement en français, pensée pour le téléphone d'abord. Le code,
les noms de tables et de colonnes sont également en français, pour rester lisible
par le client et cohérent avec le cahier des charges.

## Charte graphique

Couleurs relevées sur le logo de La Ressourcerie du Calaisis, publié sur
<https://facevalo.fr> : bleu `#007CBC`, vert `#76BE02`, texte blanc. Elles sont
déclarées comme échelles Tailwind `marque` (bleu) et `accent` (vert) dans
`resources/css/app.css`.

Le bleu porte les actions et le bandeau. Le vert reste un accent, comme sur le
logo : sur fond blanc il plafonne à un contraste de 2,3 pour 1 et ne convient
donc pas au texte courant. Le logo est servi depuis `public/images`, sur un fond
du même bleu pour que son cadre se fonde dans le bandeau.

## Collectes

Retour de Romain, 14 septembre 2026, qui remplace les consignes précédentes sur
les horaires de collecte, les véhicules et la création préalable de tournées.

Une collecte se valide et se date depuis sa fiche, sans qu'une tournée existe.
La date porte une demi-journée (`periode_rdv`), et c'est ce que le donateur
apprend, avec la plage horaire réglée pour ce moment de la journée. La tournée
se compose ensuite et doit tomber sur ce créneau : `Planning::placer` refuse
une tournée d'un autre jour ou d'une autre demi-journée. Retirer une collecte
datée d'une tournée ne l'annule pas ; elle retourne parmi les dossiers à placer.

Aucun véhicule n'est exigé pour dater une collecte. Le tableau de bord signale
les collectes datées qui ne sont sur aucune tournée, en urgence à deux jours.

Le donateur apprend chaque décision : informations à compléter, refus avec son
motif, acceptation en attente de date, puis le récapitulatif. Une demande
acceptée et datée dans le même geste ne reçoit que le récapitulatif, et une
annonce d'acceptation encore en attente est annulée si la date arrive avant.

## Envois automatiques

Aucun message ne part directement du code. Il est d'abord inscrit dans la table
`envois`, puis expédié par `envois:traiter`. Ce détour donne un état conservé
pour chaque message, des échecs visibles aux managers, et une clé d'unicité qui
rend le doublon impossible.

Un message est réévalué juste avant de partir : un rappel de retrait dont le
client est venu chercher les meubles est annulé plutôt qu'envoyé. Toute nouvelle
règle d'envoi se pose dans `ComposeurEnvois::motifAnnulation`.

Les textes viennent des réglages, jamais du code. Une nouvelle sorte de message
a besoin de ses réglages `<declencheur>_actif`, `_sujet` et `_corps`.

Le serveur a besoin d'une seule tâche planifiée, à lancer chaque minute :

    php artisan schedule:run

Sans elle, les messages restent en attente. Le reste du calendrier est réglé
dans `routes/console.php`, ce qui évite de dépendre d'un processus permanent,
rarement disponible sur un hébergement mutualisé.

## Pôles d'activité et rôle administrateur

La Ressourcerie ne se limite pas au magasin : la logistique, la boutique du
centre commercial et d'autres équipes travaillent séparément, et chacune n'a pas
à voir les dossiers des autres. Le pôle porte cette cloison, au-dessus des rôles.

Quatre rôles, du plus large au plus étroit : administrateur, manager, magasin,
logistique. L'administrateur voit tous les pôles et franchit tous les filtres de
rôle ; les trois autres ne voient que leurs pôles.

Une brique nouvelle se cloisonne en trois gestes : une colonne `pole_id` sur sa
table, le trait `AppartientAUnPole` sur son modèle, et `duPerimetre($user)` sur
ses listes. Les politiques d'accès appellent `peutAcceder($modele->pole_id)`.

Ni un compte ni un dossier ne reste sans pôle : à défaut, ils rejoignent le
premier pôle actif. Un dossier orphelin ne serait visible que de
l'administrateur, et sa cloison ne voudrait plus rien dire.

## Encombrants

Enlèvement pour la collectivité, distinct des collectes chez les donateurs :
rien n'est examiné sur photo, rien n'est facturé, et le ramassage se fait sur le
trottoir. Le pôle Logistique le porte ; le magasin n'y a pas accès.

Les adresses arrivent par feuille de route, en général un export Excel du
service de la ville. `LecteurFeuilleDeRoute` et `Geocodeur` sont repris de
l'application de suivi de tournée (`C:\laragon\www\facevalo`) : le premier
tolère le point-virgule, le Windows-1252 et des intitulés de colonnes
approximatifs, le second interroge la Base Adresse Nationale.

Le géocodage sert de relecture, pas de cartographie : une adresse que la Base
Adresse Nationale ne retrouve pas est presque toujours une faute de frappe, et
l'écran de vérification la signale avant que le camion ne s'y rende.

L'ordre des adresses sur la feuille ne veut rien dire : la collectivité les
saisit au fil des appels. Le suivre ferait traverser l'agglomération plusieurs
fois. Une tournée s'ordonne donc sur la carte, via `OrdonnanceurTournee` :
matrice des temps par OSRM, plus proche voisin, puis 2-opt. Sans OSRM, repli sur
les distances à vol d'oiseau ; l'ordre reste cohérent, la précision baisse. Le
manager garde la main et peut remanier l'ordre proposé.

Un arrêt déjà clôturé garde sa place en tête : il est derrière l'équipe. Un
arrêt sans coordonnées part en fin de tournée, signalé, plutôt que d'être placé
au hasard.

Le point de départ est le dépôt, réglable dans l'administration : une adresse
que le formulaire géocode lui-même. Sans dépôt renseigné, l'ordre part du
premier arrêt.

Les feuilles font rarement une page : deux au minimum, une trentaine d'adresses
la plupart du temps. Le titre et la ligne d'en-tête reviennent donc en haut de
chaque page, et le lecteur PDF les écarte : sans cela ils seraient recopiés dans
l'adresse du dernier arrêt de la page précédente.

Le camion se remplit avant la fin de la liste. L'équipe déclare « camion plein »,
part vider, puis reprend : c'est `PassageDepot`, plusieurs fois par jour si
besoin. Ces temps expliquent la journée, sans quoi le manager voit une équipe
lente là où elle a fait trois rotations.

L'équipe pointe ses arrêts depuis le même écran que les collectes et les
livraisons. Trois choses y changent selon l'activité : la liste des résultats
possibles, la checklist de chargement, réservée aux livraisons, et le bloc des
objets, absent pour un encombrant.

## Comptes

Le client a tranché : un compte par manager, un compte par salarié, et un compte
partagé par branche d'activité pour les nouveaux arrivants, le temps qu'ils
aient le leur. Le compte partagé porte `compte_partage` et se rattache au pôle
de sa branche, comme n'importe quel autre.

Un compte partagé vit sur plusieurs téléphones : couper ses connexions ouvertes
(`sessions_revoquees_at`) est le geste à faire au départ d'un salarié.

## Alertes internes

Deux canaux, demandés tous les deux : le courriel pour celui qui n'a pas
l'application ouverte, et l'alerte dans l'application pour celui qui y travaille,
avec un signal sonore.

`CentreAlertes` lève les alertes ; chacune porte une clé d'unicité, comme un
envoi, donc un même fait ne se signale jamais deux fois. Le bon signé et le
retrait en retard avaient déjà leur courriel : il n'est pas doublé. Les alertes
nouvelles passent par `PlanificateurEnvois::alerteInterne`, qui écrit aux
managers du pôle concerné — la cloison vaut aussi pour les messages.

L'état « lu » vit sur le couple alerte-utilisateur (`alertes_lues`), pas sur
l'alerte : sans quoi le premier manager à ouvrir l'écran la ferait disparaître
pour ses collègues.

La cloche interroge `alertes.compteur` toutes les trente secondes et sonne quand
une alerte nouvelle apparaît. Le son est produit par WebAudio, sans fichier à
héberger, et n'est armé qu'au premier geste de l'utilisateur : les navigateurs
mobiles refusent tout son avant cela. L'interrupteur du son vit dans le
`localStorage`, donc par appareil.

Une notification quand l'application est fermée demanderait Web Push, un service
worker et, sur iPhone, une installation sur l'écran d'accueil. Cela dépend de
l'hébergement définitif et reste à décider.

## Planning des effectifs

Il change souvent, et le tenir sur papier oblige le manager à le réimprimer.
Il couvre tous les salariés, magasin compris, et chaque salarié appartient à un
pôle : un manager n'y voit et n'y écrit que les siens, contrôlé sur les données
reçues. Il est indépendant des interventions : dire qui travaille ne compose
aucune tournée.

Le modèle s'appelle encore `Equipier`, parce qu'il est né pour les équipes de
terrain ; l'interface dit « salariés ». Seuls les salariés du pôle Logistique
sont proposés pour monter dans un camion.

Un type de journée peut appartenir à un pôle : les horaires du camion ne se
proposent pas au magasin. Le magasin a sa journée, sans horaires, parce que les
siens n'ont pas encore été donnés et qu'on ne les invente pas.
Deux vues, mois et semaine, et deux onglets pour un salarié : celui de l'équipe
et le sien.

Les types de journée sont des données, pas du code. Un type porte ses horaires,
dit s'il compte comme travaillé, et s'il est confidentiel. Un type confidentiel,
comme un rendez-vous avec la référente socio-professionnelle, s'affiche
« Absent » pour les collègues ; seuls le salarié concerné et les managers en
lisent le motif.

Un équipier peut recevoir un accès personnel depuis l'écran des équipiers. Il ne
voit alors que les tournées où le manager l'a affecté, et sa journée du jour est
rappelée en tête de son écran.

Horaires de la logistique : 9 h - 12 h et 13 h - 16 h 30. Sur les encombrants,
une heure plus tôt le matin et le soir, mais la coupure du midi est la même :
8 h - 12 h et 13 h - 15 h 30. Il arrive aussi que les encombrants fassent
journée continue, d'où un second type, 8 h - 15 h sans coupure.

## Bénévoles

Première version, demandée par Romain le 14 septembre 2026 : un formulaire
public de candidature, sans compte, et son suivi interne en un statut et des
notes. Les champs sont ceux qu'une association demande d'ordinaire ; ils
évolueront à l'usage.

Le candidat choisit une activité ; la candidature rejoint ce pôle et reste dans
sa cloison. Il reçoit un accusé de réception, les managers du pôle une alerte.

Les formulaires ouverts sans compte, dons et bénévolat, passent par le
limiteur `formulaires-publics`, six envois par minute par adresse. Celui des
bénévoles porte en plus un champ piège : un robot qui le remplit reçoit la page
de remerciement et n'enregistre rien.

## Formulaires allégés

Retour de Romain : alléger la saisie des objets et de l'accès au logement.
Rien n'est supprimé ; l'essentiel reste visible et le reste se replie derrière
« Préciser ». Un bloc replié se rouvre de lui-même quand il contient une saisie
revenue en erreur, sinon la personne ne verrait pas ce qu'elle doit corriger.
## Page d'entrée

L'adresse nue de l'application (`racine`) est une page publique qui aiguille le
public vers ses formulaires et les salariés vers la connexion ; un compte déjà
connecté est renvoyé sur `accueil`. Les écrans internes, eux, renvoient toujours
un visiteur non connecté vers la connexion. Tout nouveau formulaire public doit
y trouver sa tuile, et un lien depuis la page de connexion.