L'assistant publiait directement dans le wiki global. La politique de contenu
ecarte la faute franche — conseil FCL, cas client, hors perimetre — mais une
heuristique ne juge pas la justesse : une page fausse mais bien ecrite la
franchissait, et se retrouvait citee comme documentation Xpeditis aupres de
tous les clients.
Domaine
- `WikiContributionStatus` : pending / published / rejected. `create()` ne
prend pas le statut en parametre — rien ne nait publie.
- `publish(reviewerId, edits?)` valide, en acceptant une correction du
relecteur, qui repasse la meme politique de contenu.
- `reject(reviewerId, note?)` ecarte sans supprimer : la liste des refus montre
ou l'assistant se trompe.
- `revise()` remet une page validee en attente : sans cela, la validation
porterait sur un texte que l'assistant a remplace depuis.
Ce qui sort du depot
- `findPublished` pour la recherche et la page publique, `findForReview` pour
l'administration. Le statut est porte par la requete, pas filtre en memoire :
une page en attente ne peut pas sortir par le chemin des clients.
- `revision()` ne compte que le publie, donc l'index vectoriel ne se reconstruit
que sur une decision.
Relecture
- `GET /admin/wiki-contributions`, `POST :id/publish`, `POST :id/reject`, sous
JwtAuthGuard + RolesGuard et @Roles('admin').
- Chaque decision est journalisee (`WIKI_CONTRIBUTION_REVIEWED`) : une page
publiee engage la marque, on doit pouvoir dire qui l'a laissee passer.
- Ecran `/admin/wiki` : file par statut, corps complet affiche, correction et
motif de refus facultatifs.
L'assistant
- La capacite rend `pending_review` et un message d'attente, plus d'URL : le
modele annonce une proposition, jamais une publication. La consigne le lui
dit explicitement.
SQL de la migration verifie sur la base locale : l'upsert sur index
d'expression met bien a jour a la casse pres, la contrainte de statut refuse
une valeur inconnue.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'assistant pouvait conclure « prenez plutot un conteneur complet » avec
l'autorite de la marque, et repondre sur du transport interieur. Il ne pouvait
pas non plus combler un trou de documentation qu'il venait de rencontrer.
Perimetre (openai-trade.adapter)
- SCOPE_RULES : LCL uniquement. Le FCL reste explicable — c'est du vocabulaire
metier — mais jamais recommande, chiffre, ni presente comme la meilleure
option. Un cas hors LCL part vers support@xpeditis.com.
- SCOPE_RULES : transport international uniquement. Le transport interieur, le
demenagement et le transport de personnes sont refuses, pas traites « un peu ».
- La page wiki « LCL vs FCL » ne conseille plus le FCL : elle le decrit, et
liste les cas hors perimetre a signaler au support. Corpus reconstruit.
- L'amorce « LCL ou FCL ? » devient une question sur le calcul du fret LCL.
Documentation interne d'abord (KNOWLEDGE_RULES)
- Les extraits du wiki sont dits source de reference, avant les connaissances
generales du modele.
Entretien du wiki global
- Nouvelle capacite `contribute_wiki_page` (scope write) : quand le wiki ne
couvre pas un sujet d'information generale sur le transport international,
l'assistant ecrit la page. Un titre deja pris est mis a jour, pas duplique.
- `wiki-contribution-policy` (domaine) decide ce qui entre : refus du contenu
qui conseille le FCL, du cas client (dossier, tarif, coordonnees) et du hors
perimetre. Le prompt oriente, cette fonction empeche.
- Un sujet deja couvert par le wiki publie (score >= 0,62) est refuse.
- `WikiRetriever` indexe les contributions a cote du corpus fige, avec un index
en memoire invalide par la revision du jeu ; une panne de ce cote ne coute pas
la reponse.
- Page `/dashboard/wiki/complements`, etiquetee comme ecrite par l'assistant,
et carte sur l'index du wiki.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le statut PENDING_PAYMENT ne designe plus un paiement en attente mais un devis :
une reservation construite dont les frais de booking ne sont pas encore regles.
L'enum backend devient QUOTE, converti en place par migration.
Les deux onglets se partagent desormais la meme liste :
- « Reservations » n'affiche que PENDING, ACCEPTED et REJECTED ;
- « Historique des devis » regroupe QUOTE, PENDING_BANK_TRANSFER et CANCELLED,
avec les actions propres a un devis (modifier, payer, supprimer).
La table, l'export et les filtres sont extraits dans BookingListView pour eviter
de dupliquer la page entre les deux vues.
Tableau de bord :
- l'alerte « Paiement en attente » disparait des points a traiter, les devis non
regles ne bloquant aucun envoi en cours ;
- « Sans reponse du transporteur » devient « En attente du transporteur ».
Migration verifiee en transaction annulee sur la base de dev : les lignes
PENDING_PAYMENT deviennent QUOTE, l'enum et le defaut de colonne suivent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZJ5aowZJVAcyPZw5TiNEm
- Nouvel endpoint GET /csv-bookings/reservation-quota: limite = vrai plan de
l'org (pas l'override ADMIN PLATINIUM), used = bookings PAYES de l'annee
(PENDING_BANK_TRANSFER/PENDING/ACCEPTED), limitReached.
- ShipmentCounter: countPaidShipmentsForOrganizationInYear.
- create-check compte desormais les payes (au lieu de tous).
- Frontend useReservationQuota consomme l'endpoint (fiable meme pour un admin
dont l'overview renvoie PLATINIUM).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le plan gratuit (BRONZE) est limite a maxShipmentsPerYear (5/an). Cote frontend,
rien ne le refletait: le bouton 'Nouvelle Reservation' restait actif. Ajout d'un
hook useReservationQuota (limite du plan vs reservations de l'annee) qui:
- remplace le bouton 'Nouvelle Reservation' par un bouton 'Ameliorer mon
abonnement' + banniere sur la liste des reservations quand la limite est
atteinte;
- bloque la page de recherche (nouvelle resa) avec un ecran d'upgrade, sauf en
mode edition d'une resa existante.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La reprise reconstruisait un cube (cbrt du volume) au lieu de la base palette
standard, d'ou des longueur/largeur/hauteur incorrectes. Pour une palette on
reconstitue desormais 120x80 cm et on deduit la hauteur du volume (ex: 0.96 CBM
-> 120x80x100). Les colis restent en cube equivalent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les options du formulaire de recherche etaient collectees mais jamais stockees.
Ajout d'une colonne jsonb options sur csv_bookings (+ migration), stockee a la
creation et a l'edition (editFromRate), renvoyee dans la reponse, et re-pre-
remplie a la reprise (search-advanced <- URL <- booking.options).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remplace la page d'édition autonome par une reprise du parcours de réservation :
"Modifier" ré-ouvre la recherche pré-remplie (origine/destination/volume/poids/
palettes reconstruits), l'utilisateur ajuste si besoin, choisit une compagnie
(résultats) et passe au paiement — la réservation existante est mise à jour au
lieu d'en créer une nouvelle.
- Backend: PATCH /csv-bookings/:id/rate + UpdateCsvBookingRateDto + service
updateBookingRate + domaine editFromRate (carrier/route/conteneur/transit/
cargo/prix modifiables avant paiement).
- Front: search-advanced pré-rempli via query (+ editBookingId, démarre à
l'étape colis) ; results en mode édition met à jour puis redirige vers /pay ;
liens "Modifier" (liste, modale, paiement) pointent vers le parcours ;
suppression de la page /booking/:id/edit autonome.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La recherche de tarif renvoie le transporteur en slug ("ssc-consolidation")
alors que la réservation stocke le nom d'affichage ("SSC Consolidation"), donc
le matching exact companyName === carrierName ne trouvait jamais l'offre →
"aucun tarif trouvé" et prix jamais recalculé.
Matching robuste : comparaison normalisée du transporteur (minuscules, sans
séparateurs) + conteneur + transit, avec fallbacks. Vérifié en conditions
réelles (GET booking, search-csv-offers, PATCH details renvoient 200 ; le prix
change bien quand le volume change).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Suppression de documents: cause = deleteDocument restreint aux statuts
PENDING/PENDING_PAYMENT + blocage du dernier document. Alignement sur
replaceDocument (aucune restriction de statut) et autorisation de 0 document
(validation domaine assouplie ; creation exige toujours >=1 doc cote service).
- Edition avant paiement: recalcul auto du prix. La page /booking/:id/edit
relance la recherche de tarif (meme transporteur/conteneur/transit) pour le
nouveau volume/poids et met a jour fret/FOB/total ; nouveaux champs de prix
dans UpdateCsvBookingDetailsDto + editDetails.
- Liste reservations: actions regroupees dans un menu 3 points (kebab) en
positionnement fixed (anti-clipping), desktop + mobile.
- Responsive (<=1280px): tables documents & blog admin en overflow-x-auto,
menus documents en fixed, grilles de la page edition en sm:.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Edition d'une reservation avant paiement: page /booking/:id/edit
(volume, poids, palettes, notes + gestion documents), endpoint
PATCH /csv-bookings/:id/details (owner + PENDING_PAYMENT), methode
domaine editDetails. Acces "Modifier" depuis la liste, la modale detail
et la page de paiement.
- Filtre "A finaliser" (PENDING_PAYMENT + PENDING_BANK_TRANSFER) + libelles
et couleurs de tous les statuts.
- Tableau des reservations: 6 colonnes + colonne Actions collante a droite
(plus de scroll horizontal pour atteindre Payer/Voir).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Telechargement docs transporteur: proxy de streaming API (fin des URLs
presignees MinIO injoignables depuis l'exterieur)
- Documents: ajout de l'action "Supprimer" (endpoint DELETE deja existant)
- Blog: suppression reelle de l'image de couverture (null explicite bout en bout)
- Blog: le bouton Retour navigateur ferme l'editeur au lieu de quitter vers Logs
- Paiement: bouton Retour revient au contexte precedent (router.back)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace localStorage.getItem('access_token') with getAuthToken() which
also checks sessionStorage — fixes Unauthorized error for users logged
in without 'remember me'
- Add real drag-and-drop support to booking creation page (step 2):
onDragOver/onDragLeave/onDrop handlers on the dropzone container,
isDragging state with visual feedback (blue border + text change),
click-to-browse still works via hidden input ref
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add PENDING_PAYMENT and PENDING_BANK_TRANSFER to document statuses
translations (was displaying raw i18n key instead of human label)
- Fix getStatusLabel/getStatusColor to handle all booking statuses
- Fix select dropdown in Add Document modal to use getStatusLabel()
instead of hardcoded PENDING/ACCEPTED binary
- Add direct "Payer" button (orange) on PENDING_PAYMENT rows in both
desktop table and mobile cards, linking to /dashboard/booking/[id]/pay
- Update detail modal to show payment CTA for PENDING_PAYMENT bookings
- Refactor file handling in document modals to use React state (addFiles,
replaceFile) instead of reading from DOM ref — fixes upload reliability
and shows selected filename feedback in the dropzone
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SIRET/SIREN admin: controller applied siren but skipped siret — add missing updateSiret() call
- Document upload on validated booking: backend blocked PENDING_BANK_TRANSFER;
frontend filter excluded it — both now allow all non-terminal statuses
- Accent encoding in filenames: Multer stores originalname as latin1;
re-decode to utf8 before storing and displaying
- Drag & drop in document modals: replace bare input with styled dropzone
supporting click and native drag-and-drop in both Add and Replace modals
- Resume existing booking: add Actions column with View button and booking
detail modal to the bookings list page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>