fixed
This commit is contained in:
commit
6bc8373cad
@ -2,7 +2,7 @@
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FLASHLIGHT" />
|
||||
<application
|
||||
android:label="my_app"
|
||||
android:label="GUYCOM"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
|
||||
@ -2579,6 +2579,22 @@ Future<int> validerTransfert(int demandeId, int validateurId) async {
|
||||
final sourceId = fields['point_de_vente_source_id'] as int;
|
||||
final destinationId = fields['point_de_vente_destination_id'] as int;
|
||||
|
||||
|
||||
final getpointDeventeSource = await db.query(
|
||||
'Select point_de_vente_source_id FROM demandes_transfert WHERE id=?',[demandeId]
|
||||
);
|
||||
final getpointDeventeDest = await db.query(
|
||||
'Select point_de_vente_destination_id FROM demandes_transfert WHERE id=?',[demandeId]
|
||||
);
|
||||
final getpointDeventeSourceValue = getpointDeventeSource.first.fields['point_de_vente_source_id'];
|
||||
final getpointDeventedestValue = getpointDeventeDest.first.fields['point_de_vente_destination_id'];
|
||||
if(getpointDeventeSourceValue==getpointDeventedestValue){
|
||||
await db.query('update products set point_de_vente_id=? where id = ?',[getpointDeventedestValue,produitId]);
|
||||
}else{
|
||||
|
||||
|
||||
|
||||
|
||||
// 2. Vérifier le stock source
|
||||
final stockSource = await db.query(
|
||||
'SELECT stock FROM products WHERE id = ? AND point_de_vente_id = ? FOR UPDATE',
|
||||
@ -2646,7 +2662,7 @@ Future<int> validerTransfert(int demandeId, int validateurId) async {
|
||||
null, // IMEI doit être unique donc on ne le copie pas
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
// 5. Mettre à jour le statut de la demande
|
||||
await db.query('''
|
||||
UPDATE demandes_transfert
|
||||
|
||||
@ -205,7 +205,7 @@ class _GestionTransfertsPageState extends State<GestionTransfertsPage> with Tick
|
||||
),
|
||||
Text('Référence: ${demande['produit_reference']}'),
|
||||
Text('Quantité: ${demande['quantite']}'),
|
||||
Text('De: ${demande['point_vente_source']}'),
|
||||
Text(demande['point_vente_source'] == demande['point_vente_destination']?'De: Non specifier' : 'De: ${demande['point_vente_source']}'),
|
||||
Text('Vers: ${demande['point_vente_destination']}'),
|
||||
Text(
|
||||
'Stock disponible: $stockDisponible',
|
||||
@ -602,7 +602,7 @@ class _GestionTransfertsPageState extends State<GestionTransfertsPage> with Tick
|
||||
statutIcon = Icons.check_circle;
|
||||
statutText = 'Validée';
|
||||
break;
|
||||
case 'rejetee':
|
||||
case 'refusee':
|
||||
statutColor = Colors.red;
|
||||
statutIcon = Icons.cancel;
|
||||
statutText = 'Rejetée';
|
||||
@ -695,7 +695,7 @@ class _GestionTransfertsPageState extends State<GestionTransfertsPage> with Tick
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${demande['point_vente_source'] ?? 'N/A'}',
|
||||
demande['point_vente_source']==demande['point_vente_destination']?"Non specifier" : '${demande['point_vente_source'] ?? 'N/A'}',
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
@ -26,6 +26,7 @@ class ProductManagementPage extends StatefulWidget {
|
||||
|
||||
class _ProductManagementPageState extends State<ProductManagementPage> {
|
||||
final AppDatabase _productDatabase = AppDatabase.instance;
|
||||
final AppDatabase _appDatabase = AppDatabase.instance;
|
||||
final UserController _userController = Get.find<UserController>();
|
||||
|
||||
List<Product> _products = [];
|
||||
@ -99,6 +100,494 @@ bool _isUserSuperAdmin() {
|
||||
bool autoGenerateReference = true;
|
||||
bool showAddNewPoint = false;
|
||||
|
||||
// 🎨 Widget pour les cartes d'information
|
||||
Widget _buildInfoCard(String label, String value, IconData icon, Color color) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: color.withOpacity(0.3)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: color, size: 20),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
// 🎨 Widget pour les étapes de transfert
|
||||
Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Color color) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: color.withOpacity(0.3)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 16),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
pointDeVente,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
// 🎨 INTERFACE AMÉLIORÉE: Dialog moderne pour demande de transfert
|
||||
Future<void> _showDemandeTransfertDialog(Product product) async {
|
||||
final quantiteController = TextEditingController(text: '1');
|
||||
final notesController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
// Récupérer les infos du point de vente source
|
||||
final pointDeVenteSource = await _appDatabase.getPointDeVenteNomById(product.pointDeVenteId ?? 0);
|
||||
final pointDeVenteDestination = await _appDatabase.getPointDeVenteNomById(_userController.pointDeVenteId);
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Container(
|
||||
width: 400,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// En-tête avec design moderne
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Colors.blue.shade600, Colors.blue.shade700],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.swap_horizontal_circle,
|
||||
size: 48,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Demande de transfert',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Transférer un produit entre points de vente',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Contenu principal
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Informations du produit
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade100,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.inventory_2,
|
||||
color: Colors.blue.shade700,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Produit à transférer',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
product.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildInfoCard(
|
||||
'Prix unitaire',
|
||||
'${product.price.toStringAsFixed(2)} MGA',
|
||||
Icons.attach_money,
|
||||
Colors.green,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _buildInfoCard(
|
||||
'Stock disponible',
|
||||
'${product.stock ?? 0}',
|
||||
Icons.inventory,
|
||||
product.stock != null && product.stock! > 0
|
||||
? Colors.green
|
||||
: Colors.red,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (product.reference != null && product.reference!.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Référence: ${product.reference}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Informations de transfert
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.orange.shade200),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.arrow_forward, color: Colors.orange.shade700),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Informations de transfert',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.orange.shade700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildTransferStep(
|
||||
'DE',
|
||||
pointDeVenteSource ?? 'Chargement...',
|
||||
Icons.store_outlined,
|
||||
Colors.red.shade600,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Icon(
|
||||
Icons.arrow_forward,
|
||||
color: Colors.orange.shade700,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildTransferStep(
|
||||
'VERS',
|
||||
pointDeVenteDestination ?? 'Chargement...',
|
||||
Icons.store,
|
||||
Colors.green.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Champ quantité avec design amélioré
|
||||
Text(
|
||||
'Quantité à transférer',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
int currentQty = int.tryParse(quantiteController.text) ?? 1;
|
||||
if (currentQty > 1) {
|
||||
quantiteController.text = (currentQty - 1).toString();
|
||||
}
|
||||
},
|
||||
icon: Icon(Icons.remove, color: Colors.grey.shade600),
|
||||
),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: quantiteController,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16),
|
||||
hintText: 'Quantité',
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
keyboardType: TextInputType.number,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Veuillez entrer une quantité';
|
||||
}
|
||||
final qty = int.tryParse(value) ?? 0;
|
||||
if (qty <= 0) {
|
||||
return 'Quantité invalide';
|
||||
}
|
||||
if (product.stock != null && qty > product.stock!) {
|
||||
return 'Quantité supérieure au stock disponible';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
int currentQty = int.tryParse(quantiteController.text) ?? 1;
|
||||
int maxStock = product.stock ?? 999;
|
||||
if (currentQty < maxStock) {
|
||||
quantiteController.text = (currentQty + 1).toString();
|
||||
}
|
||||
},
|
||||
icon: Icon(Icons.add, color: Colors.grey.shade600),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
|
||||
// Boutons d'action avec design moderne
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Annuler',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
final qty = int.tryParse(quantiteController.text) ?? 0;
|
||||
if (qty <= 0) {
|
||||
Get.snackbar(
|
||||
'Erreur',
|
||||
'Quantité invalide',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setState(() => _isLoading = true);
|
||||
Navigator.pop(context);
|
||||
|
||||
await _appDatabase.createDemandeTransfert(
|
||||
produitId: product.id!,
|
||||
pointDeVenteSourceId: product.pointDeVenteId!,
|
||||
pointDeVenteDestinationId: _userController.pointDeVenteId,
|
||||
demandeurId: _userController.userId,
|
||||
quantite: qty,
|
||||
notes: notesController.text.isNotEmpty
|
||||
? notesController.text
|
||||
: 'Demande de transfert depuis l\'application mobile',
|
||||
);
|
||||
|
||||
Get.snackbar(
|
||||
'Demande envoyée ✅',
|
||||
'Votre demande de transfert de $qty unité(s) a été enregistrée et sera traitée prochainement.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
duration: const Duration(seconds: 4),
|
||||
icon: const Icon(Icons.check_circle, color: Colors.white),
|
||||
);
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
'Erreur',
|
||||
'Impossible d\'envoyer la demande: ${e.toString()}',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.send, color: Colors.white),
|
||||
label: const Text(
|
||||
'Envoyer la demande',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade600,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Fonction pour mettre à jour le QR preview
|
||||
void updateQrPreview() {
|
||||
if (nameController.text.isNotEmpty) {
|
||||
@ -1253,25 +1742,32 @@ bool _isUserSuperAdmin() {
|
||||
}
|
||||
|
||||
// Assigner le point de vente de l'utilisateur au produit
|
||||
final updatedProduct = Product(
|
||||
id: foundProduct.id,
|
||||
name: foundProduct.name,
|
||||
price: foundProduct.price,
|
||||
image: foundProduct.image,
|
||||
category: foundProduct.category,
|
||||
description: foundProduct.description,
|
||||
stock: foundProduct.stock,
|
||||
qrCode: foundProduct.qrCode,
|
||||
reference: foundProduct.reference,
|
||||
marque: foundProduct.marque,
|
||||
ram: foundProduct.ram,
|
||||
memoireInterne: foundProduct.memoireInterne,
|
||||
imei: foundProduct.imei,
|
||||
pointDeVenteId:
|
||||
_userController.pointDeVenteId, // Nouveau point de vente
|
||||
// final updatedProduct = Product(
|
||||
// id: foundProduct.id,
|
||||
// name: foundProduct.name,
|
||||
// price: foundProduct.price,
|
||||
// image: foundProduct.image,
|
||||
// category: foundProduct.category,
|
||||
// description: foundProduct.description,
|
||||
// stock: foundProduct.stock,
|
||||
// qrCode: foundProduct.qrCode,
|
||||
// reference: foundProduct.reference,
|
||||
// marque: foundProduct.marque,
|
||||
// ram: foundProduct.ram,
|
||||
// memoireInterne: foundProduct.memoireInterne,
|
||||
// imei: foundProduct.imei,
|
||||
// pointDeVenteId:
|
||||
// _userController.pointDeVenteId, // Nouveau point de vente
|
||||
// );
|
||||
await _appDatabase.createDemandeTransfert(
|
||||
produitId: foundProduct.id!,
|
||||
pointDeVenteSourceId: _userController.pointDeVenteId,
|
||||
pointDeVenteDestinationId: _userController.pointDeVenteId,
|
||||
demandeurId: _userController.userId,
|
||||
quantite: foundProduct.stock,
|
||||
notes: 'produit non assigner',
|
||||
);
|
||||
|
||||
await _productDatabase.updateProduct(updatedProduct);
|
||||
// await _productDatabase.updateProduct(updatedProduct);
|
||||
|
||||
// Recharger les produits pour refléter les changements
|
||||
_loadProducts();
|
||||
@ -1311,7 +1807,7 @@ bool _isUserSuperAdmin() {
|
||||
child: Icon(Icons.check_circle, color: Colors.green.shade700),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(child: Text('Attribution réussie !')),
|
||||
const Expanded(child: Text( 'demande attribution réussie en attente de validation!')),
|
||||
],
|
||||
),
|
||||
content: Column(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -608,94 +608,286 @@ void _modifierQuantite(int productId, int nouvelleQuantite) {
|
||||
}
|
||||
|
||||
void _showProductFoundAndAddedDialog(Product product, int newQuantity) {
|
||||
final isProduitCommandable = _isProduitCommandable(product);
|
||||
final canRequestTransfer = product.stock != null && product.stock! >= 1;
|
||||
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: Row(
|
||||
Dialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header avec icône de succès
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.green.shade200),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.shade100,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.check_circle,
|
||||
color: Colors.green.shade700,
|
||||
size: 24,
|
||||
),
|
||||
child: Icon(Icons.check_circle, color: Colors.green.shade700),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(child: Text('Produit identifié et ajouté !')),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Produit identifié !',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Ajouté au panier avec succès',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.green.shade700,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Informations du produit
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
product.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (product.imei != null && product.imei!.isNotEmpty)
|
||||
Text('IMEI: ${product.imei}'),
|
||||
if (product.reference != null && product.reference!.isNotEmpty)
|
||||
Text('Référence: ${product.reference}'),
|
||||
Text('Prix: ${product.price.toStringAsFixed(2)} MGA'),
|
||||
Text('Quantité dans le panier: $newQuantity'),
|
||||
if (product.stock != null)
|
||||
Text('Stock restant: ${product.stock! - newQuantity}'),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Détails du produit en grille
|
||||
_buildProductDetailRow('Prix', '${product.price.toStringAsFixed(2)} MGA'),
|
||||
_buildProductDetailRow('Quantité ajoutée', '$newQuantity'),
|
||||
|
||||
if (product.imei != null && product.imei!.isNotEmpty)
|
||||
_buildProductDetailRow('IMEI', product.imei!),
|
||||
|
||||
if (product.reference != null && product.reference!.isNotEmpty)
|
||||
_buildProductDetailRow('Référence', product.reference!),
|
||||
|
||||
if (product.stock != null)
|
||||
_buildProductDetailRow('Stock restant', '${product.stock! - newQuantity}'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Badge identification automatique
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Colors.blue.shade50,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: Colors.blue.shade200),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.auto_awesome,
|
||||
color: Colors.green.shade700, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'Produit identifié automatiquement',
|
||||
color: Colors.blue.shade700, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'Identifié automatiquement',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.blue.shade700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('Continuer'),
|
||||
),
|
||||
ElevatedButton(
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Boutons d'action redessinés
|
||||
Column(
|
||||
children: [
|
||||
// Bouton principal selon les permissions
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: (!isProduitCommandable && !_isUserSuperAdmin())
|
||||
? ElevatedButton.icon(
|
||||
onPressed: canRequestTransfer
|
||||
? () {
|
||||
Get.back();
|
||||
_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,
|
||||
margin: const EdgeInsets.all(16),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.swap_horiz, size: 20),
|
||||
label: const Text(
|
||||
'Demander un transfert',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: canRequestTransfer
|
||||
? Colors.orange.shade600
|
||||
: Colors.grey.shade400,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
)
|
||||
: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
_ajouterAuPanier(product, 1);
|
||||
Get.back();
|
||||
_showCartBottomSheet();
|
||||
},
|
||||
icon: const Icon(Icons.shopping_cart, size: 20),
|
||||
label: const Text(
|
||||
'Voir le panier',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green.shade700,
|
||||
backgroundColor: Colors.green.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Text('Voir le panier'),
|
||||
),
|
||||
ElevatedButton(
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Boutons secondaires
|
||||
Row(
|
||||
children: [
|
||||
// Continuer
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 44,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => Get.back(),
|
||||
icon: const Icon(Icons.close, size: 18),
|
||||
label: const Text('Continuer'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.grey.shade700,
|
||||
side: BorderSide(color: Colors.grey.shade300),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Scanner encore
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 44,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
_startAutomaticScanning(); // Scanner un autre produit
|
||||
_startAutomaticScanning();
|
||||
},
|
||||
icon: const Icon(Icons.qr_code_scanner, size: 18),
|
||||
label: const Text('Scanner'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade700,
|
||||
backgroundColor: Colors.blue.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Widget helper pour les détails du produit
|
||||
Widget _buildProductDetailRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: Text(
|
||||
'$label:',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
child: const Text('Scanner encore'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -1899,9 +2091,9 @@ Widget _buildUserPointDeVenteInfo() {
|
||||
// 6. Ajoutez cette méthode pour filtrer les produits par point de vente
|
||||
// 🎯 MODIFIÉ: Dropdown avec gestion améliorée
|
||||
Widget _buildPointDeVenteFilter() {
|
||||
if (!_isUserSuperAdmin()) {
|
||||
return const SizedBox.shrink(); // Cacher pour les non-admins
|
||||
}
|
||||
// if (!_isUserSuperAdmin()) {
|
||||
// return const SizedBox.shrink(); // Cacher pour les non-admins
|
||||
// }
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
@ -2630,7 +2822,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
||||
const SizedBox(height: 4),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.swap_horiz, size: 14),
|
||||
label: const Text('Demander transfert'),
|
||||
label:!isMobile ? const Text('Demander transfertt'):const SizedBox.shrink(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: (product.stock != null && product.stock! >= 1)
|
||||
? Colors.blue.shade700
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
// Config/database_config.dart - Version améliorée
|
||||
class DatabaseConfig {
|
||||
// static const String host = '10.0.2.2';
|
||||
//static const String host = '172.20.10.5';
|
||||
static const String host = 'localhost';
|
||||
static const int port = 3306;
|
||||
static const String username = 'root';
|
||||
@ -17,7 +19,7 @@ class DatabaseConfig {
|
||||
static const int maxConnections = 10;
|
||||
static const int minConnections = 2;
|
||||
|
||||
static bool get isDevelopment => true;
|
||||
static bool get isDevelopment => false;
|
||||
|
||||
static Map<String, dynamic> getConfig() {
|
||||
if (isDevelopment) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user