Browse Source

push 09112025

28062025_02
andrymodeste 4 weeks ago
parent
commit
13554ee49c
  1. 5
      lib/Components/QrScan.dart
  2. 25
      lib/Components/commandManagementComponents/CommandeActions.dart
  3. 831
      lib/Components/windows_qr_scanner.dart
  4. 1090
      lib/Services/qrService.dart
  5. 43
      lib/Services/stock_managementDatabase.dart
  6. 398
      lib/Views/HandleProduct.dart
  7. 10
      lib/Views/commandManagement.dart
  8. 218
      lib/Views/demande_sortie_personnelle_page.dart
  9. 13
      lib/config/DatabaseConfig.dart
  10. 8
      linux/flutter/generated_plugin_registrant.cc
  11. 2
      linux/flutter/generated_plugins.cmake
  12. 2
      macos/Flutter/GeneratedPluginRegistrant.swift
  13. 132
      pubspec.lock
  14. 13
      pubspec.yaml
  15. 9
      windows/flutter/generated_plugin_registrant.cc
  16. 3
      windows/flutter/generated_plugins.cmake

5
lib/Components/QrScan.dart

@ -147,7 +147,7 @@ class _ScanQRPageState extends State<ScanQRPage> {
} }
} }
}, },
errorBuilder: (context, error, child) { errorBuilder: (context, error) {
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -155,7 +155,8 @@ class _ScanQRPageState extends State<ScanQRPage> {
const Icon(Icons.error, size: 64, color: Colors.red), const Icon(Icons.error, size: 64, color: Colors.red),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Erreur: ${error.errorDetails?.message ?? 'Erreur inconnue'}'), 'Erreur: ${error.errorDetails?.message ?? 'Erreur inconnue'}',
),
const SizedBox(height: 16), const SizedBox(height: 16),
ElevatedButton( ElevatedButton(
onPressed: () => _initializeController(), onPressed: () => _initializeController(),

25
lib/Components/commandManagementComponents/CommandeActions.dart

@ -1,37 +1,43 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:youmazgestion/Models/client.dart'; import 'package:youmazgestion/Models/client.dart';
// Classe supplémentaire
//Classe suplementaire
class CommandeActions extends StatelessWidget { class CommandeActions extends StatelessWidget {
final Commande commande; final Commande commande;
final Function(int, StatutCommande) onStatutChanged; final Function(int, StatutCommande) onStatutChanged;
final Function(Commande) onGenerateBonLivraison; final Function(Commande) onGenerateBonLivraison;
const CommandeActions({ const CommandeActions({
required this.commande, required this.commande,
required this.onStatutChanged, required this.onStatutChanged,
required this.onGenerateBonLivraison, required this.onGenerateBonLivraison,
}); });
List<Widget> _buildActionButtons(BuildContext context) { List<Widget> _buildActionButtons(BuildContext context) {
List<Widget> buttons = []; List<Widget> buttons = [];
switch (commande.statut) { switch (commande.statut) {
case StatutCommande.enAttente: case StatutCommande.enAttente:
buttons.addAll([ buttons.addAll([
// Bouton confirmer
_buildActionButton( _buildActionButton(
label: 'Confirmer', label: 'Confirmer',
icon: Icons.check_circle, icon: Icons.check_circle,
color: Colors.blue, color: Colors.blue,
onPressed: () => onGenerateBonLivraison(commande), onPressed: () => _showConfirmDialog(
context,
'Confirmer la commande',
'Êtes-vous sûr de vouloir confirmer cette commande ?',
() {
// Change le statut à "confirmée"
onStatutChanged(commande.id!, StatutCommande.confirmee);
// Et génère le bon de livraison après confirmation
onGenerateBonLivraison(commande);
},
), ),
),
// Bouton annuler
_buildActionButton( _buildActionButton(
label: 'Annuler', label: 'Annuler',
icon: Icons.cancel, icon: Icons.cancel,
@ -181,6 +187,7 @@ class CommandeActions extends StatelessWidget {
}, },
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(

831
lib/Components/windows_qr_scanner.dart

@ -0,0 +1,831 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:youmazgestion/Components/app_bar.dart';
import 'package:youmazgestion/Components/appDrawer.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import 'package:youmazgestion/controller/userController.dart';
import '../Models/produit.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class DemandeSortiePersonnellePage extends StatefulWidget {
const DemandeSortiePersonnellePage({super.key});
@override
_DemandeSortiePersonnellePageState createState() =>
_DemandeSortiePersonnellePageState();
}
class _DemandeSortiePersonnellePageState
extends State<DemandeSortiePersonnellePage> with TickerProviderStateMixin {
final AppDatabase _database = AppDatabase.instance;
final UserController _userController = Get.find<UserController>();
final _formKey = GlobalKey<FormState>();
final _quantiteController = TextEditingController(text: '1');
final _motifController = TextEditingController();
final _notesController = TextEditingController();
final _searchController = TextEditingController();
Product? _selectedProduct;
List<Product> _products = [];
List<Product> _filteredProducts = [];
bool _isLoading = false;
bool _isSearching = false;
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
_slideAnimation =
Tween<Offset>(begin: const Offset(0, 0.3), end: Offset.zero).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeOutCubic),
);
_loadProducts();
_searchController.addListener(_filterProducts);
}
void _scanQrOrBarcode() async {
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Container(
width: double.maxFinite,
height: 400,
child: MobileScanner(
onDetect: (BarcodeCapture barcodeCap) {
print("BarcodeCapture: $barcodeCap");
// Now accessing the barcodes attribute
final List<Barcode> barcodes = barcodeCap.barcodes;
if (barcodes.isNotEmpty) {
// Get the first detected barcode value
String? scanResult = barcodes.first.rawValue;
print("Scanned Result: $scanResult");
if (scanResult != null && scanResult.isNotEmpty) {
setState(() {
_searchController.text = scanResult;
print(
"Updated Search Controller: ${_searchController.text}");
});
// Close dialog after scanning
Navigator.of(context).pop();
// Refresh product list based on new search input
_filterProducts();
} else {
print("Scan result was empty or null.");
Navigator.of(context).pop();
}
} else {
print("No barcodes detected.");
Navigator.of(context).pop();
}
},
),
),
);
},
);
}
void _filterProducts() {
final query = _searchController.text.toLowerCase();
setState(() {
if (query.isEmpty) {
_filteredProducts = _products;
_isSearching = false;
} else {
_isSearching = true;
_filteredProducts = _products.where((product) {
return product.name.toLowerCase().contains(query) ||
(product.reference?.toLowerCase().contains(query) ?? false);
}).toList();
}
});
}
Future<void> _loadProducts() async {
setState(() => _isLoading = true);
try {
final products = await _database.getProducts();
setState(() {
_products = products.where((p) {
// Check stock availability
print("point de vente id: ${_userController.pointDeVenteId}");
bool hasStock = _userController.pointDeVenteId == 0
? (p.stock ?? 0) > 0
: (p.stock ?? 0) > 0 &&
p.pointDeVenteId == _userController.pointDeVenteId;
return hasStock;
}).toList();
// Setting filtered products
_filteredProducts = _products;
// End loading state
_isLoading = false;
});
// Start the animation
_animationController.forward();
} catch (e) {
// Handle any errors
setState(() {
_isLoading = false;
});
_showErrorSnackbar('Impossible de charger les produits: $e');
}
}
Future<void> _soumettreDemandePersonnelle() async {
if (!_formKey.currentState!.validate() || _selectedProduct == null) {
_showErrorSnackbar('Veuillez remplir tous les champs obligatoires');
return;
}
final quantite = int.tryParse(_quantiteController.text) ?? 0;
if (quantite <= 0) {
_showErrorSnackbar('La quantité doit être supérieure à 0');
return;
}
if ((_selectedProduct!.stock ?? 0) < quantite) {
_showErrorSnackbar(
'Stock insuffisant (disponible: ${_selectedProduct!.stock})');
return;
}
// Confirmation dialog
final confirmed = await _showConfirmationDialog();
if (!confirmed) return;
setState(() => _isLoading = true);
try {
await _database.createSortieStockPersonnelle(
produitId: _selectedProduct!.id!,
adminId: _userController.userId,
quantite: quantite,
motif: _motifController.text.trim(),
pointDeVenteId: _userController.pointDeVenteId > 0
? _userController.pointDeVenteId
: null,
notes: _notesController.text.trim().isNotEmpty
? _notesController.text.trim()
: null,
);
_showSuccessSnackbar(
'Votre demande de sortie personnelle a été soumise pour approbation');
// Réinitialiser le formulaire avec animation
_resetForm();
_loadProducts();
} catch (e) {
_showErrorSnackbar('Impossible de soumettre la demande: $e');
} finally {
setState(() => _isLoading = false);
}
}
void _resetForm() {
_formKey.currentState!.reset();
_quantiteController.text = '1';
_motifController.clear();
_notesController.clear();
_searchController.clear();
setState(() {
_selectedProduct = null;
_isSearching = false;
});
}
Future<bool> _showConfirmationDialog() async {
return await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: Row(
children: [
Icon(Icons.help_outline, color: Colors.orange.shade700),
const SizedBox(width: 8),
const Text('Confirmer la demande'),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Êtes-vous sûr de vouloir soumettre cette demande ?'),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Produit: ${_selectedProduct?.name}'),
Text('Quantité: ${_quantiteController.text}'),
Text('Motif: ${_motifController.text}'),
],
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange.shade700,
foregroundColor: Colors.white,
),
child: const Text('Confirmer'),
),
],
),
) ??
false;
}
void _showSuccessSnackbar(String message) {
Get.snackbar(
'',
'',
titleText: Row(
children: [
Icon(Icons.check_circle, color: Colors.white),
const SizedBox(width: 8),
const Text('Succès',
style:
TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
],
),
messageText: Text(message, style: const TextStyle(color: Colors.white)),
backgroundColor: Colors.green.shade600,
colorText: Colors.white,
duration: const Duration(seconds: 4),
margin: const EdgeInsets.all(16),
borderRadius: 12,
icon: Icon(Icons.check_circle_outline, color: Colors.white),
);
}
void _showErrorSnackbar(String message) {
Get.snackbar(
'',
'',
titleText: Row(
children: [
Icon(Icons.error, color: Colors.white),
const SizedBox(width: 8),
const Text('Erreur',
style:
TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
],
),
messageText: Text(message, style: const TextStyle(color: Colors.white)),
backgroundColor: Colors.red.shade600,
colorText: Colors.white,
duration: const Duration(seconds: 4),
margin: const EdgeInsets.all(16),
borderRadius: 12,
);
}
Widget _buildHeaderCard() {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade600, Colors.blue.shade400],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.blue.shade200,
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Icon(Icons.inventory_2, color: Colors.white, size: 28),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Sortie personnelle de stock',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 4),
Text(
'Demande d\'approbation requise',
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.8),
),
),
],
),
),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'Cette fonctionnalité permet aux administrateurs de demander '
'la sortie d\'un produit du stock pour usage personnel. '
'Toute demande nécessite une approbation avant traitement.',
style: TextStyle(fontSize: 14, color: Colors.white),
),
),
],
),
);
}
Widget _buildProductSelector() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Sélection du produit *',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey.shade800,
),
),
const SizedBox(height: 12),
// Barre de recherche
Row(
children: [
Expanded(
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Rechercher un produit...',
prefixIcon: Icon(Icons.search, color: Colors.grey.shade600),
),
onChanged: (value) {
_filterProducts(); // Call to filter products
},
),
),
IconButton(
icon: Icon(Icons.qr_code_scanner, color: Colors.blue),
onPressed: _scanQrOrBarcode,
tooltip: 'Scanner QR ou code-barres',
),
],
),
const SizedBox(height: 12),
// Liste des produits
Container(
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
),
child: _filteredProducts.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.search_off,
size: 48, color: Colors.grey.shade400),
const SizedBox(height: 8),
Text(
_isSearching
? 'Aucun produit trouvé'
: 'Aucun produit disponible',
style: TextStyle(color: Colors.grey.shade600),
),
],
),
)
: ListView.builder(
itemCount: _filteredProducts.length,
itemBuilder: (context, index) {
final product = _filteredProducts[index];
final isSelected = _selectedProduct?.id == product.id;
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: isSelected
? Colors.orange.shade50
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSelected
? Colors.orange.shade300
: Colors.transparent,
width: 2,
),
),
child: ListTile(
leading: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: isSelected
? Colors.orange.shade100
: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Icons.inventory,
color: isSelected
? Colors.orange.shade700
: Colors.grey.shade600,
),
),
title: Text(
product.name,
style: TextStyle(
fontWeight:
isSelected ? FontWeight.bold : FontWeight.w500,
color: isSelected
? Colors.orange.shade800
: Colors.grey.shade800,
),
),
subtitle: Text(
'Stock: ${product.stock} • Réf: ${product.reference ?? 'N/A'}',
style: TextStyle(
color: isSelected
? Colors.orange.shade600
: Colors.grey.shade600,
),
),
trailing: isSelected
? Icon(Icons.check_circle,
color: Colors.orange.shade700)
: Icon(Icons.radio_button_unchecked,
color: Colors.grey.shade400),
onTap: () {
setState(() {
_selectedProduct = product;
});
},
),
);
},
),
),
],
);
}
Widget _buildFormSection() {
return Column(
children: [
// Quantité
_buildInputField(
label: 'Quantité *',
controller: _quantiteController,
keyboardType: TextInputType.number,
icon: Icons.format_list_numbered,
suffix: _selectedProduct != null
? Text('max: ${_selectedProduct!.stock}',
style: TextStyle(color: Colors.grey.shade600))
: null,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer une quantité';
}
final quantite = int.tryParse(value);
if (quantite == null || quantite <= 0) {
return 'Quantité invalide';
}
if (_selectedProduct != null &&
quantite > (_selectedProduct!.stock ?? 0)) {
return 'Quantité supérieure au stock disponible';
}
return null;
},
),
const SizedBox(height: 20),
// Motif
_buildInputField(
label: 'Motif *',
controller: _motifController,
icon: Icons.description,
hintText: 'Raison de cette sortie personnelle',
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Veuillez indiquer le motif';
}
if (value.trim().length < 5) {
return 'Le motif doit contenir au moins 5 caractères';
}
return null;
},
),
const SizedBox(height: 20),
// Notes
_buildInputField(
label: 'Notes complémentaires',
controller: _notesController,
icon: Icons.note_add,
hintText: 'Informations complémentaires (optionnel)',
maxLines: 3,
),
],
);
}
Widget _buildInputField({
required String label,
required TextEditingController controller,
required IconData icon,
String? hintText,
TextInputType? keyboardType,
int maxLines = 1,
Widget? suffix,
String? Function(String?)? validator,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
const SizedBox(height: 8),
TextFormField(
controller: controller,
keyboardType: keyboardType,
maxLines: maxLines,
validator: validator,
decoration: InputDecoration(
hintText: hintText,
prefixIcon: Icon(icon, color: Colors.grey.shade600),
suffix: suffix,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade300),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.orange.shade400, width: 2),
),
filled: true,
fillColor: Colors.grey.shade50,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
),
],
);
}
Widget _buildUserInfoCard() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.person, color: Colors.grey.shade700),
const SizedBox(width: 8),
Text(
'Informations de la demande',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey.shade800,
),
),
],
),
const SizedBox(height: 12),
_buildInfoRow(
Icons.account_circle, 'Demandeur', _userController.name),
if (_userController.pointDeVenteId > 0)
_buildInfoRow(Icons.store, 'Point de vente',
_userController.pointDeVenteDesignation),
_buildInfoRow(Icons.calendar_today, 'Date',
DateTime.now().toLocal().toString().split(' ')[0]),
],
),
);
}
Widget _buildInfoRow(IconData icon, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Icon(icon, size: 16, color: Colors.grey.shade600),
const SizedBox(width: 8),
Text(
'$label: ',
style: TextStyle(
fontWeight: FontWeight.w500,
color: Colors.grey.shade700,
),
),
Expanded(
child: Text(
value,
style: TextStyle(color: Colors.grey.shade800),
),
),
],
),
);
}
Widget _buildSubmitButton() {
return Container(
width: double.infinity,
height: 56,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: LinearGradient(
colors: [Colors.orange.shade700, Colors.orange.shade500],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: Colors.orange.shade300,
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: ElevatedButton(
onPressed: _isLoading ? null : _soumettreDemandePersonnelle,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: _isLoading
? const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
),
SizedBox(width: 12),
Text(
'Traitement...',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
],
)
: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.send, color: Colors.white),
SizedBox(width: 8),
Text(
'Soumettre la demande',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(title: 'Demande sortie personnelle'),
drawer: CustomDrawer(),
body: _isLoading && _products.isEmpty
? const Center(child: CircularProgressIndicator())
: FadeTransition(
opacity: _fadeAnimation,
child: SlideTransition(
position: _slideAnimation,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeaderCard(),
const SizedBox(height: 24),
_buildProductSelector(),
const SizedBox(height: 24),
_buildFormSection(),
const SizedBox(height: 24),
_buildUserInfoCard(),
const SizedBox(height: 32),
_buildSubmitButton(),
const SizedBox(height: 16),
],
),
),
),
),
),
);
}
@override
void dispose() {
_animationController.dispose();
_quantiteController.dispose();
_motifController.dispose();
_notesController.dispose();
_searchController.dispose();
super.dispose();
}
}
extension on BarcodeCapture {
get rawValue => null;
}

