Cette mise à jour structurelle améliore la classification des équipements en introduisant la notion de sous-catégories et supprime la gestion directe de l'appartenance d'un équipement à une boîte (`parentBoxIds`). L'appartenance est désormais uniquement définie côté conteneur. Une nouvelle catégorie "Régie / Backline" est également ajoutée.
**Changements majeurs :**
- **Suppression de `parentBoxIds` sur `EquipmentModel` :**
- Le champ `parentBoxIds` a été retiré du modèle de données `EquipmentModel` et de toutes les logiques associées (création, mise à jour, copie).
- La responsabilité de lier un équipement à un conteneur est désormais exclusivement gérée par le `ContainerModel` via sa liste `equipmentIds`.
- La logique de synchronisation complexe dans `EquipmentFormPage` qui mettait à jour les conteneurs lors de la modification d'un équipement a été entièrement supprimée, simplifiant considérablement le code.
- Le sélecteur de boîtes parentes (`ParentBoxesSelector`) a été retiré du formulaire d'équipement.
- **Ajout des sous-catégories :**
- Un champ optionnel `subCategory` (String) a été ajouté au `EquipmentModel`.
- Le formulaire de création/modification d'équipement inclut désormais un nouveau champ "Sous-catégorie" avec autocomplétion.
- Ce champ est contextuel : il propose des suggestions basées sur les sous-catégories existantes pour la catégorie principale sélectionnée (ex: "Console", "Micro" pour la catégorie "Son").
- La sous-catégorie est maintenant affichée sur les fiches de détail des équipements et dans les listes de la page de gestion, améliorant la visibilité du classement.
**Nouvelle catégorie d'équipement :**
- Une nouvelle catégorie `backline` ("Régie / Backline") a été ajoutée à `EquipmentCategory` avec une icône (`Icons.piano`) et une couleur associée.
**Refactorisation et nettoyage :**
- Le `EquipmentProvider` et `EquipmentService` ont été mis à jour pour charger et filtrer les sous-catégories.
- De nombreuses instanciations d'un `EquipmentModel` vide (`dummy`) à travers l'application ont été nettoyées pour retirer la référence à `parentBoxIds`.
- **Version de l'application :**
- La version a été incrémentée à `1.0.4`.
634 lines
26 KiB
Dart
634 lines
26 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:em2rp/models/equipment_model.dart';
|
|
import 'package:em2rp/providers/equipment_provider.dart';
|
|
import 'package:em2rp/providers/local_user_provider.dart';
|
|
import 'package:em2rp/services/equipment_service.dart';
|
|
import 'package:em2rp/utils/colors.dart';
|
|
import 'package:em2rp/views/widgets/nav/custom_app_bar.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:em2rp/views/equipment_form/brand_model_selector.dart';
|
|
import 'package:em2rp/views/equipment_form/subcategory_selector.dart';
|
|
import 'package:em2rp/utils/id_generator.dart';
|
|
import 'package:em2rp/utils/debug_log.dart';
|
|
|
|
class EquipmentFormPage extends StatefulWidget {
|
|
final EquipmentModel? equipment;
|
|
|
|
const EquipmentFormPage({super.key, this.equipment});
|
|
|
|
@override
|
|
State<EquipmentFormPage> createState() => _EquipmentFormPageState();
|
|
}
|
|
|
|
class _EquipmentFormPageState extends State<EquipmentFormPage> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
final EquipmentService _equipmentService = EquipmentService();
|
|
|
|
// Controllers
|
|
final TextEditingController _identifierController = TextEditingController();
|
|
final TextEditingController _brandController = TextEditingController();
|
|
final TextEditingController _modelController = TextEditingController();
|
|
final TextEditingController _subCategoryController = TextEditingController();
|
|
final TextEditingController _purchasePriceController = TextEditingController();
|
|
final TextEditingController _rentalPriceController = TextEditingController();
|
|
final TextEditingController _totalQuantityController = TextEditingController();
|
|
final TextEditingController _criticalThresholdController = TextEditingController();
|
|
final TextEditingController _notesController = TextEditingController();
|
|
final TextEditingController _quantityToAddController = TextEditingController(text: '1');
|
|
|
|
// State variables
|
|
EquipmentCategory _selectedCategory = EquipmentCategory.other;
|
|
EquipmentStatus _selectedStatus = EquipmentStatus.available;
|
|
DateTime? _purchaseDate;
|
|
DateTime? _lastMaintenanceDate;
|
|
DateTime? _nextMaintenanceDate;
|
|
bool _isLoading = false;
|
|
bool _addMultiple = false;
|
|
String? _selectedBrand;
|
|
List<String> _filteredModels = [];
|
|
List<String> _filteredSubCategories = [];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
final provider = Provider.of<EquipmentProvider>(context, listen: false);
|
|
provider.loadBrands();
|
|
provider.loadModels();
|
|
});
|
|
if (widget.equipment != null) {
|
|
_populateFields();
|
|
}
|
|
}
|
|
|
|
void _populateFields() {
|
|
final equipment = widget.equipment!;
|
|
setState(() {
|
|
_identifierController.text = equipment.id;
|
|
_brandController.text = equipment.brand ?? '';
|
|
_selectedBrand = equipment.brand;
|
|
_modelController.text = equipment.model ?? '';
|
|
_subCategoryController.text = equipment.subCategory ?? '';
|
|
_selectedCategory = equipment.category;
|
|
_selectedStatus = equipment.status;
|
|
_purchasePriceController.text = equipment.purchasePrice?.toStringAsFixed(2) ?? '';
|
|
_rentalPriceController.text = equipment.rentalPrice?.toStringAsFixed(2) ?? '';
|
|
_totalQuantityController.text = equipment.totalQuantity?.toString() ?? '';
|
|
_criticalThresholdController.text = equipment.criticalThreshold?.toString() ?? '';
|
|
_purchaseDate = equipment.purchaseDate;
|
|
_lastMaintenanceDate = equipment.lastMaintenanceDate;
|
|
_nextMaintenanceDate = equipment.nextMaintenanceDate;
|
|
_notesController.text = equipment.notes ?? '';
|
|
});
|
|
|
|
DebugLog.info('[EquipmentForm] Populating fields for equipment: ${equipment.id}');
|
|
|
|
|
|
if (_selectedBrand != null && _selectedBrand!.isNotEmpty) {
|
|
_loadFilteredModels(_selectedBrand!);
|
|
}
|
|
|
|
// Charger les sous-catégories pour la catégorie sélectionnée
|
|
_loadFilteredSubCategories(_selectedCategory);
|
|
}
|
|
|
|
|
|
Future<void> _loadFilteredModels(String brand) async {
|
|
try {
|
|
final equipmentProvider = Provider.of<EquipmentProvider>(context, listen: false);
|
|
final models = await equipmentProvider.loadModelsByBrand(brand);
|
|
setState(() {
|
|
_filteredModels = models;
|
|
});
|
|
} catch (e) {
|
|
setState(() {
|
|
_filteredModels = [];
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _loadFilteredSubCategories(EquipmentCategory category) async {
|
|
try {
|
|
final equipmentProvider = Provider.of<EquipmentProvider>(context, listen: false);
|
|
final subCategories = await equipmentProvider.loadSubCategoriesByCategory(category);
|
|
setState(() {
|
|
_filteredSubCategories = subCategories;
|
|
});
|
|
} catch (e) {
|
|
setState(() {
|
|
_filteredSubCategories = [];
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_identifierController.dispose();
|
|
_brandController.dispose();
|
|
_modelController.dispose();
|
|
_subCategoryController.dispose();
|
|
_purchasePriceController.dispose();
|
|
_rentalPriceController.dispose();
|
|
_totalQuantityController.dispose();
|
|
_criticalThresholdController.dispose();
|
|
_notesController.dispose();
|
|
_quantityToAddController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
bool get _isConsumable => _selectedCategory == EquipmentCategory.consumable || _selectedCategory == EquipmentCategory.cable;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final localUserProvider = Provider.of<LocalUserProvider>(context);
|
|
final hasManagePermission = localUserProvider.hasPermission('manage_equipment');
|
|
final isEditing = widget.equipment != null;
|
|
|
|
return Scaffold(
|
|
appBar: CustomAppBar(
|
|
title: isEditing ? 'Modifier l\'équipement' : 'Nouvel équipement',
|
|
),
|
|
body: _isLoading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// Identifiant (généré ou saisi)
|
|
TextFormField(
|
|
controller: _identifierController,
|
|
decoration: InputDecoration(
|
|
labelText: 'Identifiant *',
|
|
border: const OutlineInputBorder(),
|
|
prefixIcon: const Icon(Icons.tag),
|
|
hintText: isEditing ? null : 'Laissez vide pour générer automatiquement',
|
|
helperText: isEditing ? 'Non modifiable' : 'Format auto: {Marque4Chars}_{Modèle}',
|
|
),
|
|
enabled: !isEditing,
|
|
validator: (value) {
|
|
if (value != null && value.isNotEmpty) {
|
|
// Empêcher les ID commençant par BOX_ (réservé aux containers)
|
|
if (value.toUpperCase().startsWith('BOX_')) {
|
|
return 'Les ID commençant par BOX_ sont réservés aux boites';
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Case à cocher "Ajouter plusieurs" (uniquement en mode création)
|
|
if (!isEditing) ...[
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
flex: 2,
|
|
child: CheckboxListTile(
|
|
title: const Text('Ajouter plusieurs équipements'),
|
|
subtitle: const Text('Créer plusieurs équipements numérotés'),
|
|
value: _addMultiple,
|
|
contentPadding: EdgeInsets.zero,
|
|
onChanged: (bool? value) {
|
|
setState(() {
|
|
_addMultiple = value ?? false;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
if (_addMultiple) ...[
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: TextFormField(
|
|
controller: _quantityToAddController,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Quantité ou range',
|
|
border: OutlineInputBorder(),
|
|
prefixIcon: Icon(Icons.plus_one),
|
|
hintText: '5 ou 6-18',
|
|
helperText: 'Ex: 5 ou 6-18',
|
|
),
|
|
keyboardType: TextInputType.text,
|
|
validator: (value) {
|
|
if (_addMultiple) {
|
|
if (value == null || value.isEmpty) return 'Requis';
|
|
// Vérifier si c'est un nombre simple ou une range
|
|
if (value.contains('-')) {
|
|
final parts = value.split('-');
|
|
if (parts.length != 2) return 'Format invalide';
|
|
final start = int.tryParse(parts[0].trim());
|
|
final end = int.tryParse(parts[1].trim());
|
|
if (start == null || end == null) return 'Nombres invalides';
|
|
if (start >= end) return 'Le début doit être < fin';
|
|
if (end - start > 100) return 'Max 100 équipements';
|
|
} else {
|
|
final num = int.tryParse(value);
|
|
if (num == null || num < 1 || num > 100) return '1-100';
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
],
|
|
|
|
// Sélecteur Marque/Modèle
|
|
BrandModelSelector(
|
|
brandController: _brandController,
|
|
modelController: _modelController,
|
|
selectedBrand: _selectedBrand,
|
|
filteredModels: _filteredModels,
|
|
onBrandChanged: (brand) {
|
|
setState(() {
|
|
_selectedBrand = brand;
|
|
});
|
|
if (brand != null && brand.isNotEmpty) {
|
|
_loadFilteredModels(brand);
|
|
} else {
|
|
setState(() {
|
|
_filteredModels = [];
|
|
});
|
|
}
|
|
},
|
|
onModelsChanged: (models) {
|
|
setState(() {
|
|
_filteredModels = models;
|
|
});
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Catégorie et Statut
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: DropdownButtonFormField<EquipmentCategory>(
|
|
value: _selectedCategory,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Catégorie *',
|
|
border: OutlineInputBorder(),
|
|
prefixIcon: Icon(Icons.category),
|
|
),
|
|
items: EquipmentCategory.values.map((category) {
|
|
return DropdownMenuItem(
|
|
value: category,
|
|
child: Text(category.label),
|
|
);
|
|
}).toList(),
|
|
onChanged: (value) {
|
|
if (value != null) {
|
|
setState(() {
|
|
_selectedCategory = value;
|
|
_subCategoryController.clear();
|
|
});
|
|
_loadFilteredSubCategories(value);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
// Afficher le statut uniquement si ce n'est pas un consommable ou câble
|
|
if (!_isConsumable) ...[
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: DropdownButtonFormField<EquipmentStatus>(
|
|
value: _selectedStatus,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Statut *',
|
|
border: OutlineInputBorder(),
|
|
prefixIcon: Icon(Icons.info),
|
|
),
|
|
items: EquipmentStatus.values.map((status) {
|
|
return DropdownMenuItem(
|
|
value: status,
|
|
child: Text(status.label),
|
|
);
|
|
}).toList(),
|
|
onChanged: (value) {
|
|
if (value != null) {
|
|
setState(() {
|
|
_selectedStatus = value;
|
|
});
|
|
}
|
|
},
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Sous-catégorie
|
|
SubCategorySelector(
|
|
controller: _subCategoryController,
|
|
selectedCategory: _selectedCategory,
|
|
filteredSubCategories: _filteredSubCategories,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
// La valeur est déjà dans le controller
|
|
});
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Prix
|
|
if (hasManagePermission) ...[
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextFormField(
|
|
controller: _purchasePriceController,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Prix d\'achat (€)',
|
|
border: OutlineInputBorder(),
|
|
prefixIcon: Icon(Icons.euro),
|
|
),
|
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}'))],
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: TextFormField(
|
|
controller: _rentalPriceController,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Prix de location (€)',
|
|
border: OutlineInputBorder(),
|
|
prefixIcon: Icon(Icons.attach_money),
|
|
),
|
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}'))],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
],
|
|
|
|
// Quantités pour consommables
|
|
if (_isConsumable) ...[
|
|
const Divider(),
|
|
const Text('Gestion des quantités', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextFormField(
|
|
controller: _totalQuantityController,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Quantité totale',
|
|
border: OutlineInputBorder(),
|
|
prefixIcon: Icon(Icons.inventory),
|
|
),
|
|
keyboardType: TextInputType.number,
|
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: TextFormField(
|
|
controller: _criticalThresholdController,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Seuil critique',
|
|
border: OutlineInputBorder(),
|
|
prefixIcon: Icon(Icons.warning),
|
|
),
|
|
keyboardType: TextInputType.number,
|
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
],
|
|
|
|
// Dates
|
|
const Divider(),
|
|
const Text('Dates', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
|
const SizedBox(height: 16),
|
|
_buildDateField(label: 'Date d\'achat', icon: Icons.shopping_cart, value: _purchaseDate, onTap: () => _selectDate(context, 'purchase')),
|
|
const SizedBox(height: 16),
|
|
_buildDateField(label: 'Dernière maintenance', icon: Icons.build, value: _lastMaintenanceDate, onTap: () => _selectDate(context, 'lastMaintenance')),
|
|
const SizedBox(height: 16),
|
|
_buildDateField(label: 'Prochaine maintenance', icon: Icons.event, value: _nextMaintenanceDate, onTap: () => _selectDate(context, 'nextMaintenance')),
|
|
const SizedBox(height: 16),
|
|
|
|
// Notes
|
|
const Divider(),
|
|
TextFormField(
|
|
controller: _notesController,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Notes',
|
|
border: OutlineInputBorder(),
|
|
prefixIcon: Icon(Icons.notes),
|
|
),
|
|
maxLines: 3,
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Boutons
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('Annuler'),
|
|
),
|
|
const SizedBox(width: 16),
|
|
ElevatedButton(
|
|
onPressed: _saveEquipment,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.rouge,
|
|
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
|
),
|
|
child: Text(isEditing ? 'Enregistrer' : 'Créer', style: const TextStyle(color: Colors.white)),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
|
|
Widget _buildDateField({required String label, required IconData icon, required DateTime? value, required VoidCallback onTap}) {
|
|
return InkWell(
|
|
onTap: onTap,
|
|
child: InputDecorator(
|
|
decoration: InputDecoration(
|
|
labelText: label,
|
|
border: const OutlineInputBorder(),
|
|
prefixIcon: Icon(icon),
|
|
suffixIcon: value != null
|
|
? IconButton(
|
|
icon: const Icon(Icons.clear),
|
|
onPressed: () {
|
|
setState(() {
|
|
if (label.contains('achat')) {
|
|
_purchaseDate = null;
|
|
} else if (label.contains('Dernière')) {
|
|
_lastMaintenanceDate = null;
|
|
} else if (label.contains('Prochaine')) {
|
|
_nextMaintenanceDate = null;
|
|
}
|
|
});
|
|
},
|
|
)
|
|
: null,
|
|
),
|
|
child: Text(
|
|
value != null ? DateFormat('dd/MM/yyyy').format(value) : 'Sélectionner une date',
|
|
style: TextStyle(color: value != null ? Colors.black : Colors.grey),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _selectDate(BuildContext context, String field) async {
|
|
final DateTime? picked = await showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2000),
|
|
lastDate: DateTime(2100),
|
|
);
|
|
|
|
if (picked != null) {
|
|
setState(() {
|
|
switch (field) {
|
|
case 'purchase':
|
|
_purchaseDate = picked;
|
|
break;
|
|
case 'lastMaintenance':
|
|
_lastMaintenanceDate = picked;
|
|
break;
|
|
case 'nextMaintenance':
|
|
_nextMaintenanceDate = picked;
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _saveEquipment() async {
|
|
if (!_formKey.currentState!.validate()) return;
|
|
|
|
setState(() => _isLoading = true);
|
|
|
|
try {
|
|
final equipmentProvider = Provider.of<EquipmentProvider>(context, listen: false);
|
|
final isEditing = widget.equipment != null;
|
|
|
|
int? availableQuantity;
|
|
if (_isConsumable && _totalQuantityController.text.isNotEmpty) {
|
|
final totalQuantity = int.parse(_totalQuantityController.text);
|
|
if (isEditing && widget.equipment!.availableQuantity != null) {
|
|
availableQuantity = widget.equipment!.availableQuantity;
|
|
} else {
|
|
availableQuantity = totalQuantity;
|
|
}
|
|
}
|
|
|
|
// Validation marque/modèle obligatoires
|
|
String brand = _brandController.text.trim();
|
|
String model = _modelController.text.trim();
|
|
|
|
if (brand.isEmpty || model.isEmpty) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('La marque et le modèle sont obligatoires')),
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Génération d'identifiant si vide
|
|
List<String> ids = [];
|
|
List<int> numbers = [];
|
|
|
|
if (!isEditing && _identifierController.text.isEmpty) {
|
|
// Gérer la range ou nombre simple
|
|
final quantityText = _quantityToAddController.text.trim();
|
|
if (_addMultiple && quantityText.contains('-')) {
|
|
// Range: ex "6-18"
|
|
final parts = quantityText.split('-');
|
|
final start = int.parse(parts[0].trim());
|
|
final end = int.parse(parts[1].trim());
|
|
for (int i = start; i <= end; i++) {
|
|
numbers.add(i);
|
|
}
|
|
} else if (_addMultiple) {
|
|
// Nombre simple
|
|
final nbToAdd = int.tryParse(quantityText) ?? 1;
|
|
for (int i = 1; i <= nbToAdd; i++) {
|
|
numbers.add(i);
|
|
}
|
|
}
|
|
|
|
// Générer les IDs
|
|
if (numbers.isEmpty) {
|
|
String baseId = IdGenerator.generateEquipmentId(brand: brand, model: model, number: null);
|
|
String uniqueId = await IdGenerator.ensureUniqueEquipmentId(baseId, _equipmentService);
|
|
ids.add(uniqueId);
|
|
} else {
|
|
for (final num in numbers) {
|
|
String baseId = IdGenerator.generateEquipmentId(brand: brand, model: model, number: num);
|
|
String uniqueId = await IdGenerator.ensureUniqueEquipmentId(baseId, _equipmentService);
|
|
ids.add(uniqueId);
|
|
}
|
|
}
|
|
} else {
|
|
ids.add(_identifierController.text.trim());
|
|
}
|
|
|
|
// Création des équipements
|
|
for (final id in ids) {
|
|
final now = DateTime.now();
|
|
final equipment = EquipmentModel(
|
|
id: id,
|
|
name: id, // Utilisation de l'identifiant comme nom
|
|
brand: brand,
|
|
model: model,
|
|
category: _selectedCategory,
|
|
subCategory: _subCategoryController.text.trim().isNotEmpty ? _subCategoryController.text.trim() : null,
|
|
status: _selectedStatus,
|
|
purchasePrice: _purchasePriceController.text.isNotEmpty ? double.tryParse(_purchasePriceController.text) : null,
|
|
rentalPrice: _rentalPriceController.text.isNotEmpty ? double.tryParse(_rentalPriceController.text) : null,
|
|
totalQuantity: _isConsumable ? int.tryParse(_totalQuantityController.text) : null,
|
|
criticalThreshold: _isConsumable ? int.tryParse(_criticalThresholdController.text) : null,
|
|
purchaseDate: _purchaseDate,
|
|
lastMaintenanceDate: _lastMaintenanceDate,
|
|
nextMaintenanceDate: _nextMaintenanceDate,
|
|
notes: _notesController.text,
|
|
createdAt: isEditing ? (widget.equipment?.createdAt ?? now) : now,
|
|
updatedAt: now,
|
|
availableQuantity: availableQuantity,
|
|
);
|
|
if (isEditing) {
|
|
await equipmentProvider.updateEquipment(equipment);
|
|
} else {
|
|
await equipmentProvider.addEquipment(equipment);
|
|
}
|
|
}
|
|
|
|
if (mounted) {
|
|
Navigator.pop(context, true);
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Erreur lors de l\'enregistrement : $e')),
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
}
|