@ -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,9 +2394,7 @@ 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: detailPanier ? . estCadeau = = true
? Border . all ( color: Colors . green . shade300 , width: 2 )
: detailPanier ? . aRemise = = true
? Border . all ( color: Colors . orange . shade300 , width: 2 )
@ -2403,14 +2410,13 @@ 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
@ -2433,8 +2439,6 @@ 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
@ -2445,10 +2449,13 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
) ,
) ,
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 ,
) ,
) ,
) ,
/ / 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,13 +2752,11 @@ 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
color: detailPanier ? . estCadeau = = true
? Colors . green . shade50
: isCurrentUserPointDeVente
? Colors . orange . shade50
@ -2756,14 +2766,16 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
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,27 +2835,17 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
] ,
) ,
const SizedBox ( height: 4 ) ,
/ / Bouton demande de transfert
ElevatedButton . icon (
icon: const Icon ( Icons . swap_horiz , size: 14 ) ,
label: ! isMobile ? const Text ( ' Demander transfertt ' ) : const SizedBox . shrink ( ) ,
label: ! isMobile ? const Text ( ' Demander transfert ' ) : const SizedBox . shrink ( ) ,
style: ElevatedButton . styleFrom (
backgroundColor: ( product . stock ! = null & & product . stock ! > = 1 )
? Colors . blue . shade700
: Colors . grey . shade400 ,
backgroundColor: Colors . blue . shade700 ,
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 ,
) ;
} ,
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 ,