- 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>
35 lines
1.7 KiB
Python
35 lines
1.7 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import DateTime, Integer, String, Text
|
|
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.core.db import Base
|
|
from app.models.mixins import TimestampMixin
|
|
|
|
DELIVERY_STATUSES = ("pending", "delivering", "succeeded", "failed")
|
|
|
|
|
|
class OutboxEvent(TimestampMixin, Base):
|
|
__tablename__ = "outbox_events"
|
|
|
|
event_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
event_type: Mapped[str] = mapped_column(String(60), nullable=False)
|
|
aggregate_type: Mapped[str] = mapped_column(String(30), nullable=False)
|
|
aggregate_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False)
|
|
payload_json: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
|
occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
delivery_status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending")
|
|
attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
next_attempt_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
last_error: Mapped[str | None] = mapped_column(Text)
|
|
# Stable, localizable classification of last_error -- the frontend renders a
|
|
# localized summary from this code as the primary text and shows last_error itself
|
|
# only under "Technical details" (section 10 of docs/fleet-ops-correction/
|
|
# current-gap-audit.md). Kept alongside the raw message for backward compatibility.
|
|
last_error_code: Mapped[str | None] = mapped_column(String(60))
|
|
external_run_id: Mapped[str | None] = mapped_column(String(120))
|