IT-Stock/app/routers/scan.py

133 lines
4.7 KiB
Python
Raw Permalink Normal View History

"""
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
from app.categories_arbre import aplatir, categories_racines
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()
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
@router.get("/scan")
def page_scan(request: Request, session: Session = Depends(get_session)):
"""Affiche le champ de scan, vide."""
return templates.TemplateResponse(request, "scan.html", _contexte(session))
@router.post("/scan")
def rechercher_par_code_barre(
request: Request, code_barre: str = Form(...), session: Session = Depends(get_session)
):
"""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)
)
return templates.TemplateResponse(
request, "scan.html", _contexte(session, code_barre_introuvable=code_barre)
)
@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))
@router.post("/scan/{materiel_id}/ajuster")
def ajuster_quantite(
request: Request,
materiel_id: int,
quantite: int = Form(...),
sens: str = Form(...),
session: Session = Depends(get_session),
):
"""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é."""
materiel = session.get(Materiel, materiel_id)
if materiel:
delta = quantite if sens == "ajouter" else -quantite
materiel.quantite = max(0, materiel.quantite + delta)
session.add(materiel)
session.commit()
session.refresh(materiel)
verifier_et_alerter(materiel, session)
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))