1090
lib/Services/qrService.dart

File diff suppressed because it is too large

43
lib/Services/stock_managementDatabase.dart

@ -1979,12 +1979,46 @@ List<String> parseHeaderInfo(dynamic blobData) {
Future<int> updateStatutCommande( Future<int> updateStatutCommande(
int commandeId, StatutCommande statut) async { int commandeId, StatutCommande statut) async {
final db = await database; final db = await database;
try {
await db.query('START TRANSACTION');
// 🔹 Si le statut devient "annulée"
if (statut == StatutCommande.annulee) {
// 1. Récupérer les détails de la commande
final details = await db.query(
'SELECT produitId, quantite FROM details_commandes WHERE commandeId = ?',
[commandeId],
);
// 2. Remettre le stock pour chaque produit
for (final row in details) {
final produitId = row['produitId'];
final quantite = row['quantite'];
await db.query(
'UPDATE products SET stock = stock + ? WHERE id = ?',
[quantite, produitId],
);
}
}
// 3. Mettre à jour le statut de la commande
final result = await db.query( final result = await db.query(
'UPDATE commandes SET statut = ? WHERE id = ?', 'UPDATE commandes SET statut = ? WHERE id = ?',
[statut.index, commandeId]); [statut.index, commandeId],
);
await db.query('COMMIT');
return result.affectedRows!; return result.affectedRows!;
} catch (e) {
await db.query('ROLLBACK');
print("Erreur lors de la mise à jour du statut de la commande: $e");
rethrow;
}
} }
Future<List<Commande>> getCommandesByClient(int clientId) async { Future<List<Commande>> getCommandesByClient(int clientId) async {
final db = await database; final db = await database;
final result = await db.query(''' final result = await db.query('''
@ -2532,7 +2566,8 @@ Future<List<Map<String, dynamic>>> getVentesParPointDeVente({
final db = await database; final db = await database;
try { try {
String whereClause = 'WHERE c.statut != 5'; // 🔹 On ne garde que les commandes confirmées (statut = 1)
String whereClause = "WHERE c.statut = 1";
List<dynamic> whereArgs = []; List<dynamic> whereArgs = [];
if (aujourdHuiSeulement == true) { if (aujourdHuiSeulement == true) {
@ -2546,7 +2581,8 @@ Future<List<Map<String, dynamic>>> getVentesParPointDeVente({
_formatDate(endOfDay), _formatDate(endOfDay),
]); ]);
} else if (dateDebut != null && dateFin != null) { } else if (dateDebut != null && dateFin != null) {
final adjustedEndDate = DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59); final adjustedEndDate =
DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59);
whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?'; whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?';
whereArgs.addAll([ whereArgs.addAll([
_formatDate(dateDebut), _formatDate(dateDebut),
@ -2580,6 +2616,7 @@ Future<List<Map<String, dynamic>>> getVentesParPointDeVente({
return []; return [];
} }
} }
Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente( Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
int pointDeVenteId, { int pointDeVenteId, {
int limit = 5, int limit = 5,

398
lib/Views/HandleProduct.dart

@ -11,6 +11,7 @@ import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:excel/excel.dart' hide Border; import 'package:excel/excel.dart' hide Border;
import 'package:youmazgestion/Services/qrService.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import 'package:youmazgestion/controller/userController.dart'; import 'package:youmazgestion/controller/userController.dart';
import '../Components/appDrawer.dart'; import '../Components/appDrawer.dart';
@ -28,6 +29,7 @@ class _ProductManagementPageState extends State<ProductManagementPage> {
final AppDatabase _productDatabase = AppDatabase.instance; final AppDatabase _productDatabase = AppDatabase.instance;
final AppDatabase _appDatabase = AppDatabase.instance; final AppDatabase _appDatabase = AppDatabase.instance;
final UserController _userController = Get.find<UserController>(); final UserController _userController = Get.find<UserController>();
final pdfService = PdfPrintService();
List<Product> _products = []; List<Product> _products = [];
List<Product> _filteredProducts = []; List<Product> _filteredProducts = [];
@ -44,16 +46,20 @@ class _ProductManagementPageState extends State<ProductManagementPage> {
bool _isAssigning = false; bool _isAssigning = false;
final GlobalKey _qrKey = GlobalKey(debugLabel: 'QR'); final GlobalKey _qrKey = GlobalKey(debugLabel: 'QR');
Future<void> _loadAvailableCategories() async {
try {
final categories = await _productDatabase.getCategories();
setState(() {
_availableCategories = ['Non catégorisé', ...categories];
});
} catch (e) {
debugPrint('Erreur lors du chargement des catégories: $e');
// Garder la catégorie par défaut en cas d'erreur
}
}
// Catégories prédéfinies pour l'ajout de produits // Catégories prédéfinies pour l'ajout de produits
final List<String> _predefinedCategories = [ List<String> _availableCategories = ['Non catégorisé'];
'Smartphone',
'Tablette',
'Accessoires',
'Multimedia',
'Informatique',
'Laptop',
'Non catégorisé'
];
bool _isUserSuperAdmin() { bool _isUserSuperAdmin() {
return _userController.role == 'Super Admin'; return _userController.role == 'Super Admin';
} }
@ -67,6 +73,7 @@ bool _isUserSuperAdmin() {
super.initState(); super.initState();
_loadProducts(); _loadProducts();
_loadPointsDeVente(); _loadPointsDeVente();
_loadAvailableCategories();
_searchController.addListener(_filterProducts); _searchController.addListener(_filterProducts);
} }
@ -94,7 +101,7 @@ bool _isUserSuperAdmin() {
List<Map<String, dynamic>> pointsDeVente = []; List<Map<String, dynamic>> pointsDeVente = [];
bool isLoadingPoints = true; bool isLoadingPoints = true;
String selectedCategory = String selectedCategory =
_predefinedCategories.last; // 'Non catégorisé' par défaut _availableCategories.last; // 'Non catégorisé' par défaut
File? pickedImage; File? pickedImage;
String? qrPreviewData; String? qrPreviewData;
bool autoGenerateReference = true; bool autoGenerateReference = true;
@ -923,7 +930,7 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
// Catégorie // Catégorie
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
value: selectedCategory, value: selectedCategory,
items: _predefinedCategories items: _availableCategories
.map((category) => DropdownMenuItem( .map((category) => DropdownMenuItem(
value: category, child: Text(category))) value: category, child: Text(category)))
.toList(), .toList(),
@ -1360,7 +1367,43 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
if (columnIndex >= row.length || row[columnIndex]?.value == null) if (columnIndex >= row.length || row[columnIndex]?.value == null)
return null; return null;
return row[columnIndex]!.value.toString().trim(); var cellValue = row[columnIndex]!.value;
// 🔥 TRAITEMENT SPÉCIAL POUR IMEI
if (field == 'imei') {
print('🔍 IMEI brut depuis Excel: $cellValue (${cellValue.runtimeType})');
// Si c'est un nombre (notation scientifique)
if (cellValue is num) {
// Convertir directement en entier pour éviter les décimales
String imeiStr = cellValue.toInt().toString();
print('🔄 IMEI converti depuis num: $imeiStr');
return imeiStr;
}
// Si c'est déjà un String
String strValue = cellValue.toString().trim();
// Gérer la notation scientifique dans les strings
if (strValue.contains('E') || strValue.contains('e')) {
try {
// Remplacer virgule par point et parser
String normalized = strValue.replaceAll(',', '.');
double numValue = double.parse(normalized);
String imeiStr = numValue.toInt().toString();
print('🔄 IMEI converti depuis notation scientifique: $strValue$imeiStr');
return imeiStr;
} catch (e) {
print('❌ Erreur conversion IMEI: $e');
return null;
}
}
return strValue;
}
// Pour les autres champs
return cellValue.toString().trim();
} }
void _startPointDeVenteAssignmentScanning() { void _startPointDeVenteAssignmentScanning() {
@ -1390,7 +1433,6 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
}); });
}); });
} }
Map<String, dynamic> _normalizeRowData( Map<String, dynamic> _normalizeRowData(
List<Data?> row, Map<String, int> mapping, int rowIndex) { List<Data?> row, Map<String, int> mapping, int rowIndex) {
final normalizedData = <String, dynamic>{}; final normalizedData = <String, dynamic>{};
@ -1401,16 +1443,11 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
return value.toString().trim(); return value.toString().trim();
} }
// Fonction simple pour les nombres (maintenant ils sont corrects) // Fonction simple pour les nombres
double? _normalizeNumber(String? value) { double? _normalizeNumber(String? value) {
if (value == null || value.isEmpty) return null; if (value == null || value.isEmpty) return null;
// Remplacer les virgules par des points et supprimer les espaces
final cleaned = value.replaceAll(',', '.').replaceAll(RegExp(r'\s+'), ''); final cleaned = value.replaceAll(',', '.').replaceAll(RegExp(r'\s+'), '');
// Supprimer les caractères non numériques sauf le point
final numericString = cleaned.replaceAll(RegExp(r'[^0-9.]'), ''); final numericString = cleaned.replaceAll(RegExp(r'[^0-9.]'), '');
return double.tryParse(numericString); return double.tryParse(numericString);
} }
@ -1422,7 +1459,7 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
} }
} }
// Normalisation du prix (maintenant simple car corrigé en amont) // Normalisation du prix
if (mapping.containsKey('price')) { if (mapping.containsKey('price')) {
final priceValue = _cleanValue(_getColumnValue(row, mapping, 'price')); final priceValue = _cleanValue(_getColumnValue(row, mapping, 'price'));
final price = _normalizeNumber(priceValue); final price = _normalizeNumber(priceValue);
@ -1438,7 +1475,6 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
if (reference != null && reference.isNotEmpty) { if (reference != null && reference.isNotEmpty) {
normalizedData['reference'] = reference; normalizedData['reference'] = reference;
} else { } else {
// Génération automatique si non fournie
normalizedData['reference'] = _generateUniqueReference(); normalizedData['reference'] = _generateUniqueReference();
} }
} }
@ -1463,7 +1499,6 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
if (mapping.containsKey('ram')) { if (mapping.containsKey('ram')) {
final ram = _cleanValue(_getColumnValue(row, mapping, 'ram')); final ram = _cleanValue(_getColumnValue(row, mapping, 'ram'));
if (ram != null && ram.isNotEmpty) { if (ram != null && ram.isNotEmpty) {
// Standardisation du format (ex: "8 Go", "16GB" -> "8 Go", "16 Go")
final ramValue = ram.replaceAll('GB', 'Go').replaceAll('go', 'Go'); final ramValue = ram.replaceAll('GB', 'Go').replaceAll('go', 'Go');
normalizedData['ram'] = ramValue; normalizedData['ram'] = ramValue;
} }
@ -1471,48 +1506,88 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
// Normalisation de la mémoire interne // Normalisation de la mémoire interne
if (mapping.containsKey('memoire_interne')) { if (mapping.containsKey('memoire_interne')) {
final memoire = final memoire = _cleanValue(_getColumnValue(row, mapping, 'memoire_interne'));
_cleanValue(_getColumnValue(row, mapping, 'memoire_interne'));
if (memoire != null && memoire.isNotEmpty) { if (memoire != null && memoire.isNotEmpty) {
// Standardisation du format (ex: "256GB" -> "256 Go") final memoireValue = memoire.replaceAll('GB', 'Go').replaceAll('go', 'Go');
final memoireValue =
memoire.replaceAll('GB', 'Go').replaceAll('go', 'Go');
normalizedData['memoire_interne'] = memoireValue; normalizedData['memoire_interne'] = memoireValue;
} }
} }
// Normalisation de l'IMEI // 🔥 IMPORTANT: Normaliser l'IMEI EN PREMIER avant de gérer le stock
// 🔥 Normalisation de l'IMEI (simplifié car _getColumnValue le gère maintenant)
String? imeiValue;
if (mapping.containsKey('imei')) { if (mapping.containsKey('imei')) {
final imei = _cleanValue(_getColumnValue(row, mapping, 'imei')); final imei = _cleanValue(_getColumnValue(row, mapping, 'imei'));
if (imei != null && imei.isNotEmpty) { if (imei != null && imei.isNotEmpty) {
// Suppression des espaces et tirets dans l'IMEI // Nettoyer les espaces et tirets
final imeiValue = imei.replaceAll(RegExp(r'[\s-]'), ''); String cleanedImei = imei.replaceAll(RegExp(r'[\s-]'), '');
if (imeiValue.length >= 15) {
normalizedData['imei'] = imeiValue.substring(0, 15); // Vérifier que c'est bien un IMEI valide (10-15 chiffres)
} else { if (cleanedImei.length >= 10 &&
cleanedImei.length <= 15 &&
RegExp(r'^\d+$').hasMatch(cleanedImei)) {
imeiValue = cleanedImei.length > 15
? cleanedImei.substring(0, 15)
: cleanedImei;
normalizedData['imei'] = imeiValue; normalizedData['imei'] = imeiValue;
print('✅ IMEI valide enregistré: $imeiValue');
} else {
print('⚠️ IMEI invalide ignoré: "$cleanedImei" (longueur: ${cleanedImei.length})');
} }
} }
} }
// Le reste du code reste identique...
// Normalisation du point de vente // Normalisation du point de vente
if (mapping.containsKey('point_de_vente')) { if (mapping.containsKey('point_de_vente')) {
final pv = _cleanValue(_getColumnValue(row, mapping, 'point_de_vente')); final pv = _cleanValue(_getColumnValue(row, mapping, 'point_de_vente'));
if (pv != null && pv.isNotEmpty) { if (pv != null && pv.isNotEmpty) {
// Suppression des espaces superflus normalizedData['point_de_vente'] = pv.replaceAll(RegExp(r'\s+'), ' ').trim();
normalizedData['point_de_vente'] =
pv.replaceAll(RegExp(r'\s+'), ' ').trim();
} }
} }
// Valeurs par défaut // Valeurs par défaut
normalizedData['description'] = ''; // Description toujours vide normalizedData['description'] = '';
// 🎯 LOGIQUE CRITIQUE: Gestion du stock selon la présence d'IMEI
// On vérifie si normalizedData['imei'] existe ET n'est pas vide
if (normalizedData.containsKey('imei') &&
normalizedData['imei'] != null &&
normalizedData['imei'].toString().isNotEmpty) {
// Produit avec IMEI stock forcé à 1
normalizedData['stock'] = 1;
print('🔒 Stock forcé à 1 car IMEI présent: ${normalizedData['imei']}');
} else {
// Produit sans IMEI utiliser le stock du fichier
if (mapping.containsKey('stock')) { if (mapping.containsKey('stock')) {
final stockValue = _cleanValue(_getColumnValue(row, mapping, 'stock')); final stockValue = _cleanValue(_getColumnValue(row, mapping, 'stock'));
final stock = int.tryParse(stockValue ?? '0') ?? 1;
// Try parsing as int first
int? stock = int.tryParse(stockValue ?? '');
// If parsing as int fails, try parsing as double and convert to int
if (stock == null && stockValue != null && stockValue.isNotEmpty) {
final doubleValue = double.tryParse(stockValue);
if (doubleValue != null) {
stock = doubleValue.toInt();
}
}
// Final fallback: ensure at least 1
stock ??= 1;
// Never allow 0 or negative values
normalizedData['stock'] = stock > 0 ? stock : 1; normalizedData['stock'] = stock > 0 ? stock : 1;
print('📦 Stock depuis Excel: $stock (pas d\'IMEI)');
} else { } else {
normalizedData['stock'] = 1; // Valeur par défaut normalizedData['stock'] = 1;
print('📦 Stock par défaut: 1 (pas d\'IMEI, pas de colonne stock)');
}
} }
// Validation des données obligatoires // Validation des données obligatoires
@ -1523,8 +1598,6 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
return normalizedData; return normalizedData;
} }
// Méthode pour mapper les en-têtes aux colonnes (CORRIGÉE)
Map<String, int> _mapHeaders(List<Data?> headerRow) { Map<String, int> _mapHeaders(List<Data?> headerRow) {
Map<String, int> columnMapping = {}; Map<String, int> columnMapping = {};
@ -1533,62 +1606,64 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
String header = headerRow[i]!.value.toString().trim().toUpperCase(); String header = headerRow[i]!.value.toString().trim().toUpperCase();
// Debug : afficher chaque en-tête trouvé print('📋 En-tête colonne $i: "$header"');
print('En-tête trouvé: "$header" à la colonne $i');
// Mapping amélioré pour gérer les variations // Nom du produit
if ((header.contains('NOM') && if ((header.contains('NOM') && header.contains('PRODUIT')) || header == 'NOM') {
(header.contains('PRODUIT') || header.contains('DU'))) ||
header == 'NOM DU PRODUITS' ||
header == 'NOM') {
columnMapping['name'] = i; columnMapping['name'] = i;
print('→ Mappé vers name'); print(' ✅ Mappé vers name');
} else if ((header.contains('REFERENCE') && }
(header.contains('PRODUIT') || header.contains('PRODUITS'))) || // Référence
header == 'REFERENCE PRODUITS' || else if (header.contains('REFERENCE')) {
header == 'REFERENCE') {
columnMapping['reference'] = i; columnMapping['reference'] = i;
print('→ Mappé vers reference'); print(' ✅ Mappé vers reference');
} else if ((header.contains('CATEGORIES') && }
(header.contains('PRODUIT') || header.contains('PRODUITS'))) || // Catégorie - VERSION TOLÉRANTE
header == 'CATEGORIES PRODUITS' || else if (header.contains('CATEG') && header.contains('PRODUIT')) {
header == 'CATEGORIE' ||
header == 'CATEGORY') {
columnMapping['category'] = i; columnMapping['category'] = i;
print('→ Mappé vers category'); print(' ✅ Mappé vers category');
} else if (header == 'MARQUE' || header == 'BRAND') { }
// Marque
else if (header == 'MARQUE' || header == 'BRAND') {
columnMapping['marque'] = i; columnMapping['marque'] = i;
print('→ Mappé vers marque'); print(' ✅ Mappé vers marque');
} else if (header == 'RAM' || header.contains('MEMOIRE RAM')) { }
// RAM
else if (header == 'RAM' || header.contains('MEMOIRE RAM')) {
columnMapping['ram'] = i; columnMapping['ram'] = i;
print('→ Mappé vers ram'); print(' Mappé vers ram');
} else if (header == 'INTERNE' || }
header.contains('MEMOIRE INTERNE') || // Mémoire interne
header.contains('STOCKAGE')) { else if (header == 'INTERNE' || header.contains('MEMOIRE INTERNE') || header.contains('STOCKAGE')) {
columnMapping['memoire_interne'] = i; columnMapping['memoire_interne'] = i;
print('→ Mappé vers memoire_interne'); print(' ✅ Mappé vers memoire_interne');
} else if (header == 'IMEI' || header.contains('NUMERO IMEI')) { }
// IMEI
else if (header == 'IMEI' || header.contains('NUMERO IMEI')) {
columnMapping['imei'] = i; columnMapping['imei'] = i;
print('→ Mappé vers imei'); print(' ✅ Mappé vers imei');
} else if (header == 'PRIX' || header == 'PRICE') { }
// Prix
else if (header == 'PRIX' || header == 'PRICE') {
columnMapping['price'] = i; columnMapping['price'] = i;
print('→ Mappé vers price'); print(' ✅ Mappé vers price');
} else if (header == 'STOCK' || header == 'QUANTITY' || header == 'QTE') { }
// Stock
else if (header == 'STOCK' || header == 'QUANTITY' || header == 'QTE') {
columnMapping['stock'] = i; columnMapping['stock'] = i;
print('→ Mappé vers stock'); print(' Mappé vers stock');
} else if (header == 'BOUTIQUE' || }
header.contains('POINT DE VENTE') || // Point de vente
header == 'MAGASIN') { else if (header.contains('BOUTIQUE') || header.contains('POINT') || header == 'MAGASIN') {
columnMapping['point_de_vente'] = i; columnMapping['point_de_vente'] = i;
print('→ Mappé vers point_de_vente'); print(' ✅ Mappé vers point_de_vente');
} else { }
print('→ Non reconnu'); else {
print(' ⚠️ Non reconnu');
} }
} }
// Debug : afficher le mapping final print('\n🎯 MAPPING FINAL: $columnMapping\n');
print('Mapping final: $columnMapping');
return columnMapping; return columnMapping;
} }
@ -2184,7 +2259,7 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
'RAM', // ram 'RAM', // ram
'INTERNE', // memoire_interne 'INTERNE', // memoire_interne
'IMEI', // imei 'IMEI', // imei
'STOCK' 'STOCK',
'PRIX', // price 'PRIX', // price
'BOUTIQUE', // point_de_vente 'BOUTIQUE', // point_de_vente
]; ];
@ -3255,15 +3330,12 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
}); });
} }
} }
// Méthodes placeholder pour les fonctions manquantes
void _showQRCode(Product product) { void _showQRCode(Product product) {
// État pour contrôler le type d'affichage (true = URL complète, false = référence seulement) RxBool showFullUrl = false.obs;
RxBool showFullUrl = true.obs; RxInt nombreAImprimer = (product.stock ?? 1).obs; // 🔹 Valeur modifiable
Get.dialog( Get.dialog(
Obx(() { Obx(() {
// Données du QR code selon l'état
final qrData = showFullUrl.value final qrData = showFullUrl.value
? 'https://stock.guycom.mg/${product.reference}' ? 'https://stock.guycom.mg/${product.reference}'
: product.reference!; : product.reference!;
@ -3282,11 +3354,11 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
], ],
), ),
content: Container( content: Container(
width: 300, width: 350,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// Bouton pour basculer entre URL et référence // Bouton bascule URL / Référence
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () { onPressed: () {
showFullUrl.value = !showFullUrl.value; showFullUrl.value = !showFullUrl.value;
@ -3296,19 +3368,18 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
size: 16, size: 16,
), ),
label: Text( label: Text(
showFullUrl.value ? 'URL/Référence' : 'Référence', showFullUrl.value ? 'URL Complète' : 'Référence Seulement',
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor: showFullUrl.value ? Colors.blue : Colors.green,
showFullUrl.value ? Colors.blue : Colors.green,
foregroundColor: Colors.white, foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 36), minimumSize: const Size(double.infinity, 40),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Container du QR Code // QR Code affiché
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -3321,11 +3392,12 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
version: QrVersions.auto, version: QrVersions.auto,
size: 200, size: 200,
backgroundColor: Colors.white, backgroundColor: Colors.white,
errorCorrectionLevel: QrErrorCorrectLevel.M,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Affichage des données actuelles // Informations du produit
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -3334,18 +3406,60 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
), ),
child: Column( child: Column(
children: [ children: [
Text( Row(
showFullUrl.value children: [
? 'URL Complète' Icon(Icons.inventory_2, size: 16, color: Colors.grey.shade600),
: 'Référence Seulement', const SizedBox(width: 8),
style: const TextStyle(fontWeight: FontWeight.bold), Expanded(
child: Text(
product.name,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
), ),
const SizedBox(height: 4), ),
],
),
const SizedBox(height: 8),
// 🔹 Nouveau champ : nombre à imprimer
Row(
children: [
const Icon(Icons.format_list_numbered, size: 16, color: Colors.deepPurple),
const SizedBox(width: 8),
Expanded(
child: TextField(
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Nombre d\'étiquettes à imprimer',
border: const OutlineInputBorder(),
isDense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
),
controller: TextEditingController(text: nombreAImprimer.value.toString()),
onChanged: (val) {
final parsed = int.tryParse(val);
if (parsed != null && parsed > 0) {
nombreAImprimer.value = parsed;
}
},
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
Icon(Icons.label, size: 16, color: Colors.orange.shade600),
const SizedBox(width: 8),
Text( Text(
qrData, 'Format: Étiquette Niimbot B1 (50x15mm)',
style: style: TextStyle(
const TextStyle(fontSize: 12, color: Colors.grey), fontSize: 12,
textAlign: TextAlign.center, color: Colors.orange.shade700,
fontWeight: FontWeight.w600,
),
),
],
), ),
], ],
), ),
@ -3354,23 +3468,72 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
), ),
), ),
actions: [ actions: [
TextButton( // Copier
TextButton.icon(
onPressed: () { onPressed: () {
Clipboard.setData(ClipboardData(text: qrData)); Clipboard.setData(ClipboardData(text: qrData));
Get.back();
Get.snackbar( Get.snackbar(
'Copié', 'Copié',
'${showFullUrl.value ? "URL" : "Référence"} copiée dans le presse-papiers', '${showFullUrl.value ? "URL" : "Référence"} copiée',
backgroundColor: Colors.green, backgroundColor: Colors.green,
colorText: Colors.white, colorText: Colors.white,
duration: const Duration(seconds: 2),
icon: const Icon(Icons.check_circle, color: Colors.white),
); );
}, },
child: Text('Copier ${showFullUrl.value ? "URL" : "Référence"}'), icon: const Icon(Icons.copy, size: 18),
label: const Text('Copier'),
), ),
TextButton(
onPressed: () => _generatePDF(product, qrData), // Paramètres
child: const Text('Imprimer en PDF'), TextButton.icon(
onPressed: () async {
Get.back();
pdfService.showNiimbotSettingsDialog();
},
icon: const Icon(Icons.settings, size: 18),
label: const Text('Paramètres'),
style: TextButton.styleFrom(foregroundColor: Colors.blue.shade700),
), ),
// 🔹 Imprimer selon le nombre choisi
ElevatedButton.icon(
onPressed: () async {
Get.back();
final int n = nombreAImprimer.value;
for (int i = 0; i < n; i++) {
await pdfService.printQrNiimbotOptimized(
qrData,
productName: null,
reference: product.reference ?? '',
leftPadding: 1.0,
topPadding: 0.5,
qrSize: 12.0,
fontSize: 5.0,
labelSize: NiimbotLabelSize.small,
);
await Future.delayed(const Duration(milliseconds: 100));
}
Get.snackbar(
'Impression terminée',
'$n étiquette${n > 1 ? "s" : ""} imprimée${n > 1 ? "s" : ""}',
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 3),
);
},
icon: const Icon(Icons.print, size: 18),
label: const Text('Imprimer'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange.shade600,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
),
// Fermer
TextButton( TextButton(
onPressed: () => Get.back(), onPressed: () => Get.back(),
child: const Text('Fermer'), child: const Text('Fermer'),
@ -3380,7 +3543,6 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
}), }),
); );
} }
Future<void> _generatePDF(Product product, String qrUrl) async { Future<void> _generatePDF(Product product, String qrUrl) async {
final pdf = pw.Document(); final pdf = pw.Document();
@ -3439,9 +3601,9 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
List<Map<String, dynamic>> pointsDeVente = []; List<Map<String, dynamic>> pointsDeVente = [];
bool isLoadingPoints = true; bool isLoadingPoints = true;
// Initialiser la catégorie sélectionnée de manière sécurisée // Initialiser la catégorie sélectionnée de manière sécurisée
String selectedCategory = _predefinedCategories.contains(product.category) String selectedCategory = _availableCategories.contains(product.category)
? product.category ? product.category
: _predefinedCategories.last; // 'Non catégorisé' par défaut : _availableCategories.last; // 'Non catégorisé' par défaut
File? pickedImage; File? pickedImage;
String? qrPreviewData; String? qrPreviewData;
bool showAddNewPoint = false; bool showAddNewPoint = false;
@ -3809,7 +3971,7 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
// Catégorie avec gestion des valeurs non présentes // Catégorie avec gestion des valeurs non présentes
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
value: selectedCategory, value: selectedCategory,
items: _predefinedCategories items: _availableCategories
.map((category) => DropdownMenuItem( .map((category) => DropdownMenuItem(
value: category, child: Text(category))) value: category, child: Text(category)))
.toList(), .toList(),

10
lib/Views/commandManagement.dart

@ -163,11 +163,11 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
await _showCashPaymentDialog(commande, selectedPayment.amountGiven); await _showCashPaymentDialog(commande, selectedPayment.amountGiven);
} }
await _updateStatut( // await _updateStatut(
commande.id!, // commande.id!,
StatutCommande.confirmee, // StatutCommande.confirmee,
validateurId: userController.userId, // validateurId: userController.userId,
); // );
await _generateReceipt(commande, selectedPayment); await _generateReceipt(commande, selectedPayment);
} }

