feat(backend): sync endpoints + all models + seed + order workflow
- All business models: Vehicle, Order, OrderLine, Invoice, Appointment, CatalogMarca/Model/Ansamblu/Norma/Pret/TipDeviz/TipMotor, Mecanic - Sync endpoints: GET /sync/full, GET /sync/changes?since=, POST /sync/push with tenant isolation and last-write-wins conflict resolution - Order CRUD with state machine: DRAFT -> VALIDAT -> FACTURAT Auto-recalculates totals (manopera + materiale) - Vehicle CRUD: list, create, get, update - Seed data: 24 marci, 11 ansamble, 6 tipuri deviz, 5 tipuri motoare, 3 preturi - Alembic migration for all business models - 13 passing tests (auth + sync + orders) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
0
backend/app/vehicles/__init__.py
Normal file
0
backend/app/vehicles/__init__.py
Normal file
110
backend/app/vehicles/router.py
Normal file
110
backend/app/vehicles/router.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.base import uuid7
|
||||
from app.db.models.vehicle import Vehicle
|
||||
from app.db.session import get_db
|
||||
from app.deps import get_tenant_id
|
||||
from app.vehicles import schemas
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_vehicles(
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
r = await db.execute(
|
||||
select(Vehicle).where(Vehicle.tenant_id == tenant_id)
|
||||
)
|
||||
vehicles = r.scalars().all()
|
||||
return [
|
||||
{
|
||||
"id": v.id,
|
||||
"nr_auto": v.nr_inmatriculare,
|
||||
"marca_id": v.marca_id,
|
||||
"model_id": v.model_id,
|
||||
"an": v.an_fabricatie,
|
||||
"client_nume": v.client_nume,
|
||||
}
|
||||
for v in vehicles
|
||||
]
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_vehicle(
|
||||
data: schemas.CreateVehicleRequest,
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
vehicle = Vehicle(
|
||||
id=uuid7(),
|
||||
tenant_id=tenant_id,
|
||||
nr_inmatriculare=data.nr_auto,
|
||||
marca_id=data.marca_id,
|
||||
model_id=data.model_id,
|
||||
an_fabricatie=data.an_fabricatie,
|
||||
vin=data.vin,
|
||||
client_nume=data.proprietar_nume,
|
||||
client_telefon=data.proprietar_telefon,
|
||||
)
|
||||
db.add(vehicle)
|
||||
await db.commit()
|
||||
return {"id": vehicle.id}
|
||||
|
||||
|
||||
@router.get("/{vehicle_id}")
|
||||
async def get_vehicle(
|
||||
vehicle_id: str,
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
r = await db.execute(
|
||||
select(Vehicle).where(
|
||||
Vehicle.id == vehicle_id, Vehicle.tenant_id == tenant_id
|
||||
)
|
||||
)
|
||||
v = r.scalar_one_or_none()
|
||||
if not v:
|
||||
raise HTTPException(status_code=404, detail="Vehicle not found")
|
||||
return {
|
||||
"id": v.id,
|
||||
"nr_auto": v.nr_inmatriculare,
|
||||
"marca_id": v.marca_id,
|
||||
"model_id": v.model_id,
|
||||
"an": v.an_fabricatie,
|
||||
"vin": v.vin,
|
||||
"client_nume": v.client_nume,
|
||||
"client_telefon": v.client_telefon,
|
||||
"client_email": v.client_email,
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{vehicle_id}")
|
||||
async def update_vehicle(
|
||||
vehicle_id: str,
|
||||
data: schemas.UpdateVehicleRequest,
|
||||
tenant_id: str = Depends(get_tenant_id),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
r = await db.execute(
|
||||
select(Vehicle).where(
|
||||
Vehicle.id == vehicle_id, Vehicle.tenant_id == tenant_id
|
||||
)
|
||||
)
|
||||
v = r.scalar_one_or_none()
|
||||
if not v:
|
||||
raise HTTPException(status_code=404, detail="Vehicle not found")
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
if "nr_auto" in update_data:
|
||||
update_data["nr_inmatriculare"] = update_data.pop("nr_auto")
|
||||
if "proprietar_nume" in update_data:
|
||||
update_data["client_nume"] = update_data.pop("proprietar_nume")
|
||||
if "proprietar_telefon" in update_data:
|
||||
update_data["client_telefon"] = update_data.pop("proprietar_telefon")
|
||||
for key, value in update_data.items():
|
||||
setattr(v, key, value)
|
||||
await db.commit()
|
||||
return {"ok": True}
|
||||
21
backend/app/vehicles/schemas.py
Normal file
21
backend/app/vehicles/schemas.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class CreateVehicleRequest(BaseModel):
|
||||
nr_auto: str
|
||||
marca_id: str | None = None
|
||||
model_id: str | None = None
|
||||
an_fabricatie: int | None = None
|
||||
vin: str | None = None
|
||||
proprietar_nume: str | None = None
|
||||
proprietar_telefon: str | None = None
|
||||
|
||||
|
||||
class UpdateVehicleRequest(BaseModel):
|
||||
nr_auto: str | None = None
|
||||
marca_id: str | None = None
|
||||
model_id: str | None = None
|
||||
an_fabricatie: int | None = None
|
||||
vin: str | None = None
|
||||
proprietar_nume: str | None = None
|
||||
proprietar_telefon: str | None = None
|
||||
Reference in New Issue
Block a user