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:
NuklearRabbit
2026-08-03 21:37:34 +02:00
co-authored by Claude Sonnet 5
parent 18344bc8b7
commit 6deb95524d
68 changed files with 1734 additions and 225 deletions
+40 -5
View File
@@ -180,13 +180,48 @@
"heading": "Statusconflict oplossen",
"description": "Eén gezaghebbende regel beveelt een gecorrigeerde operationele status voor dit voertuig aan.",
"currentStatus": "Huidige status",
"calculateAndApply": "Aanbevolen status berekenen en toepassen",
"confirmTitle": "Statuswijziging bevestigen",
"confirmBody": "De gezaghebbende aanbevolen status voor dit voertuig toepassen?",
"reviewRecommendation": "Aanbeveling bekijken",
"loadingRecommendation": "Aanbeveling laden",
"recommendationFailed": "Kon geen aanbeveling ophalen.",
"recommendedStatus": "Aanbevolen status",
"whyHeading": "Waarom",
"evidenceHeading": "Feiten",
"consequenceHeading": "Gevolg",
"consequence": {
"statusWillChange": "De voertuigstatus wordt gewijzigd naar {{status}}.",
"issueWillBeRechecked": "Dit kwaliteitsprobleem wordt opnieuw gecontroleerd.",
"changeWillBeAudited": "De wijziging wordt vastgelegd in de audit trail.",
"bookingsNotDeleted": "De boekingen zelf worden niet verwijderd."
},
"changeStatusTo": "Status wijzigen naar {{status}}",
"applying": "Bezig met toepassen…",
"confirmYes": "Ja, toepassen",
"applied": "Toegepast {{status}} — {{reason}}",
"applyFailed": "Kon geen aanbevolen status toepassen."
"applyFailed": "Kon geen aanbevolen status toepassen.",
"staleRecommendation": "De situatie is intussen gewijzigd. Bekijk de aanbeveling opnieuw voor u ze toepast.",
"reviewAgain": "Aanbeveling opnieuw bekijken",
"manualReview": {
"heading": "Handmatige beoordeling vereist",
"body": "De feiten voor dit voertuig spreken elkaar tegen. Een Operations Manager moet dit persoonlijk beoordelen in plaats van een automatische statuswijziging toe te passen.",
"hint": "Gebruik 'Uitstellen' of 'Verwerpen' hieronder, of onderzoek het voertuig en de betrokken boekingen manueel."
},
"noConflict": {
"heading": "Geen wijziging nodig",
"body": "De huidige status van dit voertuig komt al overeen met de feiten."
},
"evidence": {
"activeBookings": "Actieve boeking(en): {{refs}}",
"overlappingBookings": "Overlappende reserveringen: {{pairs}}",
"serviceThresholdReached": "Kilometerstand ({{odometer}} km) heeft de onderhoudsdrempel ({{threshold}} km) bereikt.",
"openOverlapIssue": "Openstaand boekingsoverlapprobleem: {{ref}}"
},
"reasonCodes": {
"vehicle.active_rental": "Dit voertuig heeft een actieve verhuring maar staat niet als verhuurd geregistreerd. De status wordt gecorrigeerd zodat ze de werkelijke situatie weerspiegelt.",
"vehicle.service_threshold_reached": "Dit voertuig heeft de onderhoudsdrempel bereikt. Het wordt naar onderhoud gezet zodat het niet wordt ingezet voordat de service is uitgevoerd.",
"vehicle.booking_conflict": "Dit voertuig heeft twee overlappende reserveringen. Het blokkeren voorkomt dat het opnieuw als inzetbaar wordt beschouwd voordat het boekingsconflict is opgelost.",
"vehicle.rental_ended": "De verhuring voor dit voertuig is beëindigd en er is geen actieve boeking meer. Het wordt terug beschikbaar gezet.",
"vehicle.manual_review_required": "De feiten voor dit voertuig spreken elkaar tegen (bijvoorbeeld een actieve boeking terwijl het voertuig in onderhoud staat, of een onderhoudsdrempel die samenvalt met een actieve verhuring). Dit vereist een menselijke beoordeling in plaats van een automatische statuswijziging.",
"vehicle.no_conflict": "De huidige status van dit voertuig komt al overeen met de feiten. Er is geen wijziging nodig."
}
}
}
}