218
lib/Views/demande_sortie_personnelle_page.dart

@ -1,11 +1,18 @@
// Importations nécessaires
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:image/image.dart' as img;
import 'package:zxing2/qrcode.dart';
import 'package:camera/camera.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/app_bar.dart';
import 'package:youmazgestion/Components/appDrawer.dart'; import 'package:youmazgestion/Components/appDrawer.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import 'package:youmazgestion/controller/userController.dart'; import 'package:youmazgestion/controller/userController.dart';
import '../Models/produit.dart'; import '../Models/produit.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class DemandeSortiePersonnellePage extends StatefulWidget { class DemandeSortiePersonnellePage extends StatefulWidget {
const DemandeSortiePersonnellePage({super.key}); const DemandeSortiePersonnellePage({super.key});
@ -54,55 +61,200 @@ class _DemandeSortiePersonnellePageState
_loadProducts(); _loadProducts();
_searchController.addListener(_filterProducts); _searchController.addListener(_filterProducts);
} }
/// ----------------- SCAN QR CODE -----------------
CameraController? _cameraController;
bool _isScanning = false;
Future<void> _scanQrOrBarcode() async {
if (defaultTargetPlatform == TargetPlatform.windows) {
final cameras = await availableCameras();
if (cameras.isEmpty) {
_showErrorSnackbar("Aucune caméra détectée");
return;
}
// Disposer l'ancien contrôleur
await _cameraController?.dispose();
_cameraController = CameraController(
cameras.first,
ResolutionPreset.high, // Meilleure résolution pour QR
enableAudio: false,
);
try {
await _cameraController!.initialize();
} catch (e) {
_showErrorSnackbar("Erreur initialisation caméra: $e");
return;
}
_isScanning = true;
Future<void> scanLoop() async {
// Attendre que le dialog soit affiché
await Future.delayed(const Duration(milliseconds: 300));
while (_isScanning && _cameraController != null) {
try {
final XFile file = await _cameraController!.takePicture();
final bytes = await file.readAsBytes();
// Décoder l'image
final imageDecoded = img.decodeImage(bytes);
if (imageDecoded == null) {
print("❌ Image non décodée");
await Future.delayed(const Duration(milliseconds: 800));
continue;
}
print("✅ Image décodée: ${imageDecoded.width}x${imageDecoded.height}");
// CORRECTION CRITIQUE: Convertir en format RGB correct
final rgbBytes = <int>[];
for (int y = 0; y < imageDecoded.height; y++) {
for (int x = 0; x < imageDecoded.width; x++) {
final pixel = imageDecoded.getPixel(x, y);
rgbBytes.add(pixel.r.toInt());
rgbBytes.add(pixel.g.toInt());
rgbBytes.add(pixel.b.toInt());
}
}
// Créer Int32List pour ZXing
final int32Data = Int32List(imageDecoded.width * imageDecoded.height);
for (int i = 0; i < imageDecoded.height; i++) {
for (int j = 0; j < imageDecoded.width; j++) {
final idx = i * imageDecoded.width + j;
final rgbIdx = idx * 3;
final r = rgbBytes[rgbIdx];
final g = rgbBytes[rgbIdx + 1];
final b = rgbBytes[rgbIdx + 2];
// Format ARGB
int32Data[idx] = (0xFF << 24) | (r << 16) | (g << 8) | b;
}
}
final luminanceSource = RGBLuminanceSource(
imageDecoded.width,
imageDecoded.height,
int32Data,
);
final bitmap = BinaryBitmap(HybridBinarizer(luminanceSource));
final reader = QRCodeReader();
try {
final result = reader.decode(bitmap);
if (result.text.isNotEmpty) {
print("✅ QR Code détecté: ${result.text}");
_isScanning = false;
if (mounted && Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
setState(() {
_searchController.text = result.text;
});
_filterProducts();
_showSuccessSnackbar("QR Code détecté : ${result.text}");
break;
}
} on NotFoundException catch (_) {
// Pas de QR trouvé dans cette frame
print("⚠️ Pas de QR trouvé");
} catch (e) {
print("❌ Erreur décodage QR: $e");
}
} catch (e) {
print("❌ Erreur capture: $e");
}
// Délai plus long pour éviter la surcharge
await Future.delayed(const Duration(milliseconds: 800));
}
}
// Lancer la boucle AVANT d'afficher le dialog
final scanFuture = scanLoop();
void _scanQrOrBarcode() async {
await showDialog( await showDialog(
context: context, context: context,
barrierDismissible: true,
builder: (context) { builder: (context) {
return AlertDialog( return WillPopScope(
content: Container( onWillPop: () async {
_isScanning = false;
return true;
},
child: AlertDialog(
title: const Text('Scanner le QR Code'),
content: SizedBox(
width: 640,
height: 480,
child: _cameraController != null &&
_cameraController!.value.isInitialized
? CameraPreview(_cameraController!)
: const Center(child: CircularProgressIndicator()),
),
actions: [
TextButton(
onPressed: () {
_isScanning = false;
Navigator.of(context).pop();
},
child: const Text('Annuler'),
),
],
),
);
},
);
_isScanning = false;
await scanFuture; // Attendre la fin de la boucle
await _cameraController?.dispose();
_cameraController = null;
return;
}
// 📱 Mobile et macOS mobile_scanner
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Scanner le QR Code'),
content: SizedBox(
width: double.maxFinite, width: double.maxFinite,
height: 400, height: 400,
child: MobileScanner( child: MobileScanner(
onDetect: (BarcodeCapture barcodeCap) { onDetect: (capture) {
print("BarcodeCapture: $barcodeCap"); final List<Barcode> barcodes = capture.barcodes;
// Now accessing the barcodes attribute
final List<Barcode> barcodes = barcodeCap.barcodes;
if (barcodes.isNotEmpty) { if (barcodes.isNotEmpty) {
// Get the first detected barcode value final scanResult = barcodes.first.rawValue;
String? scanResult = barcodes.first.rawValue;
print("Scanned Result: $scanResult");
if (scanResult != null && scanResult.isNotEmpty) { if (scanResult != null && scanResult.isNotEmpty) {
setState(() {
_searchController.text = scanResult;
print(
"Updated Search Controller: ${_searchController.text}");
});
// Close dialog after scanning
Navigator.of(context).pop(); Navigator.of(context).pop();
setState(() => _searchController.text = scanResult);
// Refresh product list based on new search input
_filterProducts(); _filterProducts();
} else { _showSuccessSnackbar("QR Code détecté : $scanResult");
print("Scan result was empty or null.");
Navigator.of(context).pop();
} }
} else {
print("No barcodes detected.");
Navigator.of(context).pop();
} }
}, },
), ),
), ),
); actions: [
}, TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Annuler'),
),
],
),
); );
} }
/// ----------------- FILTRAGE PRODUITS -----------------
void _filterProducts() { void _filterProducts() {
final query = _searchController.text.toLowerCase(); final query = _searchController.text.toLowerCase();
setState(() { setState(() {
@ -825,7 +977,3 @@ class _DemandeSortiePersonnellePageState
super.dispose(); super.dispose();
} }
} }
extension on BarcodeCapture {
get rawValue => null;
}

13
lib/config/DatabaseConfig.dart

@ -5,13 +5,18 @@ import 'dart:async';
class DatabaseConfig { class DatabaseConfig {
// Local MySQL settings // Local MySQL settings
static const String localHost = '192.168.88.73'; static const String localHost = '192.168.88.3';
static const String localUsername = 'guycom'; static const String localUsername = 'guycom';
static const String? localPassword = '3iV59wjRdbuXAPR'; static const String? localPassword = '3iV59wjRdbuXAPR';
static const String localDatabase = 'guycom'; static const String localDatabase = 'guycom';
// static const String localHost = 'localhost';
// static const String localUsername = 'root';
// static const String? localPassword = null;
// static const String localDatabase = 'guycom';
// Production (public) MySQL settings // Production (public) MySQL settings
static const String prodHost = '102.17.52.31'; static const String prodHost = '102.16.56.177';
// static const String prodHost = '185.70.105.157';
static const String prodUsername = 'guycom'; static const String prodUsername = 'guycom';
static const String prodPassword = '3iV59wjRdbuXAPR'; static const String prodPassword = '3iV59wjRdbuXAPR';
static const String prodDatabase = 'guycom'; static const String prodDatabase = 'guycom';
@ -23,7 +28,7 @@ class DatabaseConfig {
static const int maxConnections = 10; static const int maxConnections = 10;
static const int minConnections = 2; static const int minConnections = 2;
static bool get isDevelopment => false; static bool get isDevelopment => true;
/// Build config map for connection /// Build config map for connection
static Map<String, dynamic> _buildConfig({ static Map<String, dynamic> _buildConfig({
@ -80,7 +85,7 @@ class DatabaseConfig {
config['database']?.toString().isNotEmpty == true && config['database']?.toString().isNotEmpty == true &&
config['user'] != null; config['user'] != null;
} catch (e) { } catch (e) {
// print("Erreur de validation de la configuration: $e"); print("Erreur de validation de la configuration: $e");
return false; return false;
} }
} }

8
linux/flutter/generated_plugin_registrant.cc

@ -6,23 +6,23 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <charset_converter/charset_converter_plugin.h>
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <open_file_linux/open_file_linux_plugin.h> #include <open_file_linux/open_file_linux_plugin.h>
#include <printing/printing_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h> #include <screen_retriever/screen_retriever_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h> #include <window_manager/window_manager_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) charset_converter_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "CharsetConverterPlugin");
charset_converter_plugin_register_with_registrar(charset_converter_registrar);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar); file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) open_file_linux_registrar = g_autoptr(FlPluginRegistrar) open_file_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin");
open_file_linux_plugin_register_with_registrar(open_file_linux_registrar); open_file_linux_plugin_register_with_registrar(open_file_linux_registrar);
g_autoptr(FlPluginRegistrar) printing_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin");
printing_plugin_register_with_registrar(printing_registrar);
g_autoptr(FlPluginRegistrar) screen_retriever_registrar = g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);

2
linux/flutter/generated_plugins.cmake

@ -3,9 +3,9 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
charset_converter
file_selector_linux file_selector_linux
open_file_linux open_file_linux
printing
screen_retriever screen_retriever
url_launcher_linux url_launcher_linux
window_manager window_manager

2
macos/Flutter/GeneratedPluginRegistrant.swift

@ -10,6 +10,7 @@ import file_selector_macos
import mobile_scanner import mobile_scanner
import open_file_mac import open_file_mac
import path_provider_foundation import path_provider_foundation
import printing
import screen_retriever import screen_retriever
import shared_preferences_foundation import shared_preferences_foundation
import url_launcher_macos import url_launcher_macos
@ -21,6 +22,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))

132
pubspec.lock

@ -49,6 +49,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.9" version: "2.2.9"
bidi:
dependency: transitive
description:
name: bidi
sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d"
url: "https://pub.dev"
source: hosted
version: "2.0.13"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -97,6 +105,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.5" version: "0.3.5"
camera_windows:
dependency: "direct main"
description:
name: camera_windows
sha256: c4339d71bc4256993f5c8ae2f3355463d830a5cb52851409ab1c627401c69811
url: "https://pub.dev"
source: hosted
version: "0.2.6+2"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -105,22 +121,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
charset_converter: charcode:
dependency: transitive dependency: transitive
description: description:
name: charset_converter name: charcode
sha256: a601f27b78ca86c3d88899d53059786d9c3f3c485b64974e9105c06c2569aef5 sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "1.4.0"
cli_util: cli_util:
dependency: transitive dependency: transitive
description: description:
name: cli_util name: cli_util
sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c" sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.5" version: "0.4.2"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -161,14 +177,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.6" version: "3.0.6"
csslib:
dependency: transitive
description:
name: csslib
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -193,22 +201,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.7" version: "2.0.7"
esc_pos_printer:
dependency: "direct main"
description:
name: esc_pos_printer
sha256: "312b05f909f3f7dd1e6a3332cf384dcee2c3a635138823654cd9c0133d8b5c45"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
esc_pos_utils:
dependency: "direct main"
description:
name: esc_pos_utils
sha256: "8ec0013d7a7f1e790ced6b09b95ce3bf2c6f9468a3e2bc49ece000761d86c6f8"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
excel: excel:
dependency: "direct main" dependency: "direct main"
description: description:
@ -281,6 +273,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.3+4" version: "0.9.3+4"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
fl_chart: fl_chart:
dependency: "direct main" dependency: "direct main"
description: description:
@ -376,14 +376,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.8.0" version: "10.8.0"
gbk_codec:
dependency: transitive
description:
name: gbk_codec
sha256: "3af5311fc9393115e3650ae6023862adf998051a804a08fb804f042724999f61"
url: "https://pub.dev"
source: hosted
version: "0.4.0"
get: get:
dependency: "direct main" dependency: "direct main"
description: description:
@ -396,10 +388,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: get_it name: get_it
sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1 sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.7.0" version: "8.2.0"
google_fonts: google_fonts:
dependency: transitive dependency: transitive
description: description:
@ -416,22 +408,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.2" version: "5.1.2"
hex:
dependency: transitive
description:
name: hex
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
html:
dependency: transitive
description:
name: html
sha256: "9475be233c437f0e3637af55e7702cbbe5c23a68bd56e8a5fa2d426297b7c6c8"
url: "https://pub.dev"
source: hosted
version: "0.15.5+1"
http: http:
dependency: transitive dependency: transitive
description: description:
@ -460,10 +436,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: image name: image
sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6" sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.0" version: "4.3.0"
image_picker: image_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@ -660,18 +636,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: mobile_scanner name: mobile_scanner
sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760 sha256: "54005bdea7052d792d35b4fef0f84ec5ddc3a844b250ecd48dc192fb9b4ebc95"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.2.3" version: "7.0.1"
msix: msix:
dependency: "direct main" dependency: "direct main"
description: description:
name: msix name: msix
sha256: e3de4d9f52543ad6e4b0f534991e1303cbd379d24be28dd241ac60bd9439a201 sha256: f88033fcb9e0dd8de5b18897cbebbd28ea30596810f4a7c86b12b0c03ace87e5
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.0" version: "3.16.12"
mysql1: mysql1:
dependency: "direct main" dependency: "direct main"
description: description:
@ -844,10 +820,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: pdf name: pdf
sha256: "10659b915e65832b106f6d1d213e09b789cc1f24bf282ee911e49db35b96be4d" sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.8.4" version: "3.11.3"
pdf_widget_wrapper:
dependency: transitive
description:
name: pdf_widget_wrapper
sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5
url: "https://pub.dev"
source: hosted
version: "1.0.4"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -888,6 +872,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.1" version: "1.5.1"
printing:
dependency: "direct main"
description:
name: printing
sha256: "482cd5a5196008f984bb43ed0e47cbfdca7373490b62f3b27b3299275bf22a93"
url: "https://pub.dev"
source: hosted
version: "5.14.2"
provider: provider:
dependency: transitive dependency: transitive
description: description:
@ -1254,7 +1246,7 @@ packages:
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
win32: win32:
dependency: transitive dependency: "direct main"
description: description:
name: win32 name: win32
sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
@ -1293,6 +1285,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
zxing2:
dependency: "direct main"
description:
name: zxing2
sha256: "2677c49a3b9ca9457cb1d294fd4bd5041cac6aab8cdb07b216ba4e98945c684f"
url: "https://pub.dev"
source: hosted
version: "0.2.4"
sdks: sdks:
dart: ">=3.7.0 <4.0.0" dart: ">=3.7.0 <4.0.0"
flutter: ">=3.27.0" flutter: ">=3.29.0"

13
pubspec.yaml

@ -44,10 +44,12 @@ dependencies:
sqflite_common_ffi: ^2.2.5 sqflite_common_ffi: ^2.2.5
quantity_input: ^1.0.2 quantity_input: ^1.0.2
grouped_list: ^5.1.2 grouped_list: ^5.1.2
esc_pos_printer: ^4.0.1 # esc_pos_printer: ^4.0.1
esc_pos_utils: ^1.1.0 win32: ^5.12.0
# esc_pos_utils: ^1.1.0
printing: ^5.10.0
flutter_login: ^4.1.1 flutter_login: ^4.1.1
image: ^3.0.2 image: ^4.3.0
logging: ^1.2.0 logging: ^1.2.0
msix: ^3.7.0 msix: ^3.7.0
flutter_charts: ^0.5.1 flutter_charts: ^0.5.1
@ -63,13 +65,14 @@ dependencies:
path_provider: ^2.0.15 path_provider: ^2.0.15
shared_preferences: ^2.2.2 shared_preferences: ^2.2.2
excel: ^2.0.1 excel: ^2.0.1
mobile_scanner: ^5.0.0 mobile_scanner: ^7.0.1
fl_chart: ^0.65.0 fl_chart: ^0.65.0
numbers_to_letters: ^1.0.0 numbers_to_letters: ^1.0.0
qr_code_scanner_plus: ^2.0.10+1 qr_code_scanner_plus: ^2.0.10+1
window_manager: ^0.3.7 window_manager: ^0.3.7
camera: ^0.10.5+9 camera: ^0.10.5+9
zxing2: ^0.2.1
camera_windows: ^0.2.6+2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter

9
windows/flutter/generated_plugin_registrant.cc

@ -6,17 +6,20 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <charset_converter/charset_converter_plugin.h> #include <camera_windows/camera_windows.h>
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <printing/printing_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h> #include <screen_retriever/screen_retriever_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
#include <window_manager/window_manager_plugin.h> #include <window_manager/window_manager_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
CharsetConverterPluginRegisterWithRegistrar( CameraWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("CharsetConverterPlugin")); registry->GetRegistrarForPlugin("CameraWindows"));
FileSelectorWindowsRegisterWithRegistrar( FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows")); registry->GetRegistrarForPlugin("FileSelectorWindows"));
PrintingPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PrintingPlugin"));
ScreenRetrieverPluginRegisterWithRegistrar( ScreenRetrieverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(

3
windows/flutter/generated_plugins.cmake

@ -3,8 +3,9 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
charset_converter camera_windows
file_selector_windows file_selector_windows
printing
screen_retriever screen_retriever
url_launcher_windows url_launcher_windows
window_manager window_manager

Loading…
Cancel
Save