2026-07-14 16:38:59 +00:00
|
|
|
"""
|
|
|
|
|
Routes pour l'écran de scan à la douchette code-barres.
|
|
|
|
|
|
|
|
|
|
La douchette émule un clavier : elle "tape" le code puis un retour à la
|
|
|
|
|
ligne (touche Entrée). Il suffit donc d'un unique <input> dans un <form> :
|
|
|
|
|
le navigateur soumet automatiquement le formulaire quand Entrée est
|
|
|
|
|
pressée, sans qu'on ait besoin d'écrire de JavaScript spécifique pour
|
|
|
|
|
capter le scan.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, Form
|
|
|
|
|
from fastapi.requests import Request
|
|
|
|
|
from sqlmodel import Session, select
|
|
|
|
|
|
2026-07-14 17:17:27 +00:00
|
|
|
from app.categories_arbre import aplatir, categories_racines
|
2026-07-14 16:38:59 +00:00
|
|
|
from app.database import get_session
|
|
|
|
|
from app.email_alerts import verifier_et_alerter
|
|
|
|
|
from app.models import Materiel
|
|
|
|
|
from app.templates_engine import templates
|
|
|
|
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
|
|
|
|
|
2026-07-14 17:17:27 +00:00
|
|
|
def _contexte(session: Session, **kwargs) -> dict:
|
|
|
|
|
"""Contexte commun à toutes les réponses de l'écran de scan.
|
|
|
|
|
|
|
|
|
|
categories_aplaties est toujours nécessaire : que le matériel scanné
|
|
|
|
|
existe ou non, on peut avoir besoin du <select> de catégorie (menu de
|
|
|
|
|
création rapide si le code est inconnu).
|
|
|
|
|
"""
|
|
|
|
|
contexte = {
|
|
|
|
|
"materiel": None,
|
|
|
|
|
"code_barre_introuvable": None,
|
|
|
|
|
"categories_aplaties": aplatir(categories_racines(session)),
|
|
|
|
|
}
|
|
|
|
|
contexte.update(kwargs)
|
|
|
|
|
return contexte
|
|
|
|
|
|
|
|
|
|
|
2026-07-14 16:38:59 +00:00
|
|
|
@router.get("/scan")
|
2026-07-14 17:17:27 +00:00
|
|
|
def page_scan(request: Request, session: Session = Depends(get_session)):
|
2026-07-14 16:38:59 +00:00
|
|
|
"""Affiche le champ de scan, vide."""
|
2026-07-14 17:17:27 +00:00
|
|
|
return templates.TemplateResponse(request, "scan.html", _contexte(session))
|
2026-07-14 16:38:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/scan")
|
|
|
|
|
def rechercher_par_code_barre(
|
|
|
|
|
request: Request, code_barre: str = Form(...), session: Session = Depends(get_session)
|
|
|
|
|
):
|
2026-07-14 17:17:27 +00:00
|
|
|
"""Cherche le matériel correspondant au code-barre scanné.
|
|
|
|
|
|
|
|
|
|
S'il n'existe pas encore, on ne se contente pas d'un message d'erreur :
|
|
|
|
|
on propose directement le même formulaire de création que sur
|
|
|
|
|
/materiels, avec le code-barre déjà rempli, pour ne pas avoir à quitter
|
|
|
|
|
l'écran de scan (voir code_barre_introuvable dans scan.html).
|
|
|
|
|
"""
|
|
|
|
|
materiel = session.exec(select(Materiel).where(Materiel.code_barre == code_barre)).first()
|
|
|
|
|
if materiel:
|
|
|
|
|
return templates.TemplateResponse(
|
|
|
|
|
request, "scan.html", _contexte(session, materiel=materiel)
|
|
|
|
|
)
|
2026-07-14 16:38:59 +00:00
|
|
|
return templates.TemplateResponse(
|
2026-07-14 17:17:27 +00:00
|
|
|
request, "scan.html", _contexte(session, code_barre_introuvable=code_barre)
|
2026-07-14 16:38:59 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-07-14 17:17:27 +00:00
|
|
|
@router.post("/scan/creer")
|
|
|
|
|
def creer_depuis_scan(
|
|
|
|
|
request: Request,
|
|
|
|
|
nom: str = Form(...),
|
|
|
|
|
code_barre: str = Form(""),
|
|
|
|
|
categorie_id: int = Form(...),
|
|
|
|
|
quantite: int = Form(0),
|
|
|
|
|
seuil_alerte: str = Form(""),
|
|
|
|
|
session: Session = Depends(get_session),
|
|
|
|
|
):
|
|
|
|
|
"""Crée le matériel correspondant à un code-barre scanné qui n'existait
|
|
|
|
|
pas encore, puis affiche directement son étiquette (comme s'il venait
|
|
|
|
|
d'être scanné avec succès) — sans repasser par /materiels."""
|
|
|
|
|
materiel = Materiel(
|
|
|
|
|
nom=nom,
|
|
|
|
|
code_barre=code_barre or None,
|
|
|
|
|
categorie_id=categorie_id,
|
|
|
|
|
quantite=quantite,
|
|
|
|
|
seuil_alerte=int(seuil_alerte) if seuil_alerte else None,
|
|
|
|
|
)
|
|
|
|
|
session.add(materiel)
|
|
|
|
|
session.commit()
|
|
|
|
|
session.refresh(materiel)
|
|
|
|
|
verifier_et_alerter(materiel, session)
|
|
|
|
|
return templates.TemplateResponse(request, "scan.html", _contexte(session, materiel=materiel))
|
|
|
|
|
|
|
|
|
|
|
2026-07-14 16:38:59 +00:00
|
|
|
@router.post("/scan/{materiel_id}/ajuster")
|
|
|
|
|
def ajuster_quantite(
|
|
|
|
|
request: Request,
|
|
|
|
|
materiel_id: int,
|
2026-07-14 17:17:27 +00:00
|
|
|
quantite: int = Form(...),
|
|
|
|
|
sens: str = Form(...),
|
2026-07-14 16:38:59 +00:00
|
|
|
session: Session = Depends(get_session),
|
|
|
|
|
):
|
2026-07-14 17:17:27 +00:00
|
|
|
"""Ajuste la quantité en stock d'un nombre choisi librement par
|
|
|
|
|
l'utilisateur (pas un pas fixe de ±1) : "sens" indique si on ajoute
|
|
|
|
|
(réception) ou retire (sortie) cette quantité."""
|
2026-07-14 16:38:59 +00:00
|
|
|
materiel = session.get(Materiel, materiel_id)
|
|
|
|
|
if materiel:
|
2026-07-14 17:17:27 +00:00
|
|
|
delta = quantite if sens == "ajouter" else -quantite
|
2026-07-14 16:38:59 +00:00
|
|
|
materiel.quantite = max(0, materiel.quantite + delta)
|
|
|
|
|
session.add(materiel)
|
|
|
|
|
session.commit()
|
|
|
|
|
session.refresh(materiel)
|
|
|
|
|
verifier_et_alerter(materiel, session)
|
2026-07-14 17:17:27 +00:00
|
|
|
return templates.TemplateResponse(request, "scan.html", _contexte(session, materiel=materiel))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/scan/{materiel_id}/definir")
|
|
|
|
|
def definir_quantite(
|
|
|
|
|
request: Request,
|
|
|
|
|
materiel_id: int,
|
|
|
|
|
quantite: int = Form(...),
|
|
|
|
|
session: Session = Depends(get_session),
|
|
|
|
|
):
|
|
|
|
|
"""Fixe directement la quantité en stock à une valeur donnée, pour
|
|
|
|
|
corriger un écart d'inventaire sans avoir à calculer un delta."""
|
|
|
|
|
materiel = session.get(Materiel, materiel_id)
|
|
|
|
|
if materiel:
|
|
|
|
|
materiel.quantite = max(0, quantite)
|
|
|
|
|
session.add(materiel)
|
|
|
|
|
session.commit()
|
|
|
|
|
session.refresh(materiel)
|
|
|
|
|
verifier_et_alerter(materiel, session)
|
|
|
|
|
return templates.TemplateResponse(request, "scan.html", _contexte(session, materiel=materiel))
|