Browse Source

commit fonctionnalite impec

31052025_02
b.razafimandimbihery 6 months ago
parent
commit
9eafda610f
  1. BIN
      assets/Orange_money.png
  2. BIN
      assets/airtel_money.png
  3. BIN
      assets/mvola.jpg
  4. 7
      lib/Components/paymentType.dart
  5. 263
      lib/Views/commandManagement.dart
  6. 304
      lib/Views/mobilepage.dart
  7. 3
      pubspec.yaml

BIN
assets/Orange_money.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/airtel_money.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
assets/mvola.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

7
lib/Components/paymentType.dart

@ -0,0 +1,7 @@
enum PaymentType {
cash,
card,
mvola,
orange,
airtel
}

263
lib/Views/commandManagement.dart

@ -1563,7 +1563,12 @@ class _CommandeActions extends StatelessWidget {
} }
} }
enum PaymentType { cash, card } enum PaymentType { cash, card ,
mvola,
orange,
airtel
}
class PaymentMethod { class PaymentMethod {
final PaymentType type; final PaymentType type;
@ -1584,7 +1589,28 @@ class PaymentMethodDialog extends StatefulWidget {
class _PaymentMethodDialogState extends State<PaymentMethodDialog> { class _PaymentMethodDialogState extends State<PaymentMethodDialog> {
PaymentType _selectedPayment = PaymentType.cash; PaymentType _selectedPayment = PaymentType.cash;
final _amountController = TextEditingController(); final _amountController = TextEditingController();
void _validatePayment() {
if (_selectedPayment == PaymentType.cash) {
final amountGiven = double.tryParse(_amountController.text) ?? 0;
if (amountGiven < widget.commande.montantTotal) {
Get.snackbar(
'Erreur',
'Le montant donné est insuffisant',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
}
Navigator.pop(context, PaymentMethod(
type: _selectedPayment,
amountGiven: _selectedPayment == PaymentType.cash
? double.parse(_amountController.text)
: widget.commande.montantTotal,
));
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -1598,94 +1624,189 @@ class _PaymentMethodDialogState extends State<PaymentMethodDialog> {
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final amount = double.tryParse(_amountController.text) ?? 0; final amount = double.tryParse(_amountController.text) ?? 0;
final change = amount - widget.commande.montantTotal; final change = amount - widget.commande.montantTotal;
return AlertDialog( return AlertDialog(
title: const Text('Méthode de paiement'), title: const Text('Méthode de paiement', style: TextStyle(fontWeight: FontWeight.bold)),
content: Column( content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
RadioListTile<PaymentType>( // Section Paiement mobile
title: const Text('Paiement en liquide'), 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, value: PaymentType.cash,
groupValue: _selectedPayment,
onChanged: (value) {
setState(() {
_selectedPayment = value!;
});
},
), ),
if (_selectedPayment == PaymentType.cash) if (_selectedPayment == PaymentType.cash) ...[
const SizedBox(height: 12),
TextField( TextField(
controller: _amountController, controller: _amountController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Montant donné', labelText: 'Montant donné',
prefixText: 'MGA ', prefixText: 'MGA ',
border: OutlineInputBorder(),
), ),
keyboardType: TextInputType.number, keyboardType: TextInputType.numberWithOptions(decimal: true),
onChanged: (value) { onChanged: (value) => setState(() {}),
setState(() {}); // Rafraîchir l'UI pour calculer la monnaie
},
), ),
RadioListTile<PaymentType>( const SizedBox(height: 8),
title: const Text('Carte bancaire'), Text(
value: PaymentType.card, 'Monnaie à rendre: ${change.toStringAsFixed(2)} MGA',
groupValue: _selectedPayment, style: TextStyle(
onChanged: (value) { fontSize: 16,
setState(() { fontWeight: FontWeight.bold,
_selectedPayment = value!; color: change >= 0 ? Colors.green : Colors.red,
});
},
),
if (_selectedPayment == PaymentType.cash)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'Monnaie à rendre: ${change.toStringAsFixed(2)} MGA',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: change >= 0 ? Colors.green : Colors.red,
),
), ),
), ),
],
], ],
), ),
actions: [ ),
TextButton( actions: [
onPressed: () => Navigator.pop(context), TextButton(
child: const Text('Annuler'), onPressed: () => Navigator.pop(context),
child: const Text('Annuler', style: TextStyle(color: Colors.grey)),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
), ),
ElevatedButton( onPressed: _validatePayment,
onPressed: () { child: const Text('Confirmer'),
final amount = _selectedPayment == PaymentType.cash ),
? double.tryParse(_amountController.text) ?? 0 ],
: widget.commande.montantTotal; );
}
if (_selectedPayment == PaymentType.cash && amount < widget.commande.montantTotal) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Le montant donné est insuffisant'),
backgroundColor: Colors.red,
),
);
return;
}
Navigator.pop( Widget _buildMobileMoneyTile({
context, required String title,
PaymentMethod( required String imagePath,
type: _selectedPayment, required PaymentType value,
amountGiven: amount, }) {
), return Card(
); elevation: 2,
}, shape: RoundedRectangleBorder(
child: const Text('Confirmer'), 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),
],
),
),
),
);
}
} }

