27 changed files with 5586 additions and 6266 deletions
Binary file not shown.
@ -1,338 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:get/get_core/src/get_main.dart'; |
|||
import 'package:youmazgestion/Components/DiscountDialog.dart'; |
|||
import 'package:youmazgestion/Components/paymentType.dart'; |
|||
import 'package:youmazgestion/Models/Client.dart'; |
|||
import 'package:youmazgestion/Models/Remise.dart'; |
|||
|
|||
// Dialogue de paiement amélioré avec support des remises |
|||
class PaymentMethodEnhancedDialog extends StatefulWidget { |
|||
final Commande commande; |
|||
|
|||
const PaymentMethodEnhancedDialog({super.key, required this.commande}); |
|||
|
|||
@override |
|||
_PaymentMethodEnhancedDialogState createState() => _PaymentMethodEnhancedDialogState(); |
|||
} |
|||
|
|||
class _PaymentMethodEnhancedDialogState extends State<PaymentMethodEnhancedDialog> { |
|||
PaymentType _selectedPayment = PaymentType.cash; |
|||
final _amountController = TextEditingController(); |
|||
Remise? _appliedRemise; |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
_amountController.text = widget.commande.montantTotal.toStringAsFixed(2); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
_amountController.dispose(); |
|||
super.dispose(); |
|||
} |
|||
|
|||
void _showDiscountDialog() { |
|||
showDialog( |
|||
context: context, |
|||
builder: (context) => DiscountDialog( |
|||
onDiscountApplied: (remise) { |
|||
setState(() { |
|||
_appliedRemise = remise; |
|||
final montantFinal = widget.commande.montantTotal - remise.calculerRemise(widget.commande.montantTotal); |
|||
_amountController.text = montantFinal.toStringAsFixed(2); |
|||
}); |
|||
}, |
|||
), |
|||
); |
|||
} |
|||
|
|||
void _removeDiscount() { |
|||
setState(() { |
|||
_appliedRemise = null; |
|||
_amountController.text = widget.commande.montantTotal.toStringAsFixed(2); |
|||
}); |
|||
} |
|||
|
|||
void _validatePayment() { |
|||
final montantFinal = _appliedRemise != null |
|||
? widget.commande.montantTotal - _appliedRemise!.calculerRemise(widget.commande.montantTotal) |
|||
: widget.commande.montantTotal; |
|||
|
|||
if (_selectedPayment == PaymentType.cash) { |
|||
final amountGiven = double.tryParse(_amountController.text) ?? 0; |
|||
if (amountGiven < montantFinal) { |
|||
Get.snackbar( |
|||
'Erreur', |
|||
'Le montant donné est insuffisant', |
|||
snackPosition: SnackPosition.BOTTOM, |
|||
backgroundColor: Colors.red, |
|||
colorText: Colors.white, |
|||
); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
Navigator.pop(context, PaymentMethodEnhanced( |
|||
type: _selectedPayment, |
|||
amountGiven: _selectedPayment == PaymentType.cash |
|||
? double.parse(_amountController.text) |
|||
: montantFinal, |
|||
remise: _appliedRemise, |
|||
)); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
final montantOriginal = widget.commande.montantTotal; |
|||
final montantFinal = _appliedRemise != null |
|||
? montantOriginal - _appliedRemise!.calculerRemise(montantOriginal) |
|||
: montantOriginal; |
|||
final amount = double.tryParse(_amountController.text) ?? 0; |
|||
final change = amount - montantFinal; |
|||
|
|||
return AlertDialog( |
|||
title: const Text('Méthode de paiement', style: TextStyle(fontWeight: FontWeight.bold)), |
|||
content: SingleChildScrollView( |
|||
child: Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: [ |
|||
// Résumé des montants |
|||
Container( |
|||
padding: const EdgeInsets.all(12), |
|||
decoration: BoxDecoration( |
|||
color: Colors.blue.shade50, |
|||
borderRadius: BorderRadius.circular(8), |
|||
border: Border.all(color: Colors.blue.shade200), |
|||
), |
|||
child: Column( |
|||
children: [ |
|||
Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
children: [ |
|||
const Text('Montant original:'), |
|||
Text('${montantOriginal.toStringAsFixed(0)} MGA'), |
|||
], |
|||
), |
|||
if (_appliedRemise != null) ...[ |
|||
const SizedBox(height: 4), |
|||
Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
children: [ |
|||
Text('Remise (${_appliedRemise!.libelle}):'), |
|||
Text( |
|||
'- ${_appliedRemise!.calculerRemise(montantOriginal).toStringAsFixed(0)} MGA', |
|||
style: const TextStyle(color: Colors.red), |
|||
), |
|||
], |
|||
), |
|||
const Divider(), |
|||
], |
|||
Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
children: [ |
|||
const Text('Total à payer:', style: TextStyle(fontWeight: FontWeight.bold)), |
|||
Text('${montantFinal.toStringAsFixed(0)} MGA', |
|||
style: const TextStyle(fontWeight: FontWeight.bold)), |
|||
], |
|||
), |
|||
], |
|||
), |
|||
), |
|||
const SizedBox(height: 16), |
|||
|
|||
// Bouton remise |
|||
Row( |
|||
children: [ |
|||
Expanded( |
|||
child: OutlinedButton.icon( |
|||
onPressed: _appliedRemise == null ? _showDiscountDialog : _removeDiscount, |
|||
icon: Icon(_appliedRemise == null ? Icons.local_offer : Icons.close), |
|||
label: Text(_appliedRemise == null ? 'Ajouter remise' : 'Supprimer remise'), |
|||
style: OutlinedButton.styleFrom( |
|||
foregroundColor: _appliedRemise == null ? Colors.orange : Colors.red, |
|||
side: BorderSide( |
|||
color: _appliedRemise == null ? Colors.orange : Colors.red, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
const SizedBox(height: 16), |
|||
|
|||
// Section Paiement mobile |
|||
const Align( |
|||
alignment: Alignment.centerLeft, |
|||
child: Text('Mobile Money', style: TextStyle(fontWeight: FontWeight.w500)), |
|||
), |
|||
const SizedBox(height: 8), |
|||
Row( |
|||
children: [ |
|||
Expanded( |
|||
child: _buildMobileMoneyTile( |
|||
title: 'Mvola', |
|||
imagePath: 'assets/mvola.jpg', |
|||
value: PaymentType.mvola, |
|||
), |
|||
), |
|||
const SizedBox(width: 8), |
|||
Expanded( |
|||
child: _buildMobileMoneyTile( |
|||
title: 'Orange Money', |
|||
imagePath: 'assets/Orange_money.png', |
|||
value: PaymentType.orange, |
|||
), |
|||
), |
|||
const SizedBox(width: 8), |
|||
Expanded( |
|||
child: _buildMobileMoneyTile( |
|||
title: 'Airtel Money', |
|||
imagePath: 'assets/airtel_money.png', |
|||
value: PaymentType.airtel, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
const SizedBox(height: 16), |
|||
|
|||
// Section Carte bancaire |
|||
const Align( |
|||
alignment: Alignment.centerLeft, |
|||
child: Text('Carte Bancaire', style: TextStyle(fontWeight: FontWeight.w500)), |
|||
), |
|||
const SizedBox(height: 8), |
|||
_buildPaymentMethodTile( |
|||
title: 'Carte bancaire', |
|||
icon: Icons.credit_card, |
|||
value: PaymentType.card, |
|||
), |
|||
const SizedBox(height: 16), |
|||
|
|||
// Section Paiement en liquide |
|||
const Align( |
|||
alignment: Alignment.centerLeft, |
|||
child: Text('Espèces', style: TextStyle(fontWeight: FontWeight.w500)), |
|||
), |
|||
const SizedBox(height: 8), |
|||
_buildPaymentMethodTile( |
|||
title: 'Paiement en liquide', |
|||
icon: Icons.money, |
|||
value: PaymentType.cash, |
|||
), |
|||
if (_selectedPayment == PaymentType.cash) ...[ |
|||
const SizedBox(height: 12), |
|||
TextField( |
|||
controller: _amountController, |
|||
decoration: const InputDecoration( |
|||
labelText: 'Montant donné', |
|||
prefixText: 'MGA ', |
|||
border: OutlineInputBorder(), |
|||
), |
|||
keyboardType: TextInputType.numberWithOptions(decimal: true), |
|||
onChanged: (value) => setState(() {}), |
|||
), |
|||
const SizedBox(height: 8), |
|||
Text( |
|||
'Monnaie à rendre: ${change.toStringAsFixed(2)} MGA', |
|||
style: TextStyle( |
|||
fontSize: 16, |
|||
fontWeight: FontWeight.bold, |
|||
color: change >= 0 ? Colors.green : Colors.red, |
|||
), |
|||
), |
|||
], |
|||
], |
|||
), |
|||
), |
|||
actions: [ |
|||
TextButton( |
|||
onPressed: () => Navigator.pop(context), |
|||
child: const Text('Annuler', style: TextStyle(color: Colors.grey)), |
|||
), |
|||
ElevatedButton( |
|||
style: ElevatedButton.styleFrom( |
|||
backgroundColor: Colors.blue.shade800, |
|||
foregroundColor: Colors.white, |
|||
), |
|||
onPressed: _validatePayment, |
|||
child: const Text('Confirmer'), |
|||
), |
|||
], |
|||
); |
|||
} |
|||
|
|||
Widget _buildMobileMoneyTile({ |
|||
required String title, |
|||
required String imagePath, |
|||
required PaymentType value, |
|||
}) { |
|||
return Card( |
|||
elevation: 2, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.circular(8), |
|||
side: BorderSide( |
|||
color: _selectedPayment == value ? Colors.blue : Colors.grey.withOpacity(0.2), |
|||
width: 2, |
|||
), |
|||
), |
|||
child: InkWell( |
|||
borderRadius: BorderRadius.circular(8), |
|||
onTap: () => setState(() => _selectedPayment = value), |
|||
child: Padding( |
|||
padding: const EdgeInsets.all(12), |
|||
child: Column( |
|||
children: [ |
|||
Image.asset( |
|||
imagePath, |
|||
height: 30, |
|||
width: 30, |
|||
fit: BoxFit.contain, |
|||
errorBuilder: (context, error, stackTrace) => |
|||
const Icon(Icons.mobile_friendly, size: 30), |
|||
), |
|||
const SizedBox(height: 8), |
|||
Text( |
|||
title, |
|||
textAlign: TextAlign.center, |
|||
style: const TextStyle(fontSize: 12), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _buildPaymentMethodTile({ |
|||
required String title, |
|||
required IconData icon, |
|||
required PaymentType value, |
|||
}) { |
|||
return Card( |
|||
elevation: 2, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.circular(8), |
|||
side: BorderSide( |
|||
color: _selectedPayment == value ? Colors.blue : Colors.grey.withOpacity(0.2), |
|||
width: 2, |
|||
), |
|||
), |
|||
child: InkWell( |
|||
borderRadius: BorderRadius.circular(8), |
|||
onTap: () => setState(() => _selectedPayment = value), |
|||
child: Padding( |
|||
padding: const EdgeInsets.all(12), |
|||
child: Row( |
|||
children: [ |
|||
Icon(icon, size: 24), |
|||
const SizedBox(width: 12), |
|||
Text(title), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,234 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:youmazgestion/Services/stock_managementDatabase.dart'; |
|||
|
|||
class PasswordVerificationDialog extends StatefulWidget { |
|||
final String title; |
|||
final String message; |
|||
final Function(String) onPasswordVerified; |
|||
|
|||
const PasswordVerificationDialog({ |
|||
Key? key, |
|||
required this.title, |
|||
required this.message, |
|||
required this.onPasswordVerified, |
|||
}) : super(key: key); |
|||
|
|||
@override |
|||
_PasswordVerificationDialogState createState() => _PasswordVerificationDialogState(); |
|||
} |
|||
|
|||
class _PasswordVerificationDialogState extends State<PasswordVerificationDialog> { |
|||
final TextEditingController _passwordController = TextEditingController(); |
|||
bool _isPasswordVisible = false; |
|||
bool _isLoading = false; |
|||
|
|||
@override |
|||
void dispose() { |
|||
_passwordController.dispose(); |
|||
super.dispose(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return AlertDialog( |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.circular(15), |
|||
), |
|||
title: Row( |
|||
children: [ |
|||
Icon( |
|||
Icons.security, |
|||
color: Colors.blue.shade700, |
|||
size: 28, |
|||
), |
|||
const SizedBox(width: 10), |
|||
Expanded( |
|||
child: Text( |
|||
widget.title, |
|||
style: TextStyle( |
|||
fontSize: 18, |
|||
fontWeight: FontWeight.bold, |
|||
color: Colors.blue.shade700, |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
content: Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Text( |
|||
widget.message, |
|||
style: const TextStyle( |
|||
fontSize: 14, |
|||
color: Colors.black87, |
|||
), |
|||
), |
|||
const SizedBox(height: 20), |
|||
Container( |
|||
decoration: BoxDecoration( |
|||
color: Colors.grey.shade50, |
|||
borderRadius: BorderRadius.circular(12), |
|||
border: Border.all(color: Colors.grey.shade300), |
|||
), |
|||
child: TextField( |
|||
controller: _passwordController, |
|||
obscureText: !_isPasswordVisible, |
|||
autofocus: true, |
|||
decoration: InputDecoration( |
|||
labelText: 'Mot de passe', |
|||
prefixIcon: Icon( |
|||
Icons.lock_outline, |
|||
color: Colors.blue.shade600, |
|||
), |
|||
suffixIcon: IconButton( |
|||
icon: Icon( |
|||
_isPasswordVisible ? Icons.visibility_off : Icons.visibility, |
|||
color: Colors.grey.shade600, |
|||
), |
|||
onPressed: () { |
|||
setState(() { |
|||
_isPasswordVisible = !_isPasswordVisible; |
|||
}); |
|||
}, |
|||
), |
|||
border: OutlineInputBorder( |
|||
borderRadius: BorderRadius.circular(12), |
|||
borderSide: BorderSide.none, |
|||
), |
|||
filled: true, |
|||
fillColor: Colors.white, |
|||
contentPadding: const EdgeInsets.symmetric( |
|||
horizontal: 16, |
|||
vertical: 12, |
|||
), |
|||
), |
|||
onSubmitted: (value) => _verifyPassword(), |
|||
), |
|||
), |
|||
const SizedBox(height: 15), |
|||
Container( |
|||
padding: const EdgeInsets.all(12), |
|||
decoration: BoxDecoration( |
|||
color: Colors.amber.shade50, |
|||
borderRadius: BorderRadius.circular(8), |
|||
border: Border.all(color: Colors.amber.shade200), |
|||
), |
|||
child: Row( |
|||
children: [ |
|||
Icon( |
|||
Icons.info_outline, |
|||
color: Colors.amber.shade700, |
|||
size: 20, |
|||
), |
|||
const SizedBox(width: 8), |
|||
Expanded( |
|||
child: Text( |
|||
'Saisissez votre mot de passe pour confirmer cette action', |
|||
style: TextStyle( |
|||
fontSize: 12, |
|||
color: Colors.amber.shade700, |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
], |
|||
), |
|||
actions: [ |
|||
TextButton( |
|||
onPressed: _isLoading ? null : () => Navigator.of(context).pop(), |
|||
child: Text( |
|||
'Annuler', |
|||
style: TextStyle( |
|||
color: Colors.grey.shade600, |
|||
fontWeight: FontWeight.w500, |
|||
), |
|||
), |
|||
), |
|||
ElevatedButton( |
|||
onPressed: _isLoading ? null : _verifyPassword, |
|||
style: ElevatedButton.styleFrom( |
|||
backgroundColor: Colors.blue.shade700, |
|||
foregroundColor: Colors.white, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.circular(8), |
|||
), |
|||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), |
|||
), |
|||
child: _isLoading |
|||
? const SizedBox( |
|||
width: 20, |
|||
height: 20, |
|||
child: CircularProgressIndicator( |
|||
strokeWidth: 2, |
|||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white), |
|||
), |
|||
) |
|||
: const Text('Vérifier'), |
|||
), |
|||
], |
|||
); |
|||
} |
|||
|
|||
void _verifyPassword() async { |
|||
final password = _passwordController.text.trim(); |
|||
|
|||
if (password.isEmpty) { |
|||
Get.snackbar( |
|||
'Erreur', |
|||
'Veuillez saisir votre mot de passe', |
|||
snackPosition: SnackPosition.BOTTOM, |
|||
backgroundColor: Colors.red, |
|||
colorText: Colors.white, |
|||
duration: const Duration(seconds: 2), |
|||
); |
|||
return; |
|||
} |
|||
|
|||
setState(() { |
|||
_isLoading = true; |
|||
}); |
|||
|
|||
try { |
|||
final database = AppDatabase.instance; |
|||
final isValid = await database.verifyCurrentUserPassword(password); |
|||
|
|||
setState(() { |
|||
_isLoading = false; |
|||
}); |
|||
|
|||
if (isValid) { |
|||
Navigator.of(context).pop(); |
|||
widget.onPasswordVerified(password); |
|||
} else { |
|||
Get.snackbar( |
|||
'Erreur', |
|||
'Mot de passe incorrect', |
|||
snackPosition: SnackPosition.BOTTOM, |
|||
backgroundColor: Colors.red, |
|||
colorText: Colors.white, |
|||
duration: const Duration(seconds: 3), |
|||
); |
|||
_passwordController.clear(); |
|||
} |
|||
} catch (e) { |
|||
setState(() { |
|||
_isLoading = false; |
|||
}); |
|||
|
|||
Get.snackbar( |
|||
'Erreur', |
|||
'Une erreur est survenue lors de la vérification', |
|||
snackPosition: SnackPosition.BOTTOM, |
|||
backgroundColor: Colors.red, |
|||
colorText: Colors.white, |
|||
duration: const Duration(seconds: 3), |
|||
); |
|||
print("Erreur vérification mot de passe: $e"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,411 @@ |
|||
// Components/newCommandComponents/CadeauDialog.dart |
|||
|
|||
import 'package:flutter/material.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:youmazgestion/Models/client.dart'; |
|||
import 'package:youmazgestion/Models/produit.dart'; |
|||
import 'package:youmazgestion/Services/stock_managementDatabase.dart'; |
|||
|
|||
class CadeauDialog extends StatefulWidget { |
|||
final Product product; |
|||
final int quantite; |
|||
final DetailCommande? detailExistant; |
|||
|
|||
const CadeauDialog({ |
|||
Key? key, |
|||
required this.product, |
|||
required this.quantite, |
|||
this.detailExistant, |
|||
}) : super(key: key); |
|||
|
|||
@override |
|||
_CadeauDialogState createState() => _CadeauDialogState(); |
|||
} |
|||
|
|||
class _CadeauDialogState extends State<CadeauDialog> { |
|||
final AppDatabase _database = AppDatabase.instance; |
|||
List<Product> _produitsDisponibles = []; |
|||
Product? _produitCadeauSelectionne; |
|||
int _quantiteCadeau = 1; |
|||
bool _isLoading = true; |
|||
String _searchQuery = ''; |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
_loadProduitsDisponibles(); |
|||
} |
|||
|
|||
Future<void> _loadProduitsDisponibles() async { |
|||
try { |
|||
final produits = await _database.getProducts(); |
|||
setState(() { |
|||
_produitsDisponibles = produits.where((p) => |
|||
p.id != widget.product.id && // Exclure le produit principal |
|||
(p.stock == null || p.stock! > 0) // Seulement les produits en stock |
|||
).toList(); |
|||
_isLoading = false; |
|||
}); |
|||
} catch (e) { |
|||
setState(() { |
|||
_isLoading = false; |
|||
}); |
|||
Get.snackbar( |
|||
'Erreur', |
|||
'Impossible de charger les produits: $e', |
|||
snackPosition: SnackPosition.BOTTOM, |
|||
backgroundColor: Colors.red, |
|||
colorText: Colors.white, |
|||
); |
|||
} |
|||
} |
|||
|
|||
List<Product> get _produitsFiltres { |
|||
if (_searchQuery.isEmpty) { |
|||
return _produitsDisponibles; |
|||
} |
|||
return _produitsDisponibles.where((p) => |
|||
p.name.toLowerCase().contains(_searchQuery.toLowerCase()) || |
|||
(p.reference?.toLowerCase().contains(_searchQuery.toLowerCase()) ?? false) |
|||
).toList(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
final isMobile = MediaQuery.of(context).size.width < 600; |
|||
|
|||
return AlertDialog( |
|||
title: Row( |
|||
children: [ |
|||
Container( |
|||
padding: const EdgeInsets.all(8), |
|||
decoration: BoxDecoration( |
|||
color: Colors.green.shade100, |
|||
borderRadius: BorderRadius.circular(8), |
|||
), |
|||
child: Icon(Icons.card_giftcard, color: Colors.green.shade700), |
|||
), |
|||
const SizedBox(width: 12), |
|||
Expanded( |
|||
child: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Text( |
|||
'Ajouter un cadeau', |
|||
style: TextStyle(fontSize: isMobile ? 16 : 18), |
|||
), |
|||
Text( |
|||
'Pour: ${widget.product.name}', |
|||
style: TextStyle( |
|||
fontSize: isMobile ? 12 : 14, |
|||
color: Colors.grey.shade600, |
|||
fontWeight: FontWeight.normal, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
], |
|||
), |
|||
content: Container( |
|||
width: isMobile ? double.maxFinite : 500, |
|||
constraints: BoxConstraints( |
|||
maxHeight: MediaQuery.of(context).size.height * 0.7, |
|||
), |
|||
child: _isLoading |
|||
? const Center(child: CircularProgressIndicator()) |
|||
: Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: [ |
|||
// Information sur le produit principal |
|||
Container( |
|||
padding: const EdgeInsets.all(12), |
|||
decoration: BoxDecoration( |
|||
color: Colors.blue.shade50, |
|||
borderRadius: BorderRadius.circular(8), |
|||
border: Border.all(color: Colors.blue.shade200), |
|||
), |
|||
child: Row( |
|||
children: [ |
|||
Icon(Icons.shopping_bag, color: Colors.blue.shade700), |
|||
const SizedBox(width: 8), |
|||
Expanded( |
|||
child: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Text( |
|||
'Produit acheté', |
|||
style: TextStyle( |
|||
fontSize: 12, |
|||
color: Colors.blue.shade700, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
Text( |
|||
'${widget.quantite}x ${widget.product.name}', |
|||
style: const TextStyle( |
|||
fontSize: 14, |
|||
fontWeight: FontWeight.w500, |
|||
), |
|||
), |
|||
Text( |
|||
'Prix: ${widget.product.price.toStringAsFixed(2)} MGA', |
|||
style: TextStyle( |
|||
fontSize: 12, |
|||
color: Colors.grey.shade600, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
|
|||
const SizedBox(height: 16), |
|||
|
|||
// Barre de recherche |
|||
TextField( |
|||
decoration: InputDecoration( |
|||
labelText: 'Rechercher un produit cadeau', |
|||
prefixIcon: Icon(Icons.search, color: Colors.green.shade600), |
|||
border: OutlineInputBorder( |
|||
borderRadius: BorderRadius.circular(8), |
|||
), |
|||
filled: true, |
|||
fillColor: Colors.green.shade50, |
|||
), |
|||
onChanged: (value) { |
|||
setState(() { |
|||
_searchQuery = value; |
|||
}); |
|||
}, |
|||
), |
|||
|
|||
const SizedBox(height: 16), |
|||
|
|||
// Liste des produits disponibles |
|||
Expanded( |
|||
child: _produitsFiltres.isEmpty |
|||
? Center( |
|||
child: Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: [ |
|||
Icon( |
|||
Icons.card_giftcard_outlined, |
|||
size: 48, |
|||
color: Colors.grey.shade400, |
|||
), |
|||
const SizedBox(height: 8), |
|||
Text( |
|||
'Aucun produit disponible', |
|||
style: TextStyle( |
|||
color: Colors.grey.shade600, |
|||
fontSize: 14, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
) |
|||
: ListView.builder( |
|||
itemCount: _produitsFiltres.length, |
|||
itemBuilder: (context, index) { |
|||
final produit = _produitsFiltres[index]; |
|||
final isSelected = _produitCadeauSelectionne?.id == produit.id; |
|||
|
|||
return Card( |
|||
margin: const EdgeInsets.only(bottom: 8), |
|||
elevation: isSelected ? 4 : 1, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.circular(8), |
|||
side: BorderSide( |
|||
color: isSelected |
|||
? Colors.green.shade300 |
|||
: Colors.grey.shade200, |
|||
width: isSelected ? 2 : 1, |
|||
), |
|||
), |
|||
child: ListTile( |
|||
contentPadding: const EdgeInsets.all(12), |
|||
leading: Container( |
|||
width: 40, |
|||
height: 40, |
|||
decoration: BoxDecoration( |
|||
color: isSelected |
|||
? Colors.green.shade100 |
|||
: Colors.grey.shade100, |
|||
borderRadius: BorderRadius.circular(8), |
|||
), |
|||
child: Icon( |
|||
Icons.card_giftcard, |
|||
color: isSelected |
|||
? Colors.green.shade700 |
|||
: Colors.grey.shade600, |
|||
), |
|||
), |
|||
title: Text( |
|||
produit.name, |
|||
style: TextStyle( |
|||
fontWeight: isSelected |
|||
? FontWeight.bold |
|||
: FontWeight.normal, |
|||
), |
|||
), |
|||
subtitle: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Text( |
|||
'Prix normal: ${produit.price.toStringAsFixed(2)} MGA', |
|||
style: TextStyle( |
|||
fontSize: 12, |
|||
color: Colors.grey.shade600, |
|||
decoration: TextDecoration.lineThrough, |
|||
), |
|||
), |
|||
Row( |
|||
children: [ |
|||
Icon( |
|||
Icons.card_giftcard, |
|||
size: 14, |
|||
color: Colors.green.shade600, |
|||
), |
|||
const SizedBox(width: 4), |
|||
Text( |
|||
'GRATUIT', |
|||
style: TextStyle( |
|||
fontSize: 12, |
|||
color: Colors.green.shade700, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
if (produit.stock != null) |
|||
Text( |
|||
'Stock: ${produit.stock}', |
|||
style: TextStyle( |
|||
fontSize: 11, |
|||
color: Colors.grey.shade500, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
trailing: isSelected |
|||
? Icon( |
|||
Icons.check_circle, |
|||
color: Colors.green.shade700, |
|||
) |
|||
: null, |
|||
onTap: () { |
|||
setState(() { |
|||
_produitCadeauSelectionne = produit; |
|||
}); |
|||
}, |
|||
), |
|||
); |
|||
}, |
|||
), |
|||
), |
|||
|
|||
// Sélection de la quantité si un produit est sélectionné |
|||
if (_produitCadeauSelectionne != null) ...[ |
|||
const SizedBox(height: 16), |
|||
Container( |
|||
padding: const EdgeInsets.all(12), |
|||
decoration: BoxDecoration( |
|||
color: Colors.green.shade50, |
|||
borderRadius: BorderRadius.circular(8), |
|||
border: Border.all(color: Colors.green.shade200), |
|||
), |
|||
child: Row( |
|||
children: [ |
|||
Icon(Icons.card_giftcard, color: Colors.green.shade700), |
|||
const SizedBox(width: 8), |
|||
Expanded( |
|||
child: Text( |
|||
'Quantité de ${_produitCadeauSelectionne!.name}', |
|||
style: TextStyle( |
|||
fontWeight: FontWeight.w500, |
|||
color: Colors.green.shade700, |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
decoration: BoxDecoration( |
|||
color: Colors.white, |
|||
borderRadius: BorderRadius.circular(20), |
|||
border: Border.all(color: Colors.green.shade300), |
|||
), |
|||
child: Row( |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: [ |
|||
IconButton( |
|||
icon: const Icon(Icons.remove, size: 16), |
|||
onPressed: _quantiteCadeau > 1 |
|||
? () { |
|||
setState(() { |
|||
_quantiteCadeau--; |
|||
}); |
|||
} |
|||
: null, |
|||
), |
|||
Text( |
|||
_quantiteCadeau.toString(), |
|||
style: const TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
fontSize: 14, |
|||
), |
|||
), |
|||
IconButton( |
|||
icon: const Icon(Icons.add, size: 16), |
|||
onPressed: () { |
|||
final maxStock = _produitCadeauSelectionne!.stock ?? 99; |
|||
if (_quantiteCadeau < maxStock) { |
|||
setState(() { |
|||
_quantiteCadeau++; |
|||
}); |
|||
} |
|||
}, |
|||
), |
|||
], |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
], |
|||
], |
|||
), |
|||
), |
|||
actions: [ |
|||
TextButton( |
|||
onPressed: () => Get.back(), |
|||
child: const Text('Annuler'), |
|||
), |
|||
ElevatedButton.icon( |
|||
style: ElevatedButton.styleFrom( |
|||
backgroundColor: Colors.green.shade700, |
|||
foregroundColor: Colors.white, |
|||
padding: EdgeInsets.symmetric( |
|||
horizontal: isMobile ? 16 : 20, |
|||
vertical: isMobile ? 10 : 12, |
|||
), |
|||
), |
|||
icon: const Icon(Icons.card_giftcard), |
|||
label: Text( |
|||
isMobile ? 'Offrir' : 'Offrir le cadeau', |
|||
style: TextStyle(fontSize: isMobile ? 12 : 14), |
|||
), |
|||
onPressed: _produitCadeauSelectionne != null |
|||
? () { |
|||
Get.back(result: { |
|||
'produit': _produitCadeauSelectionne!, |
|||
'quantite': _quantiteCadeau, |
|||
}); |
|||
} |
|||
: null, |
|||
), |
|||
], |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,331 @@ |
|||
// Components/RemiseDialog.dart |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
import 'package:youmazgestion/Models/client.dart'; |
|||
import 'package:youmazgestion/Models/produit.dart'; |
|||
|
|||
class RemiseDialog extends StatefulWidget { |
|||
final Product product; |
|||
final int quantite; |
|||
final double prixUnitaire; |
|||
final DetailCommande? detailExistant; |
|||
|
|||
const RemiseDialog({ |
|||
super.key, |
|||
required this.product, |
|||
required this.quantite, |
|||
required this.prixUnitaire, |
|||
this.detailExistant, |
|||
}); |
|||
|
|||
@override |
|||
State<RemiseDialog> createState() => _RemiseDialogState(); |
|||
} |
|||
|
|||
class _RemiseDialogState extends State<RemiseDialog> { |
|||
final _formKey = GlobalKey<FormState>(); |
|||
final _valeurController = TextEditingController(); |
|||
|
|||
RemiseType _selectedType = RemiseType.pourcentage; |
|||
double _montantRemise = 0.0; |
|||
double _prixFinal = 0.0; |
|||
late double _sousTotal; |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
_sousTotal = widget.quantite * widget.prixUnitaire; |
|||
|
|||
// Si on modifie une remise existante |
|||
if (widget.detailExistant?.aRemise == true) { |
|||
_selectedType = widget.detailExistant!.remiseType!; |
|||
_valeurController.text = widget.detailExistant!.remiseValeur.toString(); |
|||
_calculateRemise(); |
|||
} else { |
|||
_prixFinal = _sousTotal; |
|||
} |
|||
} |
|||
|
|||
void _calculateRemise() { |
|||
final valeur = double.tryParse(_valeurController.text) ?? 0.0; |
|||
|
|||
setState(() { |
|||
if (_selectedType == RemiseType.pourcentage) { |
|||
final pourcentage = valeur.clamp(0.0, 100.0); |
|||
_montantRemise = _sousTotal * (pourcentage / 100); |
|||
} else { |
|||
_montantRemise = valeur.clamp(0.0, _sousTotal); |
|||
} |
|||
_prixFinal = _sousTotal - _montantRemise; |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
final isMobile = MediaQuery.of(context).size.width < 600; |
|||
|
|||
return AlertDialog( |
|||
title: Row( |
|||
children: [ |
|||
Container( |
|||
padding: const EdgeInsets.all(8), |
|||
decoration: BoxDecoration( |
|||
color: Colors.orange.shade100, |
|||
borderRadius: BorderRadius.circular(8), |
|||
), |
|||
child: Icon(Icons.discount, color: Colors.orange.shade700), |
|||
), |
|||
const SizedBox(width: 12), |
|||
Expanded( |
|||
child: Text( |
|||
'Appliquer une remise', |
|||
style: TextStyle(fontSize: isMobile ? 16 : 18), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
content: Container( |
|||
width: isMobile ? double.maxFinite : 400, |
|||
child: Form( |
|||
key: _formKey, |
|||
child: Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
// Informations du produit |
|||
Container( |
|||
padding: const EdgeInsets.all(12), |
|||
decoration: BoxDecoration( |
|||
color: Colors.blue.shade50, |
|||
borderRadius: BorderRadius.circular(8), |
|||
), |
|||
child: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Text( |
|||
widget.product.name, |
|||
style: const TextStyle( |
|||
fontSize: 14, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
const SizedBox(height: 4), |
|||
Text( |
|||
'Quantité: ${widget.quantite}', |
|||
style: const TextStyle(fontSize: 12), |
|||
), |
|||
Text( |
|||
'Prix unitaire: ${widget.prixUnitaire.toStringAsFixed(2)} MGA', |
|||
style: const TextStyle(fontSize: 12), |
|||
), |
|||
Text( |
|||
'Sous-total: ${_sousTotal.toStringAsFixed(2)} MGA', |
|||
style: const TextStyle( |
|||
fontSize: 12, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
|
|||
const SizedBox(height: 16), |
|||
|
|||
// Type de remise |
|||
const Text( |
|||
'Type de remise:', |
|||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), |
|||
), |
|||
const SizedBox(height: 8), |
|||
|
|||
Row( |
|||
children: [ |
|||
Expanded( |
|||
child: RadioListTile<RemiseType>( |
|||
title: const Text('Pourcentage (%)', style: TextStyle(fontSize: 12)), |
|||
value: RemiseType.pourcentage, |
|||
groupValue: _selectedType, |
|||
onChanged: (value) { |
|||
setState(() { |
|||
_selectedType = value!; |
|||
_calculateRemise(); |
|||
}); |
|||
}, |
|||
contentPadding: EdgeInsets.zero, |
|||
dense: true, |
|||
), |
|||
), |
|||
Expanded( |
|||
child: RadioListTile<RemiseType>( |
|||
title: const Text('Montant (MGA)', style: TextStyle(fontSize: 12)), |
|||
value: RemiseType.montant, |
|||
groupValue: _selectedType, |
|||
onChanged: (value) { |
|||
setState(() { |
|||
_selectedType = value!; |
|||
_calculateRemise(); |
|||
}); |
|||
}, |
|||
contentPadding: EdgeInsets.zero, |
|||
dense: true, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
|
|||
const SizedBox(height: 16), |
|||
|
|||
// Valeur de la remise |
|||
TextFormField( |
|||
controller: _valeurController, |
|||
decoration: InputDecoration( |
|||
labelText: _selectedType == RemiseType.pourcentage |
|||
? 'Pourcentage (0-100)' |
|||
: 'Montant en MGA', |
|||
prefixIcon: Icon( |
|||
_selectedType == RemiseType.pourcentage |
|||
? Icons.percent |
|||
: Icons.attach_money, |
|||
), |
|||
border: OutlineInputBorder( |
|||
borderRadius: BorderRadius.circular(8), |
|||
), |
|||
filled: true, |
|||
fillColor: Colors.grey.shade50, |
|||
), |
|||
keyboardType: const TextInputType.numberWithOptions(decimal: true), |
|||
inputFormatters: [ |
|||
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*')), |
|||
], |
|||
validator: (value) { |
|||
if (value == null || value.isEmpty) { |
|||
return 'Veuillez entrer une valeur'; |
|||
} |
|||
final valeur = double.tryParse(value); |
|||
if (valeur == null || valeur < 0) { |
|||
return 'Valeur invalide'; |
|||
} |
|||
if (_selectedType == RemiseType.pourcentage && valeur > 100) { |
|||
return 'Le pourcentage ne peut pas dépasser 100%'; |
|||
} |
|||
if (_selectedType == RemiseType.montant && valeur > _sousTotal) { |
|||
return 'La remise ne peut pas dépasser le sous-total'; |
|||
} |
|||
return null; |
|||
}, |
|||
onChanged: (value) => _calculateRemise(), |
|||
), |
|||
|
|||
const SizedBox(height: 16), |
|||
|
|||
// Aperçu du calcul |
|||
Container( |
|||
padding: const EdgeInsets.all(12), |
|||
decoration: BoxDecoration( |
|||
color: Colors.green.shade50, |
|||
borderRadius: BorderRadius.circular(8), |
|||
border: Border.all(color: Colors.green.shade200), |
|||
), |
|||
child: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
children: [ |
|||
const Text('Sous-total:', style: TextStyle(fontSize: 12)), |
|||
Text( |
|||
'${_sousTotal.toStringAsFixed(2)} MGA', |
|||
style: const TextStyle(fontSize: 12), |
|||
), |
|||
], |
|||
), |
|||
if (_montantRemise > 0) ...[ |
|||
const SizedBox(height: 4), |
|||
Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
children: [ |
|||
Text( |
|||
'Remise ${_selectedType == RemiseType.pourcentage ? "(${_valeurController.text}%)" : ""}:', |
|||
style: TextStyle( |
|||
fontSize: 12, |
|||
color: Colors.orange.shade700, |
|||
), |
|||
), |
|||
Text( |
|||
'-${_montantRemise.toStringAsFixed(2)} MGA', |
|||
style: TextStyle( |
|||
fontSize: 12, |
|||
color: Colors.orange.shade700, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
], |
|||
const Divider(height: 12), |
|||
Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
children: [ |
|||
const Text( |
|||
'Prix final:', |
|||
style: TextStyle( |
|||
fontSize: 14, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
Text( |
|||
'${_prixFinal.toStringAsFixed(2)} MGA', |
|||
style: TextStyle( |
|||
fontSize: 14, |
|||
fontWeight: FontWeight.bold, |
|||
color: Colors.green.shade700, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
], |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
actions: [ |
|||
if (widget.detailExistant?.aRemise == true) |
|||
TextButton.icon( |
|||
onPressed: () => Navigator.of(context).pop('supprimer'), |
|||
icon: const Icon(Icons.delete, color: Colors.red), |
|||
label: const Text('Supprimer remise', style: TextStyle(color: Colors.red)), |
|||
), |
|||
TextButton( |
|||
onPressed: () => Navigator.of(context).pop(), |
|||
child: const Text('Annuler'), |
|||
), |
|||
ElevatedButton( |
|||
onPressed: () { |
|||
if (_formKey.currentState!.validate()) { |
|||
final valeur = double.parse(_valeurController.text); |
|||
Navigator.of(context).pop({ |
|||
'type': _selectedType, |
|||
'valeur': valeur, |
|||
'montantRemise': _montantRemise, |
|||
'prixFinal': _prixFinal, |
|||
}); |
|||
} |
|||
}, |
|||
style: ElevatedButton.styleFrom( |
|||
backgroundColor: Colors.orange.shade700, |
|||
foregroundColor: Colors.white, |
|||
), |
|||
child: const Text('Appliquer'), |
|||
), |
|||
], |
|||
); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
_valeurController.dispose(); |
|||
super.dispose(); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,304 @@ |
|||
-- Script SQL pour créer la base de données guycom_database_v1 |
|||
-- Création des tables et insertion des données par défaut |
|||
|
|||
-- ===================================================== |
|||
-- CRÉATION DES TABLES |
|||
-- ===================================================== |
|||
|
|||
-- Table permissions |
|||
CREATE TABLE `permissions` ( |
|||
`id` int(11) NOT NULL AUTO_INCREMENT, |
|||
`name` varchar(255) NOT NULL, |
|||
PRIMARY KEY (`id`), |
|||
UNIQUE KEY `name` (`name`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; |
|||
|
|||
-- Table menu |
|||
CREATE TABLE `menu` ( |
|||
`id` int(11) NOT NULL AUTO_INCREMENT, |
|||
`name` varchar(255) NOT NULL, |
|||
`route` varchar(255) NOT NULL, |
|||
PRIMARY KEY (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; |
|||
|
|||
-- Table roles |
|||
CREATE TABLE `roles` ( |
|||
`id` int(11) NOT NULL AUTO_INCREMENT, |
|||
`designation` varchar(255) NOT NULL, |
|||
PRIMARY KEY (`id`), |
|||
UNIQUE KEY `designation` (`designation`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; |
|||
|
|||
-- Table points_de_vente |
|||
CREATE TABLE `points_de_vente` ( |
|||
`id` int(11) NOT NULL AUTO_INCREMENT, |
|||
`nom` varchar(255) NOT NULL, |
|||
PRIMARY KEY (`id`), |
|||
UNIQUE KEY `nom` (`nom`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; |
|||
|
|||
-- Table clients |
|||
CREATE TABLE `clients` ( |
|||
`id` int(11) NOT NULL AUTO_INCREMENT, |
|||
`nom` varchar(255) NOT NULL, |
|||
`prenom` varchar(255) NOT NULL, |
|||
`email` varchar(255) NOT NULL, |
|||
`telephone` varchar(255) NOT NULL, |
|||
`adresse` varchar(500) DEFAULT NULL, |
|||
`dateCreation` datetime NOT NULL, |
|||
`actif` tinyint(1) NOT NULL DEFAULT 1, |
|||
PRIMARY KEY (`id`), |
|||
UNIQUE KEY `email` (`email`), |
|||
KEY `idx_clients_email` (`email`), |
|||
KEY `idx_clients_telephone` (`telephone`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; |
|||
|
|||
-- Table users |
|||
CREATE TABLE `users` ( |
|||
`id` int(11) NOT NULL AUTO_INCREMENT, |
|||
`name` varchar(255) NOT NULL, |
|||
`lastname` varchar(255) NOT NULL, |
|||
`email` varchar(255) NOT NULL, |
|||
`password` varchar(255) NOT NULL, |
|||
`username` varchar(255) NOT NULL, |
|||
`role_id` int(11) NOT NULL, |
|||
`point_de_vente_id` int(11) DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
UNIQUE KEY `email` (`email`), |
|||
UNIQUE KEY `username` (`username`), |
|||
KEY `role_id` (`role_id`), |
|||
KEY `point_de_vente_id` (`point_de_vente_id`), |
|||
CONSTRAINT `users_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`), |
|||
CONSTRAINT `users_ibfk_2` FOREIGN KEY (`point_de_vente_id`) REFERENCES `points_de_vente` (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; |
|||
|
|||
-- Table products |
|||
CREATE TABLE `products` ( |
|||
`id` int(11) NOT NULL AUTO_INCREMENT, |
|||
`name` varchar(255) NOT NULL, |
|||
`price` decimal(10,2) NOT NULL, |
|||
`image` varchar(2000) DEFAULT NULL, |
|||
`category` varchar(255) NOT NULL, |
|||
`stock` int(11) NOT NULL DEFAULT 0, |
|||
`description` varchar(1000) DEFAULT NULL, |
|||
`qrCode` varchar(500) DEFAULT NULL, |
|||
`reference` varchar(255) DEFAULT NULL, |
|||
`point_de_vente_id` int(11) DEFAULT NULL, |
|||
`marque` varchar(255) DEFAULT NULL, |
|||
`ram` varchar(100) DEFAULT NULL, |
|||
`memoire_interne` varchar(100) DEFAULT NULL, |
|||
`imei` varchar(255) DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
UNIQUE KEY `imei` (`imei`), |
|||
KEY `point_de_vente_id` (`point_de_vente_id`), |
|||
KEY `idx_products_category` (`category`), |
|||
KEY `idx_products_reference` (`reference`), |
|||
KEY `idx_products_imei` (`imei`), |
|||
CONSTRAINT `products_ibfk_1` FOREIGN KEY (`point_de_vente_id`) REFERENCES `points_de_vente` (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=127 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; |
|||
|
|||
-- Table commandes |
|||
CREATE TABLE `commandes` ( |
|||
`id` int(11) NOT NULL AUTO_INCREMENT, |
|||
`clientId` int(11) NOT NULL, |
|||
`dateCommande` datetime NOT NULL, |
|||
`statut` int(11) NOT NULL DEFAULT 0, |
|||
`montantTotal` decimal(10,2) NOT NULL, |
|||
`notes` varchar(1000) DEFAULT NULL, |
|||
`dateLivraison` datetime DEFAULT NULL, |
|||
`commandeurId` int(11) DEFAULT NULL, |
|||
`validateurId` int(11) DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
KEY `commandeurId` (`commandeurId`), |
|||
KEY `validateurId` (`validateurId`), |
|||
KEY `idx_commandes_client` (`clientId`), |
|||
KEY `idx_commandes_date` (`dateCommande`), |
|||
CONSTRAINT `commandes_ibfk_1` FOREIGN KEY (`commandeurId`) REFERENCES `users` (`id`), |
|||
CONSTRAINT `commandes_ibfk_2` FOREIGN KEY (`validateurId`) REFERENCES `users` (`id`), |
|||
CONSTRAINT `commandes_ibfk_3` FOREIGN KEY (`clientId`) REFERENCES `clients` (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; |
|||
|
|||
-- Table details_commandes |
|||
CREATE TABLE `details_commandes` ( |
|||
`id` int(11) NOT NULL AUTO_INCREMENT, |
|||
`commandeId` int(11) NOT NULL, |
|||
`produitId` int(11) NOT NULL, |
|||
`quantite` int(11) NOT NULL, |
|||
`prixUnitaire` decimal(10,2) NOT NULL, |
|||
`sousTotal` decimal(10,2) NOT NULL, |
|||
`remise_type` enum('pourcentage','montant') DEFAULT NULL, |
|||
`remise_valeur` decimal(10,2) DEFAULT 0.00, |
|||
`montant_remise` decimal(10,2) DEFAULT 0.00, |
|||
`prix_final` decimal(10,2) NOT NULL DEFAULT 0.00, |
|||
`est_cadeau` tinyint(1) NOT NULL DEFAULT 0, |
|||
PRIMARY KEY (`id`), |
|||
KEY `produitId` (`produitId`), |
|||
KEY `idx_details_commande` (`commandeId`), |
|||
KEY `idx_est_cadeau` (`est_cadeau`), |
|||
CONSTRAINT `details_commandes_ibfk_1` FOREIGN KEY (`commandeId`) REFERENCES `commandes` (`id`) ON DELETE CASCADE, |
|||
CONSTRAINT `details_commandes_ibfk_2` FOREIGN KEY (`produitId`) REFERENCES `products` (`id`) |
|||
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; |
|||
|
|||
-- Table role_permissions |
|||
CREATE TABLE `role_permissions` ( |
|||
`role_id` int(11) NOT NULL, |
|||
`permission_id` int(11) NOT NULL, |
|||
PRIMARY KEY (`role_id`,`permission_id`), |
|||
KEY `permission_id` (`permission_id`), |
|||
CONSTRAINT `role_permissions_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE, |
|||
CONSTRAINT `role_permissions_ibfk_2` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; |
|||
|
|||
-- Table role_menu_permissions |
|||
CREATE TABLE `role_menu_permissions` ( |
|||
`role_id` int(11) NOT NULL, |
|||
`menu_id` int(11) NOT NULL, |
|||
`permission_id` int(11) NOT NULL, |
|||
PRIMARY KEY (`role_id`,`menu_id`,`permission_id`), |
|||
KEY `menu_id` (`menu_id`), |
|||
KEY `permission_id` (`permission_id`), |
|||
CONSTRAINT `role_menu_permissions_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE, |
|||
CONSTRAINT `role_menu_permissions_ibfk_2` FOREIGN KEY (`menu_id`) REFERENCES `menu` (`id`) ON DELETE CASCADE, |
|||
CONSTRAINT `role_menu_permissions_ibfk_3` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; |
|||
|
|||
-- ===================================================== |
|||
-- INSERTION DES DONNÉES PAR DÉFAUT |
|||
-- ===================================================== |
|||
|
|||
-- Insertion des permissions par défaut |
|||
INSERT INTO `permissions` (`name`) VALUES |
|||
('view'), |
|||
('create'), |
|||
('update'), |
|||
('delete'), |
|||
('admin'), |
|||
('manage'), |
|||
('read'); |
|||
|
|||
-- Insertion des menus par défaut |
|||
INSERT INTO `menu` (`name`, `route`) VALUES |
|||
('Accueil', '/accueil'), |
|||
('Ajouter un utilisateur', '/ajouter-utilisateur'), |
|||
('Modifier/Supprimer un utilisateur', '/modifier-utilisateur'), |
|||
('Ajouter un produit', '/ajouter-produit'), |
|||
('Modifier/Supprimer un produit', '/modifier-produit'), |
|||
('Bilan', '/bilan'), |
|||
('Gérer les rôles', '/gerer-roles'), |
|||
('Gestion de stock', '/gestion-stock'), |
|||
('Historique', '/historique'), |
|||
('Déconnexion', '/deconnexion'), |
|||
('Nouvelle commande', '/nouvelle-commande'), |
|||
('Gérer les commandes', '/gerer-commandes'), |
|||
('Points de vente', '/points-de-vente'); |
|||
|
|||
-- Insertion des rôles par défaut |
|||
INSERT INTO `roles` (`designation`) VALUES |
|||
('Super Admin'), |
|||
('Admin'), |
|||
('User'), |
|||
('commercial'), |
|||
('caisse'); |
|||
|
|||
-- Attribution de TOUTES les permissions à TOUS les menus pour le Super Admin |
|||
-- On utilise une sous-requête pour récupérer l'ID réel du rôle Super Admin |
|||
INSERT INTO `role_menu_permissions` (`role_id`, `menu_id`, `permission_id`) |
|||
SELECT r.id, m.id, p.id |
|||
FROM menu m |
|||
CROSS JOIN permissions p |
|||
CROSS JOIN roles r |
|||
WHERE r.designation = 'Super Admin'; |
|||
|
|||
-- Attribution de permissions basiques pour Admin |
|||
-- Accès en lecture/écriture à la plupart des menus sauf gestion des rôles |
|||
INSERT INTO `role_menu_permissions` (`role_id`, `menu_id`, `permission_id`) |
|||
SELECT r.id, m.id, p.id |
|||
FROM menu m |
|||
CROSS JOIN permissions p |
|||
CROSS JOIN roles r |
|||
WHERE r.designation = 'Admin' |
|||
AND m.name != 'Gérer les rôles' |
|||
AND p.name IN ('view', 'create', 'update', 'read'); |
|||
|
|||
-- Attribution de permissions basiques pour User |
|||
-- Accès principalement en lecture et quelques actions de base |
|||
INSERT INTO `role_menu_permissions` (`role_id`, `menu_id`, `permission_id`) |
|||
SELECT r.id, m.id, p.id |
|||
FROM menu m |
|||
CROSS JOIN permissions p |
|||
CROSS JOIN roles r |
|||
WHERE r.designation = 'User' |
|||
AND m.name IN ('Accueil', 'Nouvelle commande', 'Gérer les commandes', 'Gestion de stock', 'Historique') |
|||
AND p.name IN ('view', 'read', 'create'); |
|||
|
|||
-- Attribution de permissions pour Commercial |
|||
-- Accès aux commandes, clients, produits |
|||
INSERT INTO `role_menu_permissions` (`role_id`, `menu_id`, `permission_id`) |
|||
SELECT r.id, m.id, p.id |
|||
FROM menu m |
|||
CROSS JOIN permissions p |
|||
CROSS JOIN roles r |
|||
WHERE r.designation = 'commercial' |
|||
AND m.name IN ('Accueil', 'Nouvelle commande', 'Gérer les commandes', 'Bilan', 'Historique') |
|||
AND p.name IN ('view', 'create', 'update', 'read'); |
|||
|
|||
-- Attribution de permissions pour Caisse |
|||
-- Accès principalement aux commandes et stock |
|||
INSERT INTO `role_menu_permissions` (`role_id`, `menu_id`, `permission_id`) |
|||
SELECT r.id, m.id, p.id |
|||
FROM menu m |
|||
CROSS JOIN permissions p |
|||
CROSS JOIN roles r |
|||
WHERE r.designation = 'caisse' |
|||
AND m.name IN ('Accueil', 'Nouvelle commande', 'Gestion de stock') |
|||
AND p.name IN ('view', 'create', 'read'); |
|||
|
|||
-- Insertion du Super Admin par défaut |
|||
-- On utilise une sous-requête pour récupérer l'ID réel du rôle Super Admin |
|||
INSERT INTO `users` (`name`, `lastname`, `email`, `password`, `username`, `role_id`) |
|||
SELECT 'Super', 'Admin', 'superadmin@youmazgestion.com', 'admin123', 'superadmin', r.id |
|||
FROM roles r |
|||
WHERE r.designation = 'Super Admin'; |
|||
|
|||
-- ===================================================== |
|||
-- DONNÉES D'EXEMPLE (OPTIONNEL) |
|||
-- ===================================================== |
|||
|
|||
-- Insertion d'un point de vente d'exemple |
|||
INSERT INTO `points_de_vente` (`nom`) VALUES ('Magasin Principal'); |
|||
|
|||
-- Insertion d'un client d'exemple |
|||
INSERT INTO `clients` (`nom`, `prenom`, `email`, `telephone`, `adresse`, `dateCreation`, `actif`) VALUES |
|||
('Dupont', 'Jean', 'jean.dupont@email.com', '0123456789', '123 Rue de la Paix, Paris', NOW(), 1); |
|||
|
|||
-- ===================================================== |
|||
-- VÉRIFICATIONS |
|||
-- ===================================================== |
|||
|
|||
-- Afficher les rôles créés |
|||
SELECT 'RÔLES CRÉÉS:' as info; |
|||
SELECT * FROM roles; |
|||
|
|||
-- Afficher les permissions créées |
|||
SELECT 'PERMISSIONS CRÉÉES:' as info; |
|||
SELECT * FROM permissions; |
|||
|
|||
-- Afficher les menus créés |
|||
SELECT 'MENUS CRÉÉS:' as info; |
|||
SELECT * FROM menu; |
|||
|
|||
-- Afficher le Super Admin créé |
|||
SELECT 'SUPER ADMIN CRÉÉ:' as info; |
|||
SELECT u.username, u.email, r.designation as role |
|||
FROM users u |
|||
JOIN roles r ON u.role_id = r.id |
|||
WHERE r.designation = 'Super Admin'; |
|||
|
|||
-- Vérifier les permissions du Super Admin |
|||
SELECT 'PERMISSIONS SUPER ADMIN:' as info; |
|||
SELECT COUNT(*) as total_permissions_assignees |
|||
FROM role_menu_permissions rmp |
|||
INNER JOIN roles r ON rmp.role_id = r.id |
|||
WHERE r.designation = 'Super Admin'; |
|||
|
|||
SELECT 'Script terminé avec succès!' as resultat; |
|||
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
Loading…
Reference in new issue