fix: safe status-recommendation flow, MO-016 order independence, brand constant, message codes
- Add a single shared, pure vehicle-status evaluator (app/services/vehicle_status.py)
used identically by the data-quality scanner, a new non-mutating status-recommendation
preview endpoint, and a transactional apply endpoint with optimistic-concurrency token
revalidation -- eliminates the old opaque "calculate and apply" action and the unsafe
"maintenance + active booking -> auto rented" shortcut. Frontend
DataQualityIssueDetail.tsx now shows a review/decide/confirm panel with localized
why/evidence/consequence text in nl-BE/en-GB/fr-BE, with an exact "Change status to
<status>" confirm action per the brief.
- Fix MO-016 issue-order dependency: resolving the booking-overlap issue before vs.
after the status-conflict issue now converges on the same final vehicle status,
proven by test_mo_016_status_conflict_recommendation_is_order_independent.
- Make "Fleet Ops" a non-localizable brand constant (frontend/src/product.ts,
backend PRODUCT_NAME) via {{productName}} interpolation everywhere the brand name
appeared in locale prose; add a permanent test guarding against a translation file
ever defining the brand name or an "appName" key again.
- Convert dynamic backend prose to stable message codes + params: return status
reasons, audit field/actor-type labels, automation last_error, and search
section/vehicle/booking/issue results all now carry codes the frontend localizes,
with raw technical text demoted to a "Technical details" disclosure.
- docs/fleet-ops-correction/: gap audit, i18n inventory, and the vehicle-status
decision table documenting the evaluator's rules and safe-status principles.
148 backend tests + Ruff + mypy green; Alembic migration verified upgrade/downgrade;
frontend tsc/build and the i18n-coverage Playwright suite green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
18344bc8b7
commit
6deb95524d
@@ -180,13 +180,48 @@
|
||||
"heading": "Résoudre le conflit de statut",
|
||||
"description": "Une règle faisant autorité recommande un statut opérationnel corrigé pour ce véhicule.",
|
||||
"currentStatus": "Statut actuel",
|
||||
"calculateAndApply": "Calculer et appliquer le statut recommandé",
|
||||
"confirmTitle": "Confirmer le changement de statut",
|
||||
"confirmBody": "Appliquer le statut recommandé faisant autorité pour ce véhicule ?",
|
||||
"reviewRecommendation": "Voir la recommandation",
|
||||
"loadingRecommendation": "Chargement de la recommandation…",
|
||||
"recommendationFailed": "Impossible de charger une recommandation.",
|
||||
"recommendedStatus": "Statut recommandé",
|
||||
"whyHeading": "Pourquoi",
|
||||
"evidenceHeading": "Faits",
|
||||
"consequenceHeading": "Conséquence",
|
||||
"consequence": {
|
||||
"statusWillChange": "Le statut du véhicule sera changé en {{status}}.",
|
||||
"issueWillBeRechecked": "Ce problème de qualité sera à nouveau contrôlé.",
|
||||
"changeWillBeAudited": "Le changement sera enregistré dans la piste d'audit.",
|
||||
"bookingsNotDeleted": "Les réservations elles-mêmes ne sont pas supprimées."
|
||||
},
|
||||
"changeStatusTo": "Changer le statut vers {{status}}",
|
||||
"applying": "Application en cours…",
|
||||
"confirmYes": "Oui, appliquer",
|
||||
"applied": "Appliqué {{status}} — {{reason}}",
|
||||
"applyFailed": "Impossible d'appliquer un statut recommandé."
|
||||
"applyFailed": "Impossible d'appliquer un statut recommandé.",
|
||||
"staleRecommendation": "La situation a changé depuis l'affichage de cette recommandation. Consultez-la à nouveau avant de l'appliquer.",
|
||||
"reviewAgain": "Revoir la recommandation",
|
||||
"manualReview": {
|
||||
"heading": "Évaluation manuelle requise",
|
||||
"body": "Les faits concernant ce véhicule se contredisent. Un Operations Manager doit évaluer la situation en personne plutôt que d'appliquer un changement de statut automatique.",
|
||||
"hint": "Utilisez « Reporter » ou « Rejeter » ci-dessous, ou examinez manuellement le véhicule et les réservations concernées."
|
||||
},
|
||||
"noConflict": {
|
||||
"heading": "Aucun changement nécessaire",
|
||||
"body": "Le statut actuel de ce véhicule correspond déjà aux faits."
|
||||
},
|
||||
"evidence": {
|
||||
"activeBookings": "Réservation(s) active(s) : {{refs}}",
|
||||
"overlappingBookings": "Réservations qui se chevauchent : {{pairs}}",
|
||||
"serviceThresholdReached": "Le kilométrage ({{odometer}} km) a atteint le seuil d'entretien ({{threshold}} km).",
|
||||
"openOverlapIssue": "Problème de chevauchement de réservation ouvert : {{ref}}"
|
||||
},
|
||||
"reasonCodes": {
|
||||
"vehicle.active_rental": "Ce véhicule a une location active mais n'est pas enregistré comme loué. Le statut est corrigé pour refléter la situation réelle.",
|
||||
"vehicle.service_threshold_reached": "Ce véhicule a atteint son seuil d'entretien. Il est placé en entretien afin qu'il ne soit pas utilisé avant que la révision ait eu lieu.",
|
||||
"vehicle.booking_conflict": "Ce véhicule a deux réservations qui se chevauchent. Le bloquer évite qu'il soit à nouveau considéré comme disponible avant que le conflit de réservation ne soit résolu.",
|
||||
"vehicle.rental_ended": "La location de ce véhicule est terminée et il n'y a plus de réservation active. Il est remis en disponible.",
|
||||
"vehicle.manual_review_required": "Les faits concernant ce véhicule se contredisent (par exemple, une réservation active alors que le véhicule est en entretien, ou un seuil d'entretien atteint en même temps qu'une location active). Cela nécessite une évaluation humaine plutôt qu'un changement de statut automatique.",
|
||||
"vehicle.no_conflict": "Le statut actuel de ce véhicule correspond déjà aux faits. Aucun changement n'est nécessaire."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user