304
lib/Views/mobilepage.dart

@ -258,14 +258,31 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
} }
void _showClientFormDialog() { void _showClientFormDialog() {
Get.dialog( Get.dialog(
AlertDialog( AlertDialog(
title: const Text('Informations Client'), title: Row(
content: SingleChildScrollView( children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.person_add, color: Colors.blue.shade700),
),
const SizedBox(width: 12),
const Text('Informations Client'),
],
),
content: Container(
width: 600,
constraints: const BoxConstraints(maxHeight: 600),
child: SingleChildScrollView(
child: Form( child: Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildTextFormField( _buildTextFormField(
controller: _nomController, controller: _nomController,
@ -307,38 +324,35 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildCommercialDropdown(), _buildCommercialDropdown(),
], ],
), ),
), ),
), ),
actions: [
TextButton(
onPressed: () => Get.back(),
child: const Text('Annuler'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
),
onPressed: () {
if (_formKey.currentState!.validate()) {
Get.back();
Get.snackbar(
'Succès',
'Informations client enregistrées',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green,
colorText: Colors.white,
);
}
},
child: const Text('Enregistrer'),
),
],
), ),
); actions: [
} TextButton(
onPressed: () => Get.back(),
child: const Text('Annuler'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
if (_formKey.currentState!.validate()) {
Get.back();
// Au lieu d'afficher juste un message, on valide directement la commande
_submitOrder();
}
},
child: const Text('Valider la commande'), // Changement de texte ici
),
],
),
);
}
Widget _buildTextFormField({ Widget _buildTextFormField({
required TextEditingController controller, required TextEditingController controller,
@ -464,7 +478,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
children: [ children: [
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'${product.price.toStringAsFixed(2)} DA', '${product.price.toStringAsFixed(2)} MGA',
style: TextStyle( style: TextStyle(
color: Colors.green.shade700, color: Colors.green.shade700,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -626,9 +640,9 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
child: const Icon(Icons.shopping_bag, size: 20), child: const Icon(Icons.shopping_bag, size: 20),
), ),
title: Text(product.name), title: Text(product.name),
subtitle: Text('${entry.value} x ${product.price.toStringAsFixed(2)} DA'), subtitle: Text('${entry.value} x ${product.price.toStringAsFixed(2)} MGA'),
trailing: Text( trailing: Text(
'${(entry.value * product.price).toStringAsFixed(2)} DA', '${(entry.value * product.price).toStringAsFixed(2)} MGA',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.blue.shade800, color: Colors.blue.shade800,
@ -658,7 +672,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
), ),
Text( Text(
'${total.toStringAsFixed(2)} DA', '${total.toStringAsFixed(2)} MGA',
style: const TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -708,122 +722,122 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
} }
Future<void> _submitOrder() async { Future<void> _submitOrder() async {
if (_nomController.text.isEmpty || // Vérifier d'abord si le panier est vide
_prenomController.text.isEmpty || final itemsInCart = _quantites.entries.where((e) => e.value > 0).toList();
_emailController.text.isEmpty || if (itemsInCart.isEmpty) {
_telephoneController.text.isEmpty || Get.snackbar(
_adresseController.text.isEmpty) { 'Panier vide',
Get.back(); // Ferme le bottom sheet 'Veuillez ajouter des produits à votre commande',
Get.snackbar( snackPosition: SnackPosition.BOTTOM,
'Informations manquantes', backgroundColor: Colors.red,
'Veuillez remplir les informations client', colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM, );
backgroundColor: Colors.red, _showCartBottomSheet(); // Ouvrir le panier pour montrer qu'il est vide
colorText: Colors.white, return;
); }
_showClientFormDialog();
return;
}
final itemsInCart = _quantites.entries.where((e) => e.value > 0).toList();
if (itemsInCart.isEmpty) {
Get.snackbar(
'Panier vide',
'Veuillez ajouter des produits à votre commande',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
setState(() {
_isLoading = true;
});
// Créer le client // Ensuite vérifier les informations client
final client = Client( if (_nomController.text.isEmpty ||
nom: _nomController.text, _prenomController.text.isEmpty ||
prenom: _prenomController.text, _emailController.text.isEmpty ||
email: _emailController.text, _telephoneController.text.isEmpty ||
telephone: _telephoneController.text, _adresseController.text.isEmpty) {
adresse: _adresseController.text, Get.snackbar(
dateCreation: DateTime.now(), 'Informations manquantes',
'Veuillez remplir les informations client',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
); );
_showClientFormDialog();
return;
}
// Calculer le total et préparer les détails setState(() {
double total = 0; _isLoading = true;
final details = <DetailCommande>[]; });
for (final entry in itemsInCart) { // Créer le client
final product = _products.firstWhere((p) => p.id == entry.key); final client = Client(
total += entry.value * product.price; nom: _nomController.text,
prenom: _prenomController.text,
details.add(DetailCommande( email: _emailController.text,
commandeId: 0, telephone: _telephoneController.text,
produitId: product.id!, adresse: _adresseController.text,
quantite: entry.value, dateCreation: DateTime.now(),
prixUnitaire: product.price, );
sousTotal: entry.value * product.price,
)); // Calculer le total et préparer les détails
} double total = 0;
final details = <DetailCommande>[];
for (final entry in itemsInCart) {
final product = _products.firstWhere((p) => p.id == entry.key);
total += entry.value * product.price;
details.add(DetailCommande(
commandeId: 0,
produitId: product.id!,
quantite: entry.value,
prixUnitaire: product.price,
sousTotal: entry.value * product.price,
));
}
// Créer la commande // Créer la commande
final commande = Commande( final commande = Commande(
clientId: 0, clientId: 0,
dateCommande: DateTime.now(), dateCommande: DateTime.now(),
statut: StatutCommande.enAttente, statut: StatutCommande.enAttente,
montantTotal: total, montantTotal: total,
notes: 'Commande passée via l\'application', notes: 'Commande passée via l\'application',
commandeurId: _selectedCommercialUser?.id, commandeurId: _selectedCommercialUser?.id,
);
try {
await _appDatabase.createCommandeComplete(client, commande, details);
// Afficher le dialogue de confirmation
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Commande Validée'),
content: const Text('Votre commande a été enregistrée et expédiée avec succès.'),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
// Réinitialiser le formulaire
_nomController.clear();
_prenomController.clear();
_emailController.clear();
_telephoneController.clear();
_adresseController.clear();
setState(() {
_quantites.clear();
_isLoading = false;
});
},
child: const Text('OK'),
),
],
),
); );
try { } catch (e) {
await _appDatabase.createCommandeComplete(client, commande, details); setState(() {
_isLoading = false;
Get.back(); // Ferme le bottom sheet });
// Afficher le dialogue de confirmation Get.snackbar(
await showDialog( 'Erreur',
context: context, 'Une erreur est survenue: ${e.toString()}',
builder: (context) => AlertDialog( snackPosition: SnackPosition.BOTTOM,
title: const Text('Commande Validée'), backgroundColor: Colors.red,
content: const Text('Votre commande a été enregistrée avec succès.'), colorText: Colors.white,
actions: [ );
TextButton(
onPressed: () {
Navigator.pop(context);
// Réinitialiser le formulaire
_nomController.clear();
_prenomController.clear();
_emailController.clear();
_telephoneController.clear();
_adresseController.clear();
setState(() {
_quantites.clear();
_isLoading = false;
});
},
child: const Text('OK'),
),
],
),
);
} catch (e) {
setState(() {
_isLoading = false;
});
Get.snackbar(
'Erreur',
'Une erreur est survenue: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
} }
}
@override @override
void dispose() { void dispose() {

3
pubspec.yaml

@ -102,6 +102,9 @@ flutter:
- assets/database/usersdb.db - assets/database/usersdb.db
- assets/database/work.db - assets/database/work.db
- assets/database/roles.db - assets/database/roles.db
- assets/airtel_money.png
- assets/mvola.jpg
- assets/Orange_money.png
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware # https://flutter.dev/assets-and-images/#resolution-aware

Loading…
Cancel
Save