2026-08-14 18:02:25 +02:00
{% extends "base.html" %}
{% block title %}{{ 'Nouvel équipement' if not equipment else 'Modifier l\'équipement' }} — GMAO{% endblock %}
{% block content %}
< div class = "container-fluid" >
< div class = "row mb-4" >
< div class = "col" >
< h1 class = "h3" >
< i class = "fas fa-tools me-2" > < / i >
{{ 'Nouvel équipement' if not equipment else 'Modifier l\'équipement' }}
< / h1 >
< / div >
< / div >
< div class = "row" >
< div class = "col-lg-8" >
< div class = "card" >
< div class = "card-body" >
<!-- Étape 1: Choix du type (uniquement pour création) -->
{% if not equipment %}
< div class = "mb-4" id = "type-selector" >
< label class = "form-label fw-bold" > Type d'équipement< / label >
< div class = "row g-3" >
< div class = "col-md-4" >
< div class = "card h-100 type-card" data-type = "parent" style = "cursor: pointer; transition: all 0.2s;" >
< div class = "card-body text-center" >
< i class = "bi bi-collection fs-1 text-primary" > < / i >
< h5 class = "mt-2" > Groupe parent< / h5 >
< small class = "text-muted" > Ex: Lot de 60 chaises< / small >
< / div >
< / div >
< / div >
< div class = "col-md-4" >
< div class = "card h-100 type-card" data-type = "child" style = "cursor: pointer; transition: all 0.2s;" >
< div class = "card-body text-center" >
< i class = "bi bi-diagram-3 fs-1 text-success" > < / i >
< h5 class = "mt-2" > Groupe enfant< / h5 >
< small class = "text-muted" > Ex: 30 chaises salle B101< / small >
< / div >
< / div >
< / div >
< div class = "col-md-4" >
< div class = "card h-100 type-card" data-type = "individual" style = "cursor: pointer; transition: all 0.2s;" >
< div class = "card-body text-center" >
< i class = "bi bi-box fs-1 text-warning" > < / i >
< h5 class = "mt-2" > Individuel< / h5 >
< small class = "text-muted" > Ex: Vidéo-projecteur #1< / small >
< / div >
< / div >
< / div >
< / div >
< / div >
{% endif %}
< form method = "POST" id = "equipment-form" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< input type = "hidden" name = "is_group" id = "is_group_input" value = "{{ '1' if equipment and equipment.is_group else '0' }}" >
< input type = "hidden" name = "parent_id" id = "parent_id_input" value = "{{ equipment.parent_id if equipment else '' }}" >
<!-- Champs communs -->
< div class = "row mb-3" >
< div class = "col-md-6" >
< label for = "name" class = "form-label" > Nom *< / label >
< input type = "text" class = "form-control" id = "name" name = "name"
value="{{ equipment.name if equipment else '' }}" required>
< / div >
< div class = "col-md-6" id = "code_field" >
{% if not equipment or equipment.is_individual %}
< label for = "code" class = "form-label" > Code / Numéro de série< / label >
< input type = "text" class = "form-control" id = "code" name = "code"
value="{{ equipment.code if equipment else '' }}"
placeholder="Ex: CHAISE-001">
< small class = "text-muted" > Uniquement pour les éléments individuels< / small >
{% else %}
< label class = "form-label" > Code< / label >
< input type = "text" class = "form-control" value = "Non applicable (groupe)" disabled >
< small class = "text-muted" > Les groupes n'ont pas de numéro de série< / small >
{% endif %}
< / div >
< / div >
< div class = "row mb-3" >
< div class = "col-md-6" >
< label for = "category_id" class = "form-label" > Catégorie< / label >
< select class = "form-select" id = "category_id" name = "category_id" >
< option value = "" > -- Sélectionner --< / option >
{% for cat in categories %}
< option value = "{{ cat.id }}" { { ' selected ' if equipment and equipment . category_id = = cat . id else ' ' } } >
{{ cat.name }}
< / option >
{% endfor %}
< / select >
< / div >
< div class = "col-md-6" id = "room_field" >
< label for = "room_id" class = "form-label" > Salle < span id = "room_required" > *< / span > < / label >
< select class = "form-select" id = "room_id" name = "room_id" >
< option value = "" > -- Sélectionner --< / option >
{% for building in buildings %}
< optgroup label = "{{ building.name }}" >
{% for room in building.rooms %}
< option value = "{{ room.id }}" { { ' selected ' if equipment and equipment . room_id = = room . id else ' ' } } >
{{ room.name }}
< / option >
{% endfor %}
< / optgroup >
{% endfor %}
< / select >
< small class = "text-muted" id = "room_hint" > Obligatoire pour les éléments individuels< / small >
< / div >
< / div >
< div class = "row mb-3" >
< div class = "col-md-6" >
< label for = "lot_id" class = "form-label" > Lot< / label >
< select class = "form-select" id = "lot_id" name = "lot_id" >
< option value = "" > -- Aucun lot --< / option >
{% for lot in lots %}
2026-08-15 01:37:17 +02:00
< option value = "{{ lot.id }}" data-category-id = "{{ lot.category_id or '' }}" { { ' selected ' if equipment and equipment . lot_id = = lot . id else ' ' } } >
2026-08-14 18:02:25 +02:00
{{ lot.name }}
< / option >
{% endfor %}
< / select >
2026-08-15 01:37:17 +02:00
< button type = "button" class = "btn btn-sm btn-outline-primary mt-2" id = "quick-add-lot" disabled > < i class = "bi bi-plus-lg" > < / i > Créer un lot dans cette catégorie< / button >
2026-08-14 18:02:25 +02:00
< small class = "text-muted" > Associe les tâches préventives du lot à l'équipement< / small >
< / div >
< div class = "col-md-6" >
< label for = "status" class = "form-label" > Statut< / label >
< select class = "form-select" id = "status" name = "status" >
< option value = "en_service" { { ' selected ' if equipment and equipment . status = = ' en_service ' else ' ' } } > En service< / option >
< option value = "en_panne" { { ' selected ' if equipment and equipment . status = = ' en_panne ' else ' ' } } > En panne< / option >
< option value = "en_maintenance" { { ' selected ' if equipment and equipment . status = = ' en_maintenance ' else ' ' } } > En maintenance< / option >
< option value = "hs" { { ' selected ' if equipment and equipment . status = = ' hs ' else ' ' } } > Hors service< / option >
< / select >
< / div >
< div class = "col-md-6" id = "quantity_field" >
< label for = "quantity" class = "form-label" > Quantité< / label >
< input type = "number" class = "form-control" id = "quantity" name = "quantity"
value="{{ equipment.quantity if equipment else 1 }}" min="1">
< small class = "text-muted" > Nombre d'unités dans le groupe< / small >
< / div >
< / div >
< div class = "row mb-3" id = "parent_selector" style = "display: none;" >
< div class = "col-md-12" >
< label for = "parent_select" class = "form-label" > Équipement parent *< / label >
< select class = "form-select" id = "parent_select" name = "parent_select" >
< option value = "" > -- Sélectionner un parent --< / option >
{% for eq in parent_equipments %}
< option value = "{{ eq.id }}" data-quantity = "{{ eq.quantity }}" data-category = "{{ eq.category_id }}" { { ' selected ' if preselected_parent_id = = eq . id | string else ' ' } } >
{{ eq.name }} ({{ eq.quantity }} unités)
< / option >
{% endfor %}
< / select >
< small class = "text-muted" > L'enfant héritera de la catégorie du parent< / small >
< / div >
< / div >
2026-08-14 21:16:01 +02:00
< div class = "card mb-3" >
< div class = "card-header" > < i class = "bi bi-wrench-adjustable" > < / i > Suivi technique et mobilité< / div >
< div class = "card-body row g-3" >
< div class = "col-md-4" > < label class = "form-label" for = "mobility" > Mobilité< / label > < select class = "form-select" id = "mobility" name = "mobility" > < option value = "non_precise" > À préciser< / option > < option value = "mobile" > Mobile< / option > < option value = "fixe" > Fixe< / option > < / select > < / div >
2026-08-20 00:49:15 +02:00
< div class = "col-md-8" > < label class = "form-label" for = "management_mode" > Mode de gestion< / label > < select class = "form-select" id = "management_mode" name = "management_mode" > < option value = "" > Hériter de la catégorie< / option > < option value = "quantitatif" > Quantitatif (chaises, tables…)< / option > < option value = "individuel_fixe" > Individuel fixe (prise, luminaire…)< / option > < option value = "individuel_mobile" > Individuel mobile (outil, ordinateur…)< / option > < option value = "serialise" > Sérialisé avec compteur/contrat< / option > < option value = "structure" > Élément de bâtiment (mur, sol…)< / option > < option value = "consommable" > Consommable / stock< / option > < / select > < / div >
2026-08-14 21:16:01 +02:00
< div class = "col-md-4" > < label class = "form-label" for = "serial_number" > Numéro de série< / label > < input class = "form-control" id = "serial_number" name = "serial_number" > < / div >
< div class = "col-md-4" > < label class = "form-label" for = "manufacturer" > Fabricant< / label > < input class = "form-control" id = "manufacturer" name = "manufacturer" > < / div >
< div class = "col-md-4" > < label class = "form-label" for = "model_reference" > Modèle< / label > < input class = "form-control" id = "model_reference" name = "model_reference" > < / div >
< div class = "col-md-4" > < label class = "form-label" for = "supplier_id" > Fournisseur< / label > < select class = "form-select" id = "supplier_id" name = "supplier_id" > < option value = "" > —< / option > {% for company in companies %}< option value = "{{ company.id }}" > {{ company.name }}< / option > {% endfor %}< / select > < / div >
< div class = "col-md-4" > < label class = "form-label" for = "supplier_reference" > Référence fournisseur< / label > < input class = "form-control" id = "supplier_reference" name = "supplier_reference" > < / div >
< div class = "col-md-4" > < label class = "form-label" for = "purchase_price" > Prix d'achat informatif< / label > < input class = "form-control" id = "purchase_price" name = "purchase_price" type = "number" min = "0" step = "0.01" > < / div >
< div class = "col-md-4" > < label class = "form-label" for = "purchase_date" > Date d'achat< / label > < input class = "form-control" id = "purchase_date" name = "purchase_date" type = "date" > < / div >
< div class = "col-md-4 d-flex align-items-end" > < div class = "form-check mb-2" > < input class = "form-check-input" id = "recurrence_monitoring" name = "recurrence_monitoring" value = "1" type = "checkbox" > < label class = "form-check-label" for = "recurrence_monitoring" > Alerter après 3 pannes récurrentes< / label > < / div > < / div >
< / div >
< / div >
2026-08-14 18:02:25 +02:00
< div class = "mb-3" >
< label for = "description" class = "form-label" > Description< / label >
< textarea class = "form-control" id = "description" name = "description" rows = "3" > {{ equipment.description if equipment else '' }}< / textarea >
< / div >
< div class = "d-flex justify-content-between" >
< a href = "{{ url_for('equipments.index') }}" class = "btn btn-outline-secondary" >
< i class = "fas fa-arrow-left me-1" > < / i > Retour
< / a >
< button type = "submit" class = "btn btn-primary" >
< i class = "fas fa-save me-1" > < / i > Enregistrer
< / button >
< / div >
< / form >
< / div >
< / div >
< / div >
< div class = "col-lg-4" >
< div class = "card" >
< div class = "card-header" >
< h5 class = "mb-0" > Aide< / h5 >
< / div >
< div class = "card-body" id = "help-panel" >
< p class = "text-muted" > Sélectionnez un type d'équipement pour voir les détails.< / p >
< div id = "help-parent" style = "display:none;" >
< h6 > < i class = "bi bi-collection text-primary me-2" > < / i > Groupe parent< / h6 >
< ul class = "small" >
< li > Équipement principal (ex: Lot de 60 chaises)< / li >
< li > Pas de salle assignée< / li >
< li > Peut contenir des enfants< / li >
< li > Quantité déclarée< / li >
< / ul >
< / div >
< div id = "help-child" style = "display:none;" >
< h6 > < i class = "bi bi-diagram-3 text-success me-2" > < / i > Groupe enfant< / h6 >
< ul class = "small" >
< li > Sous-ensemble localisé (ex: 30 chaises salle B101)< / li >
< li > Salle optionnelle< / li >
< li > Lié à un parent< / li >
< li > Hérite de la catégorie du parent< / li >
< / ul >
< / div >
< div id = "help-individual" style = "display:none;" >
< h6 > < i class = "bi bi-box text-warning me-2" > < / i > Individuel< / h6 >
< ul class = "small" >
< li > Unité unique suivie individuellement< / li >
< li > Numéro de série requis< / li >
< li > Salle obligatoire< / li >
< li > Peut être déplacé entre salles< / li >
< / ul >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< style >
.type-card {
border: 2px solid #dee2e6;
}
.type-card:hover {
border-color: #0d6efd;
transform: translateY(-2px);
}
.type-card.selected {
border-color: #0d6efd;
background-color: #f8f9fa;
}
< / style >
< script >
document.addEventListener('DOMContentLoaded', function() {
const typeCards = document.querySelectorAll('.type-card');
const isGroupInput = document.getElementById('is_group_input');
const parentIdInput = document.getElementById('parent_id_input');
const parentSelector = document.getElementById('parent_selector');
const quantityField = document.getElementById('quantity_field');
const roomField = document.getElementById('room_field');
const codeField = document.getElementById('code_field');
const roomRequired = document.getElementById('room_required');
const roomHint = document.getElementById('room_hint');
2026-08-15 01:37:17 +02:00
const categorySelect = document.getElementById('category_id');
const lotSelect = document.getElementById('lot_id');
const quickAddLot = document.getElementById('quick-add-lot');
function filterLots() {
const categoryId = categorySelect.value;
let selectedIsValid = !lotSelect.value;
[...lotSelect.options].forEach((option, index) => {
if (!index) return;
const visible = !!categoryId & & option.dataset.categoryId === categoryId;
option.hidden = !visible;
option.disabled = !visible;
if (visible & & option.selected) selectedIsValid = true;
});
if (!selectedIsValid) lotSelect.value = '';
quickAddLot.disabled = !categoryId;
}
categorySelect.addEventListener('change', filterLots);
quickAddLot.addEventListener('click', async () => {
const categoryName = categorySelect.selectedOptions[0]?.textContent.trim();
const name = window.prompt(`Nom du nouveau lot pour « ${categoryName} » :`);
if (!name || !name.trim()) return;
const response = await fetch('{{ url_for("wizard.quick_create_lot") }}', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token() }}'},
body: JSON.stringify({name: name.trim(), category_id: categorySelect.value})
});
const data = await response.json();
if (!response.ok & & !data.lot) {
window.alert(data.error || 'Création du lot impossible.');
return;
}
const created = data.lot;
let option = [...lotSelect.options].find(item => item.value === String(created.id));
if (!option) {
option = new Option(created.name, created.id);
option.dataset.categoryId = String(created.category_id);
lotSelect.add(option);
}
filterLots();
lotSelect.value = String(created.id);
if (!response.ok) window.alert(data.error);
});
filterLots();
2026-08-14 18:02:25 +02:00
// Vérifier si on a un parent_id dans l'URL (via template)
const preselectedParentId = "{{ preselected_parent_id or '' }}";
// Masquer les champs conditionnels au départ (création uniquement)
{% if not equipment %}
parentSelector.style.display = 'none';
quantityField.style.display = 'none';
{% endif %}
// Gestion du changement de parent
const parentSelect = document.getElementById('parent_select');
if (parentSelect) {
parentSelect.addEventListener('change', function() {
parentIdInput.value = this.value;
// Hériter de la catégorie du parent
if (this.selectedOptions[0]) {
const categoryId = this.selectedOptions[0].dataset.category;
document.getElementById('category_id').value = categoryId;
2026-08-15 01:37:17 +02:00
filterLots();
2026-08-14 18:02:25 +02:00
}
});
}
// Fonction pour sélectionner un type
function selectType(type) {
// Retirer la sélection précédente
typeCards.forEach(c => c.classList.remove('selected'));
// Trouver et ajouter la classe selected
const card = document.querySelector('.type-card[data-type="' + type + '"]');
if (card) {
card.classList.add('selected');
}
// Masquer tous les panneaux d'aide
document.getElementById('help-parent').style.display = 'none';
document.getElementById('help-child').style.display = 'none';
document.getElementById('help-individual').style.display = 'none';
// Afficher le panneau d'aide correspondant
document.getElementById('help-' + type).style.display = 'block';
// Ajuster les champs selon le type
if (type === 'parent') {
isGroupInput.value = '1';
parentIdInput.value = '';
parentSelector.style.display = 'none';
quantityField.style.display = 'block';
roomField.style.display = 'block';
roomRequired.textContent = '';
roomHint.textContent = 'Optionnel pour un groupe parent';
codeField.querySelector('input').disabled = true;
codeField.querySelector('input').value = '';
codeField.querySelector('small').textContent = 'Les groupes n\'ont pas de numéro de série';
} else if (type === 'child') {
isGroupInput.value = '1';
parentSelector.style.display = 'block';
quantityField.style.display = 'block';
roomField.style.display = 'block';
roomRequired.textContent = '';
roomHint.textContent = 'Optionnel pour un groupe enfant';
codeField.querySelector('input').disabled = true;
codeField.querySelector('small').textContent = 'Les groupes n\'ont pas de numéro de série';
} else if (type === 'individual') {
isGroupInput.value = '0';
parentIdInput.value = '';
parentSelector.style.display = 'none';
quantityField.style.display = 'none';
roomField.style.display = 'block';
roomRequired.textContent = '*';
roomHint.textContent = 'Obligatoire pour un élément individuel';
codeField.querySelector('input').disabled = false;
codeField.querySelector('input').placeholder = 'Ex: CHAISE-001';
codeField.querySelector('small').textContent = 'Uniquement pour les éléments individuels';
}
}
// Gestion du clic sur les cartes de type
typeCards.forEach(card => {
card.addEventListener('click', function() {
selectType(this.dataset.type);
});
});
// Si parent_id pré-sélectionné, activer le mode "enfant"
{% if preselected_parent_id %}
selectType('child');
if (parentSelect) {
parentSelect.value = '{{ preselected_parent_id }}';
parentSelect.dispatchEvent(new Event('change'));
}
{% endif %}
});
< / script >
2026-08-14 21:16:01 +02:00
{% endblock %}