2026-07-14 16:38:59 +00:00
|
|
|
|
{% extends "base.html" %}
|
|
|
|
|
|
{% block titre %}Scanner - Gestion de stock IT{% endblock %}
|
|
|
|
|
|
{% block contenu %}
|
2026-07-14 17:06:37 +00:00
|
|
|
|
<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>
|
|
|
|
|
|
|
2026-07-14 17:17:27 +00:00
|
|
|
|
{% 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>
|
2026-07-14 17:06:37 +00:00
|
|
|
|
{% 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 %}
|
|
|
|
|
|
|
2026-07-14 17:17:27 +00:00
|
|
|
|
{#
|
|
|
|
|
|
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>
|
2026-07-14 17:06:37 +00:00
|
|
|
|
</form>
|
2026-07-14 17:17:27 +00:00
|
|
|
|
|
|
|
|
|
|
<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>
|
2026-07-14 17:06:37 +00:00
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
{% endif %}
|
2026-07-14 16:38:59 +00:00
|
|
|
|
</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 %}
|