commit de 18072025
This commit is contained in:
parent
13296529c3
commit
d043f6f20b
@ -92,6 +92,8 @@ class Commande {
|
||||
final String? clientNom;
|
||||
final String? clientPrenom;
|
||||
final String? clientEmail;
|
||||
final int? pointDeVenteId;
|
||||
final String? pointDeVenteDesign;
|
||||
|
||||
Commande({
|
||||
this.id,
|
||||
@ -106,6 +108,8 @@ class Commande {
|
||||
this.clientNom,
|
||||
this.clientPrenom,
|
||||
this.clientEmail,
|
||||
this.pointDeVenteId,
|
||||
this.pointDeVenteDesign,
|
||||
});
|
||||
|
||||
String get clientNomComplet {
|
||||
@ -156,6 +160,8 @@ class Commande {
|
||||
clientNom: map['clientNom'] as String?,
|
||||
clientPrenom: map['clientPrenom'] as String?,
|
||||
clientEmail: map['clientEmail'] as String?,
|
||||
pointDeVenteId: map['pointDeVenteId'] as int?,
|
||||
pointDeVenteDesign: map['pointDeVenteDesign'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -597,6 +597,31 @@ class AppDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
Future<double> getValeurTotaleStock() async {
|
||||
final db = await database;
|
||||
|
||||
try {
|
||||
// Récupérer tous les produits
|
||||
final List<Map<String, dynamic>> products = (await db.query('products')) as List<Map<String, dynamic>>;
|
||||
|
||||
double valeurTotale = 0.0;
|
||||
for (final product in products) {
|
||||
final stock = (product['stock'] as num?)?.toDouble();
|
||||
final price = (product['price'] as num?)?.toDouble();
|
||||
|
||||
// Vérifier que stock et price ne sont pas null
|
||||
if (stock != null && price != null) {
|
||||
valeurTotale += (stock * price);
|
||||
}
|
||||
}
|
||||
|
||||
return valeurTotale;
|
||||
} catch (e) {
|
||||
print('Erreur lors du calcul de la valeur totale du stock: $e');
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// --- STATISTIQUES ---
|
||||
|
||||
Future<Map<String, dynamic>> getStatistiques() async {
|
||||
@ -807,9 +832,16 @@ class AppDatabase {
|
||||
Future<List<Commande>> getCommandes() async {
|
||||
final db = await database;
|
||||
final result = await db.query('''
|
||||
SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail
|
||||
SELECT c.*,
|
||||
cl.nom AS clientNom,
|
||||
cl.prenom AS clientPrenom,
|
||||
cl.email AS clientEmail,
|
||||
u.point_de_vente_id AS pointDeVenteId,
|
||||
pv.nom AS pointDeVenteDesign
|
||||
FROM commandes c
|
||||
LEFT JOIN clients cl ON c.clientId = cl.id
|
||||
LEFT JOIN users u ON c.commandeurId = u.id
|
||||
LEFT JOIN points_de_vente pv ON u.point_de_vente_id = pv.id
|
||||
ORDER BY c.dateCommande DESC
|
||||
''');
|
||||
return result.map((row) => Commande.fromMap(row.fields)).toList();
|
||||
|
||||
@ -7,6 +7,7 @@ import 'package:youmazgestion/controller/userController.dart';
|
||||
import 'package:youmazgestion/Models/users.dart';
|
||||
import 'package:youmazgestion/Models/client.dart';
|
||||
import 'package:youmazgestion/Models/produit.dart'; // Ajout de l'import manquant
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class DashboardPage extends StatefulWidget {
|
||||
@override
|
||||
@ -14,6 +15,8 @@ class DashboardPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DashboardPageState extends State<DashboardPage> with SingleTickerProviderStateMixin {
|
||||
DateTimeRange? _dateRange;
|
||||
bool _showOnlyToday = false;
|
||||
final AppDatabase _database = AppDatabase.instance;
|
||||
final UserController _userController = Get.find<UserController>();
|
||||
final GlobalKey _recentClientsKey = GlobalKey();
|
||||
@ -60,8 +63,8 @@ void initState() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _loadData() {
|
||||
_statsFuture = _database.getStatistiques();
|
||||
void _loadData() {
|
||||
_statsFuture = _database.getStatistiques(); // Plus besoin de calcul supplémentaire
|
||||
_recentOrdersFuture = _database.getCommandes().then((orders) => orders.take(5).toList());
|
||||
_lowStockProductsFuture = _database.getProducts().then((products) {
|
||||
return products.where((p) => (p.stock ?? 0) < 10).toList();
|
||||
@ -69,7 +72,7 @@ void initState() {
|
||||
_recentClientsFuture = _database.getClients().then((clients) => clients.take(5).toList());
|
||||
_allOrdersFuture = _database.getCommandes();
|
||||
_productsByCategoryFuture = _database.getProductCountByCategory();
|
||||
}
|
||||
}
|
||||
Future<void> _showCategoryProductsDialog(String category) async {
|
||||
final products = await _database.getProductsByCategory(category);
|
||||
|
||||
@ -90,7 +93,7 @@ Future<void> _showCategoryProductsDialog(String category) async {
|
||||
: CircleAvatar(child: Icon(Icons.inventory)),
|
||||
title: Text(product.name),
|
||||
subtitle: Text('Stock: ${product.stock}'),
|
||||
trailing: Text('${product.price.toStringAsFixed(2)} MGA'),
|
||||
trailing: Text('${NumberFormat('#,##0', 'fr_FR').format(product.price)} MGA'),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -417,7 +420,7 @@ Future<void> _showCategoryProductsDialog(String category) async {
|
||||
final month = salesData[groupIndex]['month'];
|
||||
final total = salesData[groupIndex]['total'];
|
||||
return BarTooltipItem(
|
||||
'$month\n${total.toStringAsFixed(2)} MGA',
|
||||
'$month\n${NumberFormat('#,##0', 'fr_FR').format(total)} MGA',
|
||||
TextStyle(color: Colors.white),
|
||||
);
|
||||
},
|
||||
@ -716,54 +719,60 @@ Future<void> _showCategoryProductsDialog(String category) async {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMiniStatistics() {
|
||||
return FutureBuilder<Map<String, dynamic>>(
|
||||
future: _statsFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
Widget _buildMiniStatistics() {
|
||||
return FutureBuilder<Map<String, dynamic>>(
|
||||
future: _statsFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return Text('Erreur de chargement des statistiques');
|
||||
}
|
||||
if (snapshot.hasError) {
|
||||
return Text('Erreur de chargement des statistiques');
|
||||
}
|
||||
|
||||
final stats = snapshot.data ?? {};
|
||||
|
||||
return Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
_buildMiniStatCard(
|
||||
title: 'Clients',
|
||||
value: '${stats['totalClients'] ?? 0}',
|
||||
icon: Icons.people,
|
||||
color: Colors.blue,
|
||||
),
|
||||
_buildMiniStatCard(
|
||||
title: 'Commandes',
|
||||
value: '${stats['totalCommandes'] ?? 0}',
|
||||
icon: Icons.shopping_cart,
|
||||
color: Colors.green,
|
||||
),
|
||||
_buildMiniStatCard(
|
||||
title: 'Produits',
|
||||
value: '${stats['totalProduits'] ?? 0}',
|
||||
icon: Icons.inventory,
|
||||
color: Colors.orange,
|
||||
),
|
||||
_buildMiniStatCard(
|
||||
title: 'CA (MGA)',
|
||||
value: '${(stats['chiffreAffaires'] ?? 0.0).toStringAsFixed(2)}',
|
||||
icon: Icons.euro_symbol,
|
||||
color: Colors.purple,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
final stats = snapshot.data ?? {};
|
||||
|
||||
return Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
_buildMiniStatCard(
|
||||
title: 'Clients',
|
||||
value: '${stats['totalClients'] ?? 0}',
|
||||
icon: Icons.people,
|
||||
color: Colors.blue,
|
||||
),
|
||||
_buildMiniStatCard(
|
||||
title: 'Commandes',
|
||||
value: '${stats['totalCommandes'] ?? 0}',
|
||||
icon: Icons.shopping_cart,
|
||||
color: Colors.green,
|
||||
),
|
||||
_buildMiniStatCard(
|
||||
title: 'Produits',
|
||||
value: '${stats['totalProduits'] ?? 0}',
|
||||
icon: Icons.inventory,
|
||||
color: Colors.orange,
|
||||
),
|
||||
_buildMiniStatCard(
|
||||
title: 'CA (MGA)',
|
||||
value: NumberFormat('#,##0', 'fr_FR').format(stats['chiffreAffaires'] ?? 0.0),
|
||||
icon: Icons.euro_symbol,
|
||||
color: Colors.purple,
|
||||
),
|
||||
// ✅ NOUVELLE CARTE : Valeur totale du stock
|
||||
_buildMiniStatCard(
|
||||
title: 'Valeur Stock (MGA)',
|
||||
value: NumberFormat('#,##0', 'fr_FR').format(stats['valeurTotaleStock'] ?? 0.0),
|
||||
icon: Icons.inventory_2,
|
||||
color: Colors.teal,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _buildMiniStatCard({required String title, required String value, required IconData icon, required Color color}) {
|
||||
@ -952,7 +961,7 @@ Future<void> _showCategoryProductsDialog(String category) async {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${order.montantTotal.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(order.montantTotal)} MGA',
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
Text(
|
||||
@ -1160,7 +1169,7 @@ Widget _buildVentesParPointDeVenteCard() {
|
||||
final ca = pointVente['chiffre_affaires'] ?? 0.0;
|
||||
final nbCommandes = pointVente['nombre_commandes'] ?? 0;
|
||||
return BarTooltipItem(
|
||||
'${pointVente['point_vente_nom']}\n${ca.toStringAsFixed(2)} MGA\n$nbCommandes commandes',
|
||||
'${pointVente['point_vente_nom']}\n${NumberFormat('#,##0', 'fr_FR').format(ca)} MGA\n$nbCommandes commandes',
|
||||
TextStyle(color: Colors.white, fontSize: 12),
|
||||
);
|
||||
},
|
||||
@ -1333,7 +1342,9 @@ Widget _buildTableauVentesPointDeVente(List<Map<String, dynamic>> ventesData) {
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
'${((data['chiffre_affaires'] as num?)?.toDouble() ?? 0.0).toStringAsFixed(2)}',
|
||||
NumberFormat('#,##0.00', 'fr_FR').format(
|
||||
(data['chiffre_affaires'] as num?)?.toDouble() ?? 0.0,
|
||||
),
|
||||
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
@ -1354,7 +1365,9 @@ Widget _buildTableauVentesPointDeVente(List<Map<String, dynamic>> ventesData) {
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
'${((data['panier_moyen'] as num?)?.toDouble() ?? 0.0).toStringAsFixed(2)}',
|
||||
NumberFormat('#,##0.00', 'fr_FR').format(
|
||||
(data['panier_moyen'] as num?)?.toDouble() ?? 0.0,
|
||||
),
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
@ -1403,80 +1416,159 @@ String _formatCurrency(double value) {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _selectDateRange(BuildContext context) async {
|
||||
final DateTimeRange? picked = await showDateRangePicker(
|
||||
context: context,
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime.now().add(const Duration(days: 365)),
|
||||
initialDateRange: _dateRange ??
|
||||
DateTimeRange(
|
||||
start: DateTime.now().subtract(const Duration(days: 30)),
|
||||
end: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
_dateRange = picked;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _toggleTodayFilter() {
|
||||
setState(() {
|
||||
_showOnlyToday = !_showOnlyToday;
|
||||
});
|
||||
}
|
||||
|
||||
void _showPointVenteDetails(Map<String, dynamic> pointVenteData) async {
|
||||
final isMobile = MediaQuery.of(context).size.width < 600;
|
||||
final pointVenteId = pointVenteData['point_vente_id'] as int;
|
||||
final pointVenteNom = pointVenteData['point_vente_nom'] as String;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Détails - $pointVenteNom'),
|
||||
content: Container(
|
||||
width: double.maxFinite,
|
||||
height: 500,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: _toggleTodayFilter,
|
||||
icon: Icon(
|
||||
_showOnlyToday ? Icons.today : Icons.calendar_today,
|
||||
size: 20,
|
||||
),
|
||||
label: Text(_showOnlyToday
|
||||
? isMobile
|
||||
? 'Toutes dates'
|
||||
: 'Toutes les dates'
|
||||
: isMobile
|
||||
? 'Aujourd\'hui'
|
||||
: 'Aujourd\'hui seulement'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _showOnlyToday
|
||||
? Colors.green.shade600
|
||||
: Colors.blue.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: isMobile ? 12 : 16,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _selectDateRange(context),
|
||||
icon: const Icon(Icons.date_range, size: 20),
|
||||
label: Text(_dateRange != null
|
||||
? isMobile
|
||||
? 'Période'
|
||||
: 'Période sélectionnée'
|
||||
: isMobile
|
||||
? 'Période'
|
||||
: 'Choisir période'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _dateRange != null
|
||||
? Colors.orange.shade600
|
||||
: Colors.grey.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: isMobile ? 12 : 16,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Statistiques générales
|
||||
_buildStatRow('Chiffre d\'affaires:', '${NumberFormat('#,##0.00', 'fr_FR').format((pointVenteData['chiffre_affaires'] as num?)?.toDouble() ?? 0.0)} MGA'),
|
||||
_buildStatRow('Nombre de commandes:', '${pointVenteData['nombre_commandes'] ?? 0}'),
|
||||
_buildStatRow('Articles vendus:', '${pointVenteData['nombre_articles_vendus'] ?? 0}'),
|
||||
_buildStatRow('Quantité totale:', '${pointVenteData['quantite_totale_vendue'] ?? 0}'),
|
||||
_buildStatRow('Panier moyen:', '${NumberFormat('#,##0.00', 'fr_FR').format((pointVenteData['panier_moyen'] as num?)?.toDouble() ?? 0.0)} MGA'),
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Détails - $pointVenteNom'),
|
||||
content: Container(
|
||||
width: double.maxFinite,
|
||||
height: 400,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Statistiques générales
|
||||
_buildStatRow('Chiffre d\'affaires:', '${((pointVenteData['chiffre_affaires'] as num?)?.toDouble() ?? 0.0).toStringAsFixed(2)} MGA'),
|
||||
_buildStatRow('Nombre de commandes:', '${pointVenteData['nombre_commandes'] ?? 0}'),
|
||||
_buildStatRow('Articles vendus:', '${pointVenteData['nombre_articles_vendus'] ?? 0}'),
|
||||
_buildStatRow('Quantité totale:', '${pointVenteData['quantite_totale_vendue'] ?? 0}'),
|
||||
_buildStatRow('Panier moyen:', '${((pointVenteData['panier_moyen'] as num?)?.toDouble() ?? 0.0).toStringAsFixed(2)} MGA'),
|
||||
SizedBox(height: 16),
|
||||
Text('Top 5 des produits:', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 8),
|
||||
|
||||
SizedBox(height: 16),
|
||||
Text('Top 5 des produits:', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 8),
|
||||
|
||||
// Top produits
|
||||
FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: _database.getTopProduitsParPointDeVente(pointVenteId),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
SizedBox(height: 16),
|
||||
|
||||
if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return Text('Aucun produit vendu', style: TextStyle(color: Colors.grey));
|
||||
}
|
||||
// ✅ Top produits
|
||||
FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: _database.getTopProduitsParPointDeVente(pointVenteId),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final produits = snapshot.data!;
|
||||
return Column(
|
||||
children: produits.map((produit) => Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
produit['produit_nom'] ?? 'N/A',
|
||||
style: TextStyle(fontSize: 12),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return Text('Aucun produit vendu', style: TextStyle(color: Colors.grey));
|
||||
}
|
||||
|
||||
final produits = snapshot.data!;
|
||||
return Column(
|
||||
children: produits.map((produit) => Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
produit['produit_nom'] ?? 'N/A',
|
||||
style: TextStyle(fontSize: 12),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
'${produit['quantite_vendue'] ?? 0} vendus',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
)).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${produit['quantite_vendue'] ?? 0} vendus',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
)).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Fermer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Fermer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatRow(String label, String value) {
|
||||
@ -1582,7 +1674,7 @@ Widget _buildStatRow(String label, String value) {
|
||||
],
|
||||
),
|
||||
trailing: Text(
|
||||
'${product.price.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(product.price)} MGA',
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
|
||||
@ -307,7 +307,7 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
|
||||
Expanded(
|
||||
child: _buildInfoCard(
|
||||
'Prix unitaire',
|
||||
'${product.price.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0.00', 'fr_FR').format(product.price)} MGA',
|
||||
Icons.attach_money,
|
||||
Colors.green,
|
||||
),
|
||||
@ -1823,7 +1823,7 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text('IMEI: ${product.imei}'),
|
||||
Text('Prix: ${product.price.toStringAsFixed(2)} MGA'),
|
||||
Text('Prix: ${NumberFormat('#,##0.00', 'fr_FR').format(product.price)} MGA'),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
@ -4476,7 +4476,7 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
|
||||
icon: Icons.info_outline,
|
||||
color: Colors.blue,
|
||||
children: [
|
||||
_buildModernInfoRow('Prix', '${product.price} MGA',
|
||||
_buildModernInfoRow('Prix', '${NumberFormat('#,##0.00', 'fr_FR').format(product.price)} MGA',
|
||||
Icons.payments_outlined),
|
||||
_buildModernInfoRow('Catégorie', product.category,
|
||||
Icons.category_outlined),
|
||||
|
||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:youmazgestion/Components/app_bar.dart';
|
||||
import 'package:youmazgestion/controller/HistoryController.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'bilanDesJourne.dart';
|
||||
|
||||
@ -38,7 +39,7 @@ class _BilanMoisState extends State<BilanMois> {
|
||||
children: [
|
||||
_buildInfoCard(
|
||||
title: 'Chiffre réalisé',
|
||||
value: '${controller.totalSum.value.toStringAsFixed(2)} MGA',
|
||||
value: '${NumberFormat('#,##0', 'fr_FR').format(controller.totalSum.value)} MGA',
|
||||
color: Colors.green,
|
||||
icon: Icons.monetization_on,
|
||||
),
|
||||
|
||||
@ -188,7 +188,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Montant total: ${montantFinal.toStringAsFixed(2)} MGA'),
|
||||
Text('Montant total: ${NumberFormat('#,##0', 'fr_FR').format(montantFinal)} MGA'),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: amountController,
|
||||
@ -207,7 +207,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
const SizedBox(height: 20),
|
||||
if (amountGiven >= montantFinal)
|
||||
Text(
|
||||
'Monnaie à rendre: ${change.toStringAsFixed(2)} MGA',
|
||||
'Monnaie à rendre: ${NumberFormat('#,##0', 'fr_FR').format(change)} MGA',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -674,7 +674,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
),
|
||||
] else
|
||||
pw.Text(
|
||||
'${detail.prixUnitaire.toStringAsFixed(0)}',
|
||||
NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire),
|
||||
style: smallTextStyle
|
||||
),
|
||||
],
|
||||
@ -690,7 +690,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
children: [
|
||||
if (detail.estCadeau) ...[
|
||||
pw.Text(
|
||||
'${detail.sousTotal.toStringAsFixed(0)}',
|
||||
NumberFormat('#,##0', 'fr_FR').format(detail.sousTotal),
|
||||
style: pw.TextStyle(
|
||||
fontSize: 8,
|
||||
decoration: pw.TextDecoration.lineThrough,
|
||||
@ -796,7 +796,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
children: [
|
||||
pw.Text('TOTAL:', style: boldTextStyle),
|
||||
pw.SizedBox(width: 10),
|
||||
pw.Text('${commande.montantTotal.toStringAsFixed(0)} MGA', style: boldTextStyle),
|
||||
pw.Text('${NumberFormat('#,##0', 'fr_FR').format(commande.montantTotal)} MGA', style: boldTextStyle),
|
||||
],
|
||||
),
|
||||
],
|
||||
@ -1552,7 +1552,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
if (totalRemises > 0 || totalCadeaux > 0) ...[
|
||||
pw.SizedBox(height: 4),
|
||||
pw.Text(
|
||||
'Économies réalisées: ${(totalRemises + totalCadeaux).toStringAsFixed(0)} MGA',
|
||||
'Économies réalisées: ${NumberFormat('#,##0', 'fr_FR').format(totalRemises + totalCadeaux)} MGA',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 8,
|
||||
color: PdfColors.green,
|
||||
@ -1694,7 +1694,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
pw.Text('🎁 ', style: emojifont),
|
||||
pw.Expanded(
|
||||
child: pw.Text(
|
||||
'Merci de votre confiance ! Nous espérons que nos cadeaux vous feront plaisir. ($nombreCadeaux article(s) offert(s) - Valeur: ${totalCadeaux.toStringAsFixed(0)} MGA)',
|
||||
'Merci de votre confiance ! Nous espérons que nos cadeaux vous feront plaisir. ($nombreCadeaux article(s) offert(s) - Valeur: ${NumberFormat('#,##0', 'fr_FR').format(totalCadeaux)} MGA)',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 9,
|
||||
fontStyle: pw.FontStyle.italic,
|
||||
@ -1893,7 +1893,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
String _getPaymentMethodLabel(PaymentMethod payment) {
|
||||
switch (payment.type) {
|
||||
case PaymentType.cash:
|
||||
return 'LIQUIDE (${payment.amountGiven.toStringAsFixed(0)} MGA)';
|
||||
return 'LIQUIDE (${NumberFormat('#,##0', 'fr_FR').format(payment.amountGiven)} MGA)';
|
||||
case PaymentType.card:
|
||||
return 'CARTE BANCAIRE';
|
||||
case PaymentType.mvola:
|
||||
@ -2149,7 +2149,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
children: [
|
||||
pw.Text('SOUS-TOTAL:',
|
||||
style: const pw.TextStyle(fontSize: 8)),
|
||||
pw.Text('${sousTotal.toStringAsFixed(0)} MGA',
|
||||
pw.Text('${NumberFormat('#,##0', 'fr_FR').format(sousTotal)} MGA',
|
||||
style: const pw.TextStyle(fontSize: 8)),
|
||||
],
|
||||
),
|
||||
@ -2160,7 +2160,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
pw.Text('REMISES:',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 8, color: PdfColors.orange)),
|
||||
pw.Text('-${totalRemises.toStringAsFixed(0)} MGA',
|
||||
pw.Text('-${NumberFormat('#,##0', 'fr_FR').format(totalRemises)} MGA',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 8, color: PdfColors.orange)),
|
||||
],
|
||||
@ -2173,7 +2173,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
pw.Text('CADEAUX ($nombreCadeaux):',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 8, color: PdfColors.green700)),
|
||||
pw.Text('-${totalCadeaux.toStringAsFixed(0)} MGA',
|
||||
pw.Text('-${NumberFormat('#,##0', 'fr_FR').format(totalCadeaux)} MGA',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 8, color: PdfColors.green700)),
|
||||
],
|
||||
@ -2189,7 +2189,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
pw.Text('TOTAL:',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 9, fontWeight: pw.FontWeight.bold)),
|
||||
pw.Text('${commande.montantTotal.toStringAsFixed(0)} MGA',
|
||||
pw.Text('${NumberFormat('#,##0', 'fr_FR').format(commande.montantTotal)} MGA',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 9, fontWeight: pw.FontWeight.bold)),
|
||||
],
|
||||
@ -2198,7 +2198,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
if (totalRemises > 0 || totalCadeaux > 0) ...[
|
||||
pw.SizedBox(height: 4),
|
||||
pw.Text(
|
||||
'Économies: ${(totalRemises + totalCadeaux).toStringAsFixed(0)} MGA !',
|
||||
'Économies: ${NumberFormat('#,##0', 'fr_FR').format(totalRemises + totalCadeaux)} MGA !',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 7,
|
||||
color: PdfColors.green,
|
||||
@ -2222,7 +2222,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
if (payment.type == PaymentType.cash &&
|
||||
payment.amountGiven > commande.montantTotal)
|
||||
pw.Text(
|
||||
'Monnaie rendue: ${(payment.amountGiven - commande.montantTotal).toStringAsFixed(0)} MGA',
|
||||
'Monnaie rendue: ${NumberFormat('#,##0', 'fr_FR').format(payment.amountGiven - commande.montantTotal)} MGA',
|
||||
style: const pw.TextStyle(fontSize: 8)),
|
||||
|
||||
pw.SizedBox(height: 8),
|
||||
@ -2827,7 +2827,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${commande.montantTotal.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(commande.montantTotal)} MGA',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
@ -54,7 +54,7 @@ class _HistoriquePageState extends State<HistoriquePage> {
|
||||
|
||||
Future<void> _loadPointsDeVenteWithDefault() async {
|
||||
try {
|
||||
print(_userController.userId);
|
||||
print(_userController.pointDeVenteId);
|
||||
final points = await _appDatabase.getPointsDeVente();
|
||||
setState(() {
|
||||
_pointsDeVente = points;
|
||||
@ -88,7 +88,11 @@ class _HistoriquePageState extends State<HistoriquePage> {
|
||||
});
|
||||
|
||||
try {
|
||||
final commandes = await _appDatabase.getCommandes();
|
||||
final allCommandes = await _appDatabase.getCommandes();
|
||||
final pointDeVenteId = _userController.pointDeVenteId;
|
||||
final commandes = pointDeVenteId == 0
|
||||
? allCommandes
|
||||
: allCommandes.where((cmd) => cmd.pointDeVenteId == pointDeVenteId).toList();
|
||||
|
||||
setState(() {
|
||||
_commandes.clear();
|
||||
@ -136,11 +140,13 @@ class _HistoriquePageState extends State<HistoriquePage> {
|
||||
final searchText = _searchController.text.toLowerCase();
|
||||
final clientQuery = _searchClientController.text.toLowerCase();
|
||||
final commandeIdQuery = _searchCommandeIdController.text.toLowerCase();
|
||||
final pointDeVenteId = _userController.pointDeVenteId;
|
||||
|
||||
setState(() {
|
||||
_filteredCommandes.clear();
|
||||
|
||||
for (var commande in _commandes) {
|
||||
if (pointDeVenteId != 0 && commande.pointDeVenteId != pointDeVenteId) continue;
|
||||
bool matchesSearch = searchText.isEmpty ||
|
||||
commande.clientNom!.toLowerCase().contains(searchText) ||
|
||||
commande.clientPrenom!.toLowerCase().contains(searchText) ||
|
||||
@ -590,7 +596,7 @@ class _HistoriquePageState extends State<HistoriquePage> {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${commande.montantTotal.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0.00', 'fr_FR').format(commande.montantTotal)} MGA',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -641,10 +647,10 @@ class _HistoriquePageState extends State<HistoriquePage> {
|
||||
const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${detail.quantite} x ${detail.prixUnitaire.toStringAsFixed(2)} MGA',
|
||||
'${detail.quantite} x ${NumberFormat('#,##0.00', 'fr_FR').format(detail.prixUnitaire)} MGA',
|
||||
),
|
||||
trailing: Text(
|
||||
'${detail.sousTotal.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0.00', 'fr_FR').format(detail.sousTotal)} MGA',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue.shade800,
|
||||
@ -789,7 +795,7 @@ class _HistoriquePageState extends State<HistoriquePage> {
|
||||
Widget _buildCommandeListItem(Commande commande) {
|
||||
final isMobile = MediaQuery.of(context).size.width < 600;
|
||||
// print(commande.commandeurId);
|
||||
print(_userController.userId);
|
||||
print(_userController.pointDeVenteId);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
@ -838,7 +844,7 @@ class _HistoriquePageState extends State<HistoriquePage> {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'PV: ${_selectedPointDeVente}',
|
||||
'PV: ${commande.pointDeVenteDesign}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
@ -858,7 +864,7 @@ class _HistoriquePageState extends State<HistoriquePage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'${commande.montantTotal.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0.00', 'fr_FR').format(commande.montantTotal)} MGA',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.green.shade700,
|
||||
|
||||
@ -5,6 +5,7 @@ import 'package:youmazgestion/Views/voirPlus.dart';
|
||||
import 'package:youmazgestion/controller/HistoryController.dart';
|
||||
import '../Models/Order.dart';
|
||||
import 'package:youmazgestion/Views/detailHistory.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class HistoryDetailPage extends StatelessWidget {
|
||||
final DateTime selectedDate;
|
||||
@ -112,7 +113,7 @@ class HistoryDetailPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Total Somme: $totalSum MGA',
|
||||
'Total Somme: ${NumberFormat('#,##0', 'fr_FR').format(totalSum)} MGA',
|
||||
style: const TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -197,7 +198,7 @@ class HistoryDetailPage extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Text('Total: ${order.totalPrice} MGA'),
|
||||
subtitle: Text('Total: ${NumberFormat('#,##0', 'fr_FR').format(order.totalPrice)} MGA'),
|
||||
trailing: Text('Date: ${order.dateTime}'),
|
||||
leading: Text('vendeur: ${order.user}'),
|
||||
onTap: () {
|
||||
|
||||
@ -10,6 +10,7 @@ import 'package:youmazgestion/Models/users.dart';
|
||||
import 'package:youmazgestion/Models/produit.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
import 'package:youmazgestion/controller/userController.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class NouvelleCommandePage extends StatefulWidget {
|
||||
const NouvelleCommandePage({super.key});
|
||||
@ -695,7 +696,7 @@ void _modifierQuantite(int productId, int nouvelleQuantite) {
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Détails du produit en grille
|
||||
_buildProductDetailRow('Prix', '${product.price.toStringAsFixed(2)} MGA'),
|
||||
_buildProductDetailRow('Prix', '${NumberFormat('#,##0', 'fr_FR').format(product.price)} MGA'),
|
||||
_buildProductDetailRow('Quantité ajoutée', '$newQuantity'),
|
||||
|
||||
if (product.imei != null && product.imei!.isNotEmpty)
|
||||
@ -2348,10 +2349,18 @@ Widget _buildRoleBasedHeader() {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 🎯 MODIFIÉ: Interface produit avec indication visuelle de la commandabilité
|
||||
// 🎯 MODIFIÉ: Interface produit avec filtrage des produits en rupture ou non assignés
|
||||
Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
// ===== FILTRAGE : Ne pas afficher les produits en rupture ou non assignés =====
|
||||
final bool isOutOfStock = product.stock != null && product.stock! <= 0;
|
||||
final bool hasNoPointDeVente = product.pointDeVenteId == null || product.pointDeVenteId == 0;
|
||||
|
||||
// Si le produit est en rupture de stock OU n'a pas de point de vente assigné, on ne l'affiche pas
|
||||
if (isOutOfStock || hasNoPointDeVente) {
|
||||
return const SizedBox.shrink(); // Widget vide qui ne prend pas d'espace
|
||||
}
|
||||
|
||||
// ===== VARIABLES D'ÉTAT =====
|
||||
final detailPanier = _panierDetails[product.id!];
|
||||
final int currentQuantity = detailPanier?.quantite ?? 0;
|
||||
final isCurrentUserPointDeVente = product.pointDeVenteId == _userController.pointDeVenteId;
|
||||
@ -2385,17 +2394,15 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: isOutOfStock
|
||||
? Border.all(color: Colors.red.shade200, width: 1.5)
|
||||
: detailPanier?.estCadeau == true
|
||||
? Border.all(color: Colors.green.shade300, width: 2)
|
||||
: detailPanier?.aRemise == true
|
||||
border: detailPanier?.estCadeau == true
|
||||
? Border.all(color: Colors.green.shade300, width: 2)
|
||||
: detailPanier?.aRemise == true
|
||||
? Border.all(color: Colors.orange.shade300, width: 2)
|
||||
: isCurrentUserPointDeVente
|
||||
? Border.all(color: Colors.orange.shade300, width: 2)
|
||||
: isCurrentUserPointDeVente
|
||||
? Border.all(color: Colors.orange.shade300, width: 2)
|
||||
: !isProduitCommandable
|
||||
? Border.all(color: Colors.grey.shade200, width: 1)
|
||||
: null,
|
||||
: !isProduitCommandable
|
||||
? Border.all(color: Colors.grey.shade200, width: 1)
|
||||
: null,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
@ -2403,21 +2410,20 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// ===== ICÔNE PRODUIT =====
|
||||
Container(
|
||||
width: isMobile ? 40 : 50,
|
||||
height: isMobile ? 40 : 50,
|
||||
decoration: BoxDecoration(
|
||||
color: !isProduitCommandable
|
||||
? Colors.grey.shade100
|
||||
: isOutOfStock
|
||||
? Colors.red.shade50
|
||||
: detailPanier?.estCadeau == true
|
||||
? Colors.green.shade50
|
||||
: detailPanier?.aRemise == true
|
||||
: detailPanier?.estCadeau == true
|
||||
? Colors.green.shade50
|
||||
: detailPanier?.aRemise == true
|
||||
? Colors.orange.shade50
|
||||
: isCurrentUserPointDeVente
|
||||
? Colors.orange.shade50
|
||||
: isCurrentUserPointDeVente
|
||||
? Colors.orange.shade50
|
||||
: Colors.blue.shade50,
|
||||
: Colors.blue.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
@ -2433,22 +2439,23 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
size: isMobile ? 20 : 24,
|
||||
color: !isProduitCommandable
|
||||
? Colors.grey.shade500
|
||||
: isOutOfStock
|
||||
? Colors.red
|
||||
: detailPanier?.estCadeau == true
|
||||
? Colors.green.shade700
|
||||
: detailPanier?.aRemise == true
|
||||
: detailPanier?.estCadeau == true
|
||||
? Colors.green.shade700
|
||||
: detailPanier?.aRemise == true
|
||||
? Colors.orange.shade700
|
||||
: isCurrentUserPointDeVente
|
||||
? Colors.orange.shade700
|
||||
: isCurrentUserPointDeVente
|
||||
? Colors.orange.shade700
|
||||
: Colors.blue,
|
||||
: Colors.blue,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// ===== INFORMATIONS PRODUIT =====
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Nom du produit avec badges de statut
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@ -2459,13 +2466,12 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
fontSize: isMobile ? 14 : 16,
|
||||
color: !isProduitCommandable
|
||||
? Colors.grey.shade600
|
||||
: isOutOfStock
|
||||
? Colors.red.shade700
|
||||
: null,
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Indicateurs de statut
|
||||
|
||||
// Badge "AUTRE PV" pour produits non commandables
|
||||
if (!isProduitCommandable && !_isUserSuperAdmin())
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
@ -2489,6 +2495,8 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Badge "CADEAU"
|
||||
if (detailPanier?.estCadeau == true)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
@ -2505,6 +2513,8 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Badge "MON PV"
|
||||
if (isCurrentUserPointDeVente && detailPanier?.estCadeau != true && isProduitCommandable)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
@ -2529,6 +2539,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
Row(
|
||||
children: [
|
||||
if (detailPanier?.estCadeau == true) ...[
|
||||
// Prix gratuit pour les cadeaux
|
||||
Text(
|
||||
'Gratuit',
|
||||
style: TextStyle(
|
||||
@ -2539,7 +2550,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${product.price.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(product.price)} MGA',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade500,
|
||||
fontWeight: FontWeight.w600,
|
||||
@ -2548,8 +2559,9 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
// Prix normal ou avec remise
|
||||
Text(
|
||||
'${product.price.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(product.price)} MGA',
|
||||
style: TextStyle(
|
||||
color: Colors.green.shade700,
|
||||
fontWeight: FontWeight.w600,
|
||||
@ -2559,10 +2571,11 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
: null,
|
||||
),
|
||||
),
|
||||
// Prix après remise
|
||||
if (detailPanier?.aRemise == true) ...[
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${(detailPanier!.prixFinal / detailPanier.quantite).toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(detailPanier!.prixFinal / detailPanier.quantite)} MGA',
|
||||
style: TextStyle(
|
||||
color: Colors.orange.shade700,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -2574,7 +2587,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
],
|
||||
),
|
||||
|
||||
// Affichage remise
|
||||
// Description de la remise
|
||||
if (detailPanier?.aRemise == true && !detailPanier!.estCadeau)
|
||||
Text(
|
||||
'Remise: ${detailPanier!.remiseDescription}',
|
||||
@ -2585,20 +2598,17 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
),
|
||||
),
|
||||
|
||||
// Stock
|
||||
// ===== STOCK (Toujours > 0 grâce au filtrage) =====
|
||||
if (product.stock != null)
|
||||
Text(
|
||||
'Stock: ${product.stock}${isOutOfStock ? ' (Rupture)' : ''}',
|
||||
'Stock: ${product.stock}',
|
||||
style: TextStyle(
|
||||
fontSize: isMobile ? 10 : 12,
|
||||
color: isOutOfStock
|
||||
? Colors.red.shade600
|
||||
: Colors.grey.shade600,
|
||||
fontWeight: isOutOfStock ? FontWeight.w600 : FontWeight.normal,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
|
||||
// ===== AFFICHAGE IMEI ET RÉFÉRENCE =====
|
||||
// ===== IMEI ET RÉFÉRENCE =====
|
||||
if (product.imei != null && product.imei!.isNotEmpty)
|
||||
Text(
|
||||
'IMEI: ${product.imei}',
|
||||
@ -2617,7 +2627,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
),
|
||||
),
|
||||
|
||||
// Point de vente
|
||||
// ===== POINT DE VENTE =====
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
@ -2680,7 +2690,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
? Colors.green.shade700
|
||||
: Colors.grey.shade600,
|
||||
),
|
||||
onPressed: isOutOfStock ? null : () => _basculerStatutCadeau(product.id!),
|
||||
onPressed: () => _basculerStatutCadeau(product.id!),
|
||||
tooltip: detailPanier?.estCadeau == true
|
||||
? 'Retirer le statut cadeau'
|
||||
: 'Marquer comme cadeau',
|
||||
@ -2692,6 +2702,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Bouton remise (seulement pour les articles non-cadeaux)
|
||||
if (!detailPanier!.estCadeau)
|
||||
Container(
|
||||
@ -2706,7 +2717,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
? Colors.orange.shade700
|
||||
: Colors.grey.shade600,
|
||||
),
|
||||
onPressed: isOutOfStock ? null : () => _showRemiseDialog(product),
|
||||
onPressed: () => _showRemiseDialog(product),
|
||||
tooltip: detailPanier.aRemise
|
||||
? 'Modifier la remise'
|
||||
: 'Ajouter une remise',
|
||||
@ -2718,6 +2729,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Bouton pour ajouter un cadeau à un autre produit
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 4),
|
||||
@ -2727,7 +2739,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
size: isMobile ? 16 : 18,
|
||||
color: Colors.green.shade600,
|
||||
),
|
||||
onPressed: isOutOfStock ? null : () => _showCadeauDialog(product),
|
||||
onPressed: () => _showCadeauDialog(product),
|
||||
tooltip: 'Ajouter un cadeau',
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.green.shade50,
|
||||
@ -2740,30 +2752,30 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
|
||||
// Contrôles de quantité (seulement si commandable)
|
||||
// ===== CONTRÔLES DE QUANTITÉ =====
|
||||
if (isProduitCommandable)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isOutOfStock
|
||||
? Colors.grey.shade100
|
||||
: detailPanier?.estCadeau == true
|
||||
? Colors.green.shade50
|
||||
: isCurrentUserPointDeVente
|
||||
? Colors.orange.shade50
|
||||
: Colors.blue.shade50,
|
||||
color: detailPanier?.estCadeau == true
|
||||
? Colors.green.shade50
|
||||
: isCurrentUserPointDeVente
|
||||
? Colors.orange.shade50
|
||||
: Colors.blue.shade50,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Bouton diminuer
|
||||
IconButton(
|
||||
icon: Icon(Icons.remove, size: isMobile ? 16 : 18),
|
||||
onPressed: isOutOfStock ? null : () {
|
||||
onPressed: () {
|
||||
if (currentQuantity > 0) {
|
||||
_modifierQuantite(product.id!, currentQuantity - 1);
|
||||
}
|
||||
},
|
||||
),
|
||||
// Quantité actuelle
|
||||
Text(
|
||||
currentQuantity.toString(),
|
||||
style: TextStyle(
|
||||
@ -2771,9 +2783,10 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
fontSize: isMobile ? 12 : 14,
|
||||
),
|
||||
),
|
||||
// Bouton augmenter
|
||||
IconButton(
|
||||
icon: Icon(Icons.add, size: isMobile ? 16 : 18),
|
||||
onPressed: isOutOfStock ? null : () {
|
||||
onPressed: () {
|
||||
if (product.stock == null || currentQuantity < product.stock!) {
|
||||
if (currentQuantity == 0) {
|
||||
_ajouterAuPanier(product, 1);
|
||||
@ -2795,7 +2808,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
),
|
||||
)
|
||||
else
|
||||
// Message informatif pour produits non-commandables
|
||||
// ===== SECTION PRODUITS NON-COMMANDABLES =====
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
@ -2805,6 +2818,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Indicateur de consultation
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -2821,28 +2835,18 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.swap_horiz, size: 14),
|
||||
label:!isMobile ? const Text('Demander transfertt'):const SizedBox.shrink(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: (product.stock != null && product.stock! >= 1)
|
||||
? Colors.blue.shade700
|
||||
: Colors.grey.shade400,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
),
|
||||
onPressed: (product.stock != null && product.stock! >= 1)
|
||||
? () => _showDemandeTransfertDialog(product)
|
||||
: () {
|
||||
Get.snackbar(
|
||||
'Stock insuffisant',
|
||||
'Impossible de demander un transfert : produit en rupture de stock',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.orange.shade600,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// Bouton demande de transfert
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.swap_horiz, size: 14),
|
||||
label: !isMobile ? const Text('Demander transfert') : const SizedBox.shrink(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade700,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
),
|
||||
onPressed: () => _showDemandeTransfertDialog(product),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -2989,7 +2993,7 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
|
||||
Expanded(
|
||||
child: _buildInfoCard(
|
||||
'Prix unitaire',
|
||||
'${product.price.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(product.price)} MGA',
|
||||
Icons.attach_money,
|
||||
Colors.green,
|
||||
),
|
||||
@ -3513,7 +3517,7 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'(${detail.prixUnitaire.toStringAsFixed(2)} MGA)',
|
||||
'(${NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire)} MGA)',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey.shade500,
|
||||
@ -3530,19 +3534,19 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo
|
||||
),
|
||||
const Text(' → '),
|
||||
Text(
|
||||
'${(detail.prixFinal / detail.quantite).toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal / detail.quantite)} MGA',
|
||||
style: TextStyle(
|
||||
color: Colors.orange.shade700,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
] else
|
||||
Text('${detail.prixUnitaire.toStringAsFixed(2)} MGA'),
|
||||
Text('${NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire)} MGA'),
|
||||
],
|
||||
),
|
||||
if (detail.aRemise && !detail.estCadeau)
|
||||
Text(
|
||||
'Remise: ${detail.remiseDescription} (-${detail.montantRemise.toStringAsFixed(2)} MGA)',
|
||||
'Remise: ${detail.remiseDescription} (-${NumberFormat('#,##0', 'fr_FR').format(detail.montantRemise)} MGA)',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.orange.shade600,
|
||||
@ -3595,7 +3599,7 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'Valeur: ${detail.sousTotal.toStringAsFixed(2)} MGA',
|
||||
'Valeur: ${NumberFormat('#,##0', 'fr_FR').format(detail.sousTotal)} MGA',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.grey.shade500,
|
||||
@ -3604,7 +3608,7 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo
|
||||
),
|
||||
] else if (detail.aRemise && detail.sousTotal != detail.prixFinal) ...[
|
||||
Text(
|
||||
'${detail.sousTotal.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(detail.sousTotal)} MGA',
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
@ -3612,7 +3616,7 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${detail.prixFinal.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal)} MGA',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.orange.shade700,
|
||||
@ -3621,7 +3625,7 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo
|
||||
),
|
||||
] else
|
||||
Text(
|
||||
'${detail.prixFinal.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal)} MGA',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue.shade800,
|
||||
@ -3673,7 +3677,7 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo
|
||||
children: [
|
||||
const Text('Sous-total:', style: TextStyle(fontSize: 14)),
|
||||
Text(
|
||||
'${sousTotal.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(sousTotal)} MGA',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
],
|
||||
@ -3694,7 +3698,7 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'-${totalRemises.toStringAsFixed(2)} MGA',
|
||||
'-${NumberFormat('#,##0', 'fr_FR').format(totalRemises)} MGA',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.orange.shade700,
|
||||
@ -3729,7 +3733,7 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'-${totalCadeaux.toStringAsFixed(2)} MGA',
|
||||
'-${NumberFormat('#,##0', 'fr_FR').format(totalCadeaux)} MGA',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.green.shade700,
|
||||
@ -3753,7 +3757,7 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
'${total.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(total)} MGA',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -3791,7 +3795,7 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Économies totales: ${(totalRemises + totalCadeaux).toStringAsFixed(2)} MGA',
|
||||
'Économies totales: ${NumberFormat('#,##0', 'fr_FR').format(totalRemises + totalCadeaux)} MGA',
|
||||
style: TextStyle(
|
||||
color: Colors.green.shade700,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -3807,7 +3811,7 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo
|
||||
if (totalRemises > 0 && totalCadeaux > 0) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Remises: ${totalRemises.toStringAsFixed(2)} MGA • Cadeaux: ${totalCadeaux.toStringAsFixed(2)} MGA',
|
||||
'Remises: ${NumberFormat('#,##0', 'fr_FR').format(totalRemises)} MGA • Cadeaux: ${NumberFormat('#,##0', 'fr_FR').format(totalCadeaux)} MGA',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
fontSize: 11,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:io';
|
||||
import 'package:youmazgestion/Models/produit.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class ProductCard extends StatefulWidget {
|
||||
final Product product;
|
||||
@ -221,7 +222,7 @@ class _ProductCardState extends State<ProductCard>
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${widget.product.price.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(widget.product.price)} MGA',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
@ -67,8 +67,8 @@ class TicketPage extends StatelessWidget {
|
||||
return [
|
||||
product.name,
|
||||
quantity.toString(),
|
||||
'${product.price.toStringAsFixed(2)} MGA',
|
||||
'${productTotal.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0.00', 'fr_FR').format(product.price)} MGA',
|
||||
'${NumberFormat('#,##0.00', 'fr_FR').format(productTotal)} MGA',
|
||||
];
|
||||
}).toList(),
|
||||
],
|
||||
@ -82,7 +82,7 @@ class TicketPage extends StatelessWidget {
|
||||
pw.Text('Total :',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 18, fontWeight: pw.FontWeight.bold)),
|
||||
pw.Text('${totalCartPrice.toStringAsFixed(2)} MGA',
|
||||
pw.Text('${NumberFormat('#,##0.00', 'fr_FR').format(totalCartPrice)} MGA',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 18, fontWeight: pw.FontWeight.bold)),
|
||||
],
|
||||
@ -95,7 +95,7 @@ class TicketPage extends StatelessWidget {
|
||||
children: [
|
||||
pw.Text('Somme remise :',
|
||||
style: const pw.TextStyle(fontSize: 16)),
|
||||
pw.Text('${amountPaid.toStringAsFixed(2)} MGA',
|
||||
pw.Text('${NumberFormat('#,##0.00', 'fr_FR').format(amountPaid)} MGA',
|
||||
style: const pw.TextStyle(fontSize: 16)),
|
||||
],
|
||||
),
|
||||
@ -106,7 +106,7 @@ class TicketPage extends StatelessWidget {
|
||||
pw.Text('Somme rendue :',
|
||||
style: const pw.TextStyle(fontSize: 16)),
|
||||
pw.Text(
|
||||
'${(amountPaid - totalCartPrice).toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0.00', 'fr_FR').format(amountPaid - totalCartPrice)} MGA',
|
||||
style: const pw.TextStyle(fontSize: 16)),
|
||||
],
|
||||
),
|
||||
@ -271,14 +271,14 @@ class TicketPage extends StatelessWidget {
|
||||
TableCell(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${product.price.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0.00', 'fr_FR').format(product.price)} MGA',
|
||||
),
|
||||
),
|
||||
),
|
||||
TableCell(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${productTotal.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0.00', 'fr_FR').format(productTotal)} MGA',
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -305,7 +305,7 @@ class TicketPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${totalOrderAmount.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0.00', 'fr_FR').format(totalOrderAmount)} MGA',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -330,7 +330,7 @@ class TicketPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${amountPaid.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0.00', 'fr_FR').format(amountPaid)} MGA',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
@ -348,7 +348,7 @@ class TicketPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${change.toStringAsFixed(2)} MGA',
|
||||
'${NumberFormat('#,##0.00', 'fr_FR').format(change)} MGA',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
|
||||
@ -5,7 +5,7 @@ import 'dart:async';
|
||||
|
||||
class DatabaseConfig {
|
||||
// Local MySQL settings
|
||||
static const String localHost = '192.168.88.73';
|
||||
static const String localHost = '102.17.52.31';
|
||||
static const String localUsername = 'guycom';
|
||||
static const String? localPassword = '3iV59wjRdbuXAPR';
|
||||
static const String localDatabase = 'guycom';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user