feat: endpoint pratiche rendicontazione (lato beneficiario)
- 4 nuove tabelle: remission_practice, remission_invoice, remission_ula_employee, remission_document
con cascade delete e FK
- 13 endpoint /api/remission-practices/*:
GET /mine (lista pratiche user + applications CONTRACT_SIGNED ready_to_start)
POST /start (avvia pratica da application_id, richiede schema PUBLISHED)
GET /{id}, PUT /{id} (regime IVA + note)
POST/DELETE /{id}/invoices
POST/DELETE /{id}/ula-employees
PUT/DELETE /{id}/documents/{doc_code}
GET /{id}/gate-check (valida gate rules contro pratica, ritorna totali + checks)
POST /{id}/submit (gate-check obbligatorio, status DRAFT -> SUBMITTED)
- 1 endpoint debug /api/debug/impersonate (sandbox-only, genera JWT per utente
- necessario perche' /v1/user/login del BE Spring esclude ROLE_BENEFICIARY)
- Gate check calcola: totali per categoria, grand_total, max_remission = min(cap_pct*erogato, cap_abs),
remission_due = min(grand_total, max_remission), applica iva_ordinario_imponibile_only
This commit is contained in:
67
app/routers/debug.py
Normal file
67
app/routers/debug.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
Endpoint di debug SOLO per sandbox. Permette a un superadmin di impersonare
|
||||
un utente (tipicamente un beneficiario) per testare i flussi senza SPID.
|
||||
Non deve essere mai abilitato in produzione.
|
||||
"""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import text
|
||||
from jose import jwt
|
||||
|
||||
from ..db import get_db
|
||||
from ..config import get_settings
|
||||
from ..auth import AuthUser, require_superadmin
|
||||
from ..schemas import ApiResponse
|
||||
|
||||
router = APIRouter(prefix="/api/debug", tags=["debug"])
|
||||
settings = get_settings()
|
||||
|
||||
|
||||
class ImpersonateRequest(BaseModel):
|
||||
email: str
|
||||
|
||||
|
||||
@router.post("/impersonate", response_model=ApiResponse)
|
||||
def impersonate(body: ImpersonateRequest,
|
||||
db: Session = Depends(get_db),
|
||||
admin: AuthUser = Depends(require_superadmin)):
|
||||
"""
|
||||
Genera un token JWT valido per l'email indicata. Solo sandbox dev,
|
||||
richiede ruolo superadmin per chiamarlo.
|
||||
"""
|
||||
row = db.execute(text("""
|
||||
SELECT u.id, u.email, u.first_name, u.last_name, u.hub_id,
|
||||
r.role_type
|
||||
FROM gepafin_schema.gepafin_user u
|
||||
JOIN gepafin_schema.role r ON r.id = u.role_id
|
||||
WHERE u.email = :email AND u.is_deleted = false
|
||||
"""), {"email": body.email}).mappings().first()
|
||||
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail=f"Utente {body.email} non trovato")
|
||||
|
||||
# Genero token con lo stesso formato del BE Spring
|
||||
exp = datetime.now(timezone.utc) + timedelta(hours=8)
|
||||
payload = {
|
||||
"sub": f"{row['email']}:{row['id']}:{row['hub_id']}",
|
||||
"userId": row["id"],
|
||||
"auth": row["role_type"],
|
||||
"exp": int(exp.timestamp()),
|
||||
}
|
||||
token = jwt.encode(payload, settings.jwt_secret, algorithm=settings.jwt_algorithm)
|
||||
|
||||
return ApiResponse(
|
||||
message=f"Token generato per {body.email}",
|
||||
data={
|
||||
"token": token,
|
||||
"user": {
|
||||
"id": row["id"],
|
||||
"email": row["email"],
|
||||
"firstName": row["first_name"],
|
||||
"lastName": row["last_name"],
|
||||
"role": {"roleType": row["role_type"]}
|
||||
}
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user