Un code-barre inconnu scanné propose désormais le même formulaire de création que /materiels au lieu d'un simple message d'erreur. La quantité peut être ajustée d'un delta choisi librement (pas figé à ±1) ou définie directement à une valeur absolue après un inventaire. La racine "/" sert directement la liste des matériels sans redirection. La page Matériels gagne un filtre par catégorie, un filtre stock bas, et un tri incluant un regroupement par catégorie.
127 lines
5 KiB
HTML
127 lines
5 KiB
HTML
{% extends "base.html" %}
|
||
{% block titre %}Scanner - Gestion de stock IT{% endblock %}
|
||
{% block contenu %}
|
||
<section class="scanner">
|
||
<header class="scanner-entete">
|
||
<p class="scanner-eyebrow">Poste de scan</p>
|
||
<h1>Identifier un matériel</h1>
|
||
</header>
|
||
|
||
{#
|
||
Un seul champ dans un <form> : la douchette tape le code puis Entrée,
|
||
ce qui soumet le formulaire nativement (aucun JavaScript nécessaire
|
||
pour capter le scan). La "ligne laser" sous le champ ne s'anime que
|
||
lorsqu'il a le focus (voir .scanner-champ:focus + .laser-ligne dans
|
||
style.css) : un clin d'œil au faisceau de la douchette physique.
|
||
#}
|
||
<form method="post" action="/scan" class="scanner-formulaire">
|
||
<label for="champ-scan" class="scanner-label">Code-barre</label>
|
||
<input
|
||
type="text"
|
||
name="code_barre"
|
||
id="champ-scan"
|
||
class="scanner-champ"
|
||
placeholder="En attente de scan…"
|
||
autocomplete="off"
|
||
autofocus
|
||
required
|
||
>
|
||
<div class="laser-ligne" aria-hidden="true"></div>
|
||
</form>
|
||
|
||
{% if code_barre_introuvable %}
|
||
{#
|
||
Code scanné mais aucun matériel ne le porte : au lieu d'un simple
|
||
message d'erreur, on propose le même formulaire de création rapide que
|
||
sur /materiels, avec le code-barre déjà rempli, pour ne pas avoir à
|
||
quitter l'écran de scan.
|
||
#}
|
||
<article class="etiquette-resultat etiquette-creation">
|
||
<div class="rayures-code-barre etiquette-rayures" aria-hidden="true"></div>
|
||
<p class="etiquette-fil">Code inconnu</p>
|
||
<h2 class="etiquette-nom">« {{ code_barre_introuvable }} »</h2>
|
||
|
||
{% if categories_aplaties %}
|
||
<form method="post" action="/scan/creer" class="formulaire-grille etiquette-formulaire">
|
||
<input type="hidden" name="code_barre" value="{{ code_barre_introuvable }}">
|
||
<label>Nom <input type="text" name="nom" required autofocus></label>
|
||
<label>Catégorie
|
||
<select name="categorie_id" required>
|
||
{% for profondeur, categorie in categories_aplaties %}
|
||
<option value="{{ categorie.id }}">{{ "— " * profondeur }}{{ categorie.nom }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</label>
|
||
<label>Quantité initiale <input type="number" name="quantite" value="0" min="0"></label>
|
||
<label>Seuil d'alerte <input type="number" name="seuil_alerte" min="0"></label>
|
||
<button type="submit">Créer et ajouter au stock</button>
|
||
</form>
|
||
{% else %}
|
||
<p class="etiquette-sous-titre">
|
||
Créez d'abord une <a href="/categories">catégorie</a> avant de pouvoir créer ce matériel.
|
||
</p>
|
||
{% endif %}
|
||
</article>
|
||
{% endif %}
|
||
|
||
{% if materiel %}
|
||
{% set sous_seuil = materiel.seuil_alerte is not none and materiel.quantite < materiel.seuil_alerte %}
|
||
<article class="etiquette-resultat">
|
||
<div class="rayures-code-barre etiquette-rayures" aria-hidden="true"></div>
|
||
|
||
<p class="etiquette-fil">{{ materiel.categorie.chemin() }}</p>
|
||
<h2 class="etiquette-nom">{{ materiel.nom }}</h2>
|
||
|
||
<div class="etiquette-quantite">
|
||
<span class="etiquette-nombre">{{ materiel.quantite }}</span>
|
||
<span class="etiquette-unite">en stock</span>
|
||
</div>
|
||
|
||
{% if materiel.seuil_alerte is not none %}
|
||
<p class="etiquette-statut">
|
||
<span class="statut-puce {{ 'statut-bas' if sous_seuil else 'statut-ok' }}"></span>
|
||
{% if sous_seuil %}
|
||
Sous le seuil d'alerte ({{ materiel.seuil_alerte }})
|
||
{% else %}
|
||
Seuil d'alerte : {{ materiel.seuil_alerte }}
|
||
{% endif %}
|
||
</p>
|
||
{% endif %}
|
||
|
||
{#
|
||
Deux façons distinctes d'agir sur le stock, pour ne pas mélanger les
|
||
deux gestes : "ajuster" applique un delta (une quantité reçue ou
|
||
sortie, pas forcément 1) ; "définir" corrige directement le total
|
||
(utile après un inventaire physique, sans calculer d'écart).
|
||
#}
|
||
<div class="etiquette-controles">
|
||
<form method="post" action="/scan/{{ materiel.id }}/ajuster" class="formulaire-ajustement">
|
||
<label class="etiquette-champ-quantite">
|
||
Quantité à ajuster
|
||
<input type="number" name="quantite" value="1" min="1" required>
|
||
</label>
|
||
<div class="etiquette-actions">
|
||
<button type="submit" name="sens" value="retirer" class="bouton-scan bouton-sortie">− Retirer</button>
|
||
<button type="submit" name="sens" value="ajouter" class="bouton-scan bouton-reception">+ Ajouter</button>
|
||
</div>
|
||
</form>
|
||
|
||
<form method="post" action="/scan/{{ materiel.id }}/definir" class="formulaire-definir">
|
||
<label class="etiquette-champ-quantite">
|
||
Définir le stock à
|
||
<input type="number" name="quantite" value="{{ materiel.quantite }}" min="0" required>
|
||
</label>
|
||
<button type="submit">Enregistrer</button>
|
||
</form>
|
||
</div>
|
||
</article>
|
||
{% endif %}
|
||
</section>
|
||
|
||
<script>
|
||
// Remet le focus sur le champ de scan à chaque affichage de la page,
|
||
// pour pouvoir enchaîner les scans sans avoir à cliquer dans le champ.
|
||
document.getElementById("champ-scan").focus();
|
||
</script>
|
||
{% endblock %}
|