changement au nivaue de scan
This commit is contained in:
parent
332ed228ae
commit
55e896775d
@ -2579,6 +2579,22 @@ Future<int> validerTransfert(int demandeId, int validateurId) async {
|
|||||||
final sourceId = fields['point_de_vente_source_id'] as int;
|
final sourceId = fields['point_de_vente_source_id'] as int;
|
||||||
final destinationId = fields['point_de_vente_destination_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
|
// 2. Vérifier le stock source
|
||||||
final stockSource = await db.query(
|
final stockSource = await db.query(
|
||||||
'SELECT stock FROM products WHERE id = ? AND point_de_vente_id = ? FOR UPDATE',
|
'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
|
null, // IMEI doit être unique donc on ne le copie pas
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 5. Mettre à jour le statut de la demande
|
// 5. Mettre à jour le statut de la demande
|
||||||
await db.query('''
|
await db.query('''
|
||||||
UPDATE demandes_transfert
|
UPDATE demandes_transfert
|
||||||
|
|||||||
@ -205,7 +205,7 @@ class _GestionTransfertsPageState extends State<GestionTransfertsPage> with Tick
|
|||||||
),
|
),
|
||||||
Text('Référence: ${demande['produit_reference']}'),
|
Text('Référence: ${demande['produit_reference']}'),
|
||||||
Text('Quantité: ${demande['quantite']}'),
|
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('Vers: ${demande['point_vente_destination']}'),
|
||||||
Text(
|
Text(
|
||||||
'Stock disponible: $stockDisponible',
|
'Stock disponible: $stockDisponible',
|
||||||
@ -602,7 +602,7 @@ class _GestionTransfertsPageState extends State<GestionTransfertsPage> with Tick
|
|||||||
statutIcon = Icons.check_circle;
|
statutIcon = Icons.check_circle;
|
||||||
statutText = 'Validée';
|
statutText = 'Validée';
|
||||||
break;
|
break;
|
||||||
case 'rejetee':
|
case 'refusee':
|
||||||
statutColor = Colors.red;
|
statutColor = Colors.red;
|
||||||
statutIcon = Icons.cancel;
|
statutIcon = Icons.cancel;
|
||||||
statutText = 'Rejetée';
|
statutText = 'Rejetée';
|
||||||
@ -695,7 +695,7 @@ class _GestionTransfertsPageState extends State<GestionTransfertsPage> with Tick
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
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),
|
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -26,6 +26,7 @@ class ProductManagementPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _ProductManagementPageState extends State<ProductManagementPage> {
|
class _ProductManagementPageState extends State<ProductManagementPage> {
|
||||||
final AppDatabase _productDatabase = AppDatabase.instance;
|
final AppDatabase _productDatabase = AppDatabase.instance;
|
||||||
|
final AppDatabase _appDatabase = AppDatabase.instance;
|
||||||
final UserController _userController = Get.find<UserController>();
|
final UserController _userController = Get.find<UserController>();
|
||||||
|
|
||||||
List<Product> _products = [];
|
List<Product> _products = [];
|
||||||
@ -99,6 +100,494 @@ bool _isUserSuperAdmin() {
|
|||||||
bool autoGenerateReference = true;
|
bool autoGenerateReference = true;
|
||||||
bool showAddNewPoint = false;
|
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
|
// Fonction pour mettre à jour le QR preview
|
||||||
void updateQrPreview() {
|
void updateQrPreview() {
|
||||||
if (nameController.text.isNotEmpty) {
|
if (nameController.text.isNotEmpty) {
|
||||||
@ -1253,25 +1742,32 @@ bool _isUserSuperAdmin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Assigner le point de vente de l'utilisateur au produit
|
// Assigner le point de vente de l'utilisateur au produit
|
||||||
final updatedProduct = Product(
|
// final updatedProduct = Product(
|
||||||
id: foundProduct.id,
|
// id: foundProduct.id,
|
||||||
name: foundProduct.name,
|
// name: foundProduct.name,
|
||||||
price: foundProduct.price,
|
// price: foundProduct.price,
|
||||||
image: foundProduct.image,
|
// image: foundProduct.image,
|
||||||
category: foundProduct.category,
|
// category: foundProduct.category,
|
||||||
description: foundProduct.description,
|
// description: foundProduct.description,
|
||||||
stock: foundProduct.stock,
|
// stock: foundProduct.stock,
|
||||||
qrCode: foundProduct.qrCode,
|
// qrCode: foundProduct.qrCode,
|
||||||
reference: foundProduct.reference,
|
// reference: foundProduct.reference,
|
||||||
marque: foundProduct.marque,
|
// marque: foundProduct.marque,
|
||||||
ram: foundProduct.ram,
|
// ram: foundProduct.ram,
|
||||||
memoireInterne: foundProduct.memoireInterne,
|
// memoireInterne: foundProduct.memoireInterne,
|
||||||
imei: foundProduct.imei,
|
// imei: foundProduct.imei,
|
||||||
pointDeVenteId:
|
// pointDeVenteId:
|
||||||
_userController.pointDeVenteId, // Nouveau point de vente
|
// _userController.pointDeVenteId, // Nouveau point de vente
|
||||||
);
|
// );
|
||||||
|
await _appDatabase.createDemandeTransfert(
|
||||||
await _productDatabase.updateProduct(updatedProduct);
|
produitId: foundProduct.id!,
|
||||||
|
pointDeVenteSourceId: _userController.pointDeVenteId,
|
||||||
|
pointDeVenteDestinationId: _userController.pointDeVenteId,
|
||||||
|
demandeurId: _userController.userId,
|
||||||
|
quantite: foundProduct.stock,
|
||||||
|
notes: 'produit non assigner',
|
||||||
|
);
|
||||||
|
// await _productDatabase.updateProduct(updatedProduct);
|
||||||
|
|
||||||
// Recharger les produits pour refléter les changements
|
// Recharger les produits pour refléter les changements
|
||||||
_loadProducts();
|
_loadProducts();
|
||||||
@ -1311,7 +1807,7 @@ bool _isUserSuperAdmin() {
|
|||||||
child: Icon(Icons.check_circle, color: Colors.green.shade700),
|
child: Icon(Icons.check_circle, color: Colors.green.shade700),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
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(
|
content: Column(
|
||||||
|
|||||||
@ -251,6 +251,7 @@ Future<pw.Widget> buildIconGift() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bon de livraison==============================================
|
// Bon de livraison==============================================
|
||||||
|
|
||||||
Future<void> _generateBonLivraison(Commande commande) async {
|
Future<void> _generateBonLivraison(Commande commande) async {
|
||||||
final details = await _database.getDetailsCommande(commande.id!);
|
final details = await _database.getDetailsCommande(commande.id!);
|
||||||
final client = await _database.getClientById(commande.clientId);
|
final client = await _database.getClientById(commande.clientId);
|
||||||
@ -297,23 +298,25 @@ Future<void> _generateBonLivraison(Commande commande) async {
|
|||||||
final image = pw.MemoryImage(imageBytes);
|
final image = pw.MemoryImage(imageBytes);
|
||||||
final italicFont = pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Italic.ttf'));
|
final italicFont = pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Italic.ttf'));
|
||||||
|
|
||||||
// Tailles de texte adaptées pour côte à côte
|
// Tailles de texte agrandies pour une meilleure lisibilité
|
||||||
final tinyTextStyle = pw.TextStyle(fontSize: 5);
|
final tinyTextStyle = pw.TextStyle(fontSize: 9);
|
||||||
final smallTextStyle = pw.TextStyle(fontSize: 6);
|
final smallTextStyle = pw.TextStyle(fontSize: 10);
|
||||||
final normalTextStyle = pw.TextStyle(fontSize: 7);
|
final normalTextStyle = pw.TextStyle(fontSize: 11);
|
||||||
final boldTextStyle = pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold);
|
final boldTextStyle = pw.TextStyle(fontSize: 11, fontWeight: pw.FontWeight.bold);
|
||||||
final boldClientStyle = pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold);
|
final boldClientStyle = pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold);
|
||||||
final frameTextStyle = pw.TextStyle(fontSize: 6);
|
final frameTextStyle = pw.TextStyle(fontSize: 10);
|
||||||
final italicTextStyle = pw.TextStyle(fontSize: 5, fontWeight: pw.FontWeight.bold, font: italicFont);
|
final italicTextStyle = pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold, font: italicFont);
|
||||||
final italicLogoStyle = pw.TextStyle(fontSize: 4, fontWeight: pw.FontWeight.bold, font: italicFont);
|
final italicLogoStyle = pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold, font: italicFont);
|
||||||
final titleStyle = pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold);
|
final titleStyle = pw.TextStyle(fontSize: 14, fontWeight: pw.FontWeight.bold);
|
||||||
|
final headerStyle = pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold);
|
||||||
|
|
||||||
// Fonction pour créer un exemplaire
|
// Fonction pour créer un exemplaire en mode paysage
|
||||||
pw.Widget buildExemplaire(String typeExemplaire, {bool isSecond = false}) {
|
pw.Widget buildExemplaire(String typeExemplaire) {
|
||||||
return pw.Container(
|
return pw.Container(
|
||||||
|
height: 380, // Hauteur ajustée pour le mode paysage
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
decoration: pw.BoxDecoration(
|
decoration: pw.BoxDecoration(
|
||||||
border: pw.Border.all(color: PdfColors.black, width: 1),
|
border: pw.Border.all(color: PdfColors.black, width: 1.5),
|
||||||
),
|
),
|
||||||
child: pw.Column(
|
child: pw.Column(
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
@ -321,7 +324,7 @@ Future<void> _generateBonLivraison(Commande commande) async {
|
|||||||
// En-tête avec indication de l'exemplaire
|
// En-tête avec indication de l'exemplaire
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const pw.EdgeInsets.all(3),
|
padding: const pw.EdgeInsets.all(5),
|
||||||
decoration: pw.BoxDecoration(
|
decoration: pw.BoxDecoration(
|
||||||
color: typeExemplaire == "CLIENT" ? PdfColors.blue100 : PdfColors.green100,
|
color: typeExemplaire == "CLIENT" ? PdfColors.blue100 : PdfColors.green100,
|
||||||
),
|
),
|
||||||
@ -329,7 +332,7 @@ Future<void> _generateBonLivraison(Commande commande) async {
|
|||||||
child: pw.Text(
|
child: pw.Text(
|
||||||
'BON DE LIVRAISON - EXEMPLAIRE $typeExemplaire',
|
'BON DE LIVRAISON - EXEMPLAIRE $typeExemplaire',
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
fontSize: 8,
|
fontSize: 14,
|
||||||
fontWeight: pw.FontWeight.bold,
|
fontWeight: pw.FontWeight.bold,
|
||||||
color: typeExemplaire == "CLIENT" ? PdfColors.blue800 : PdfColors.green800,
|
color: typeExemplaire == "CLIENT" ? PdfColors.blue800 : PdfColors.green800,
|
||||||
),
|
),
|
||||||
@ -337,383 +340,395 @@ Future<void> _generateBonLivraison(Commande commande) async {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
pw.Padding(
|
pw.Expanded(
|
||||||
padding: const pw.EdgeInsets.all(6),
|
child: pw.Padding(
|
||||||
child: pw.Column(
|
padding: const pw.EdgeInsets.all(8),
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
child: pw.Column(
|
||||||
children: [
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
// En-tête principal
|
children: [
|
||||||
pw.Row(
|
// En-tête principal
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
pw.Row(
|
||||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
children: [
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||||
// Logo et infos entreprise - très compact
|
children: [
|
||||||
pw.Column(
|
// Logo et infos entreprise
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
pw.Column(
|
||||||
children: [
|
|
||||||
pw.Container(
|
|
||||||
width: 45,
|
|
||||||
height: 45,
|
|
||||||
child: pw.Image(image),
|
|
||||||
),
|
|
||||||
pw.Text('NOTRE COMPETENCE, A VOTRE SERVICE', style: italicLogoStyle),
|
|
||||||
pw.SizedBox(height: 3),
|
|
||||||
pw.Column(
|
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
pw.Text('📍 REMAX Andravoangy', style: tinyTextStyle),
|
|
||||||
pw.Text('📍 SUPREME CENTER Behoririka', style: tinyTextStyle),
|
|
||||||
pw.Text('📞 033 37 808 18', style: tinyTextStyle),
|
|
||||||
pw.Text('🌐 www.guycom.mg', style: tinyTextStyle),
|
|
||||||
pw.SizedBox(height: 1),
|
|
||||||
// Ajout du NIF
|
|
||||||
pw.Text('NIF: 1026/GC78-20-02-22', style: pw.TextStyle(fontSize: 5, fontWeight: pw.FontWeight.bold)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
// Informations centrales
|
|
||||||
pw.Column(
|
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
pw.Text('Date: ${DateFormat('dd/MM/yyyy').format(DateTime.now())}', style: boldClientStyle),
|
|
||||||
pw.SizedBox(height: 3),
|
|
||||||
pw.Container(width: 80, height: 1, color: PdfColors.black),
|
|
||||||
pw.SizedBox(height: 3),
|
|
||||||
pw.Container(
|
|
||||||
padding: const pw.EdgeInsets.all(3),
|
|
||||||
decoration: pw.BoxDecoration(
|
|
||||||
border: pw.Border.all(color: PdfColors.black),
|
|
||||||
),
|
|
||||||
child: pw.Column(
|
|
||||||
children: [
|
|
||||||
pw.Text('Boutique:', style: frameTextStyle),
|
|
||||||
pw.Text('${pointDeVente?['nom'] ?? 'S405A'}', style: boldTextStyle),
|
|
||||||
pw.SizedBox(height: 1),
|
|
||||||
pw.Text('Bon N°:', style: frameTextStyle),
|
|
||||||
pw.Text('${pointDeVente?['nom'] ?? 'S405A'}-P${commande.id}', style: boldTextStyle),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
// Informations client - compact
|
|
||||||
pw.Container(
|
|
||||||
width: 100,
|
|
||||||
decoration: pw.BoxDecoration(
|
|
||||||
border: pw.Border.all(color: PdfColors.black, width: 1),
|
|
||||||
),
|
|
||||||
padding: const pw.EdgeInsets.all(4),
|
|
||||||
child: pw.Column(
|
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
pw.Text('CLIENT', style: frameTextStyle),
|
|
||||||
pw.SizedBox(height: 1),
|
|
||||||
pw.Text('ID: ${pointDeVente?['nom'] ?? 'S405A'}-${client?.id ?? 'Non spécifié'}', style: smallTextStyle),
|
|
||||||
pw.Container(width: 80, height: 1, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 1)),
|
|
||||||
pw.Text(client?.nom ?? 'Non spécifié', style: boldTextStyle),
|
|
||||||
pw.SizedBox(height: 1),
|
|
||||||
pw.Text(client?.telephone ?? 'Non spécifié', style: tinyTextStyle),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
pw.SizedBox(height: 4),
|
|
||||||
|
|
||||||
// Tableau des produits - très compact
|
|
||||||
pw.Table(
|
|
||||||
border: pw.TableBorder.all(width: 0.5),
|
|
||||||
columnWidths: {
|
|
||||||
0: const pw.FlexColumnWidth(3.5),
|
|
||||||
1: const pw.FlexColumnWidth(0.8),
|
|
||||||
2: const pw.FlexColumnWidth(1.2),
|
|
||||||
3: const pw.FlexColumnWidth(1.5),
|
|
||||||
4: const pw.FlexColumnWidth(1.2),
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
pw.TableRow(
|
|
||||||
decoration: const pw.BoxDecoration(color: PdfColors.grey200),
|
|
||||||
children: [
|
|
||||||
pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('Désignations', style: boldTextStyle)),
|
|
||||||
pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('Qté', style: boldTextStyle, textAlign: pw.TextAlign.center)),
|
|
||||||
pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('P.U.', style: boldTextStyle, textAlign: pw.TextAlign.right)),
|
|
||||||
pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('Remise/Cadeau', style: boldTextStyle, textAlign: pw.TextAlign.center)),
|
|
||||||
pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('Montant', style: boldTextStyle, textAlign: pw.TextAlign.right)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
...detailsAvecProduits.map((item) {
|
|
||||||
final detail = item['detail'] as DetailCommande;
|
|
||||||
final produit = item['produit'];
|
|
||||||
|
|
||||||
return pw.TableRow(
|
|
||||||
decoration: detail.estCadeau
|
|
||||||
? const pw.BoxDecoration(color: PdfColors.green50)
|
|
||||||
: detail.aRemise
|
|
||||||
? const pw.BoxDecoration(color: PdfColors.orange50)
|
|
||||||
: null,
|
|
||||||
children: [
|
|
||||||
pw.Padding(
|
|
||||||
padding: const pw.EdgeInsets.all(1),
|
|
||||||
child: pw.Column(
|
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
pw.Row(
|
|
||||||
children: [
|
|
||||||
pw.Expanded(
|
|
||||||
child: pw.Text(detail.produitNom ?? 'Produit inconnu',
|
|
||||||
style: pw.TextStyle(fontSize: 6, fontWeight: pw.FontWeight.bold)),
|
|
||||||
),
|
|
||||||
if (detail.estCadeau)
|
|
||||||
pw.Container(
|
|
||||||
padding: const pw.EdgeInsets.symmetric(horizontal: 1, vertical: 0.5),
|
|
||||||
decoration: pw.BoxDecoration(
|
|
||||||
color: PdfColors.green,
|
|
||||||
borderRadius: pw.BorderRadius.circular(2),
|
|
||||||
),
|
|
||||||
child: pw.Text('🎁', style: pw.TextStyle(fontSize: 4, color: PdfColors.white)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (produit?.category != null && produit!.category.isNotEmpty)
|
|
||||||
pw.Text('${produit.category}${produit?.marque != null && produit!.marque.isNotEmpty ? ' - ${produit.marque}' : ''}', style: tinyTextStyle),
|
|
||||||
if (produit?.imei != null && produit!.imei!.isNotEmpty)
|
|
||||||
pw.Text('IMEI: ${produit.imei}', style: tinyTextStyle),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
pw.Padding(
|
|
||||||
padding: const pw.EdgeInsets.all(1),
|
|
||||||
child: pw.Text('${detail.quantite}', style: normalTextStyle, textAlign: pw.TextAlign.center),
|
|
||||||
),
|
|
||||||
pw.Padding(
|
|
||||||
padding: const pw.EdgeInsets.all(1),
|
|
||||||
child: pw.Column(
|
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
if (detail.estCadeau) ...[
|
|
||||||
pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}',
|
|
||||||
style: pw.TextStyle(fontSize: 4, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)),
|
|
||||||
pw.Text('GRATUIT', style: pw.TextStyle(fontSize: 5, color: PdfColors.green700, fontWeight: pw.FontWeight.bold)),
|
|
||||||
] else if (detail.aRemise) ...[
|
|
||||||
pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}',
|
|
||||||
style: pw.TextStyle(fontSize: 4, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)),
|
|
||||||
pw.Text('${(detail.prixFinal / detail.quantite).toStringAsFixed(0)}',
|
|
||||||
style: pw.TextStyle(fontSize: 6, color: PdfColors.orange)),
|
|
||||||
] else
|
|
||||||
pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}', style: smallTextStyle),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
pw.Padding(
|
|
||||||
padding: const pw.EdgeInsets.all(1),
|
|
||||||
child: pw.Text(
|
|
||||||
detail.estCadeau
|
|
||||||
? 'CADEAU'
|
|
||||||
: detail.aRemise
|
|
||||||
? 'REMISE'
|
|
||||||
: '-',
|
|
||||||
style: pw.TextStyle(
|
|
||||||
fontSize: 5,
|
|
||||||
color: detail.estCadeau ? PdfColors.green700 : detail.aRemise ? PdfColors.orange : PdfColors.grey600,
|
|
||||||
fontWeight: detail.estCadeau ? pw.FontWeight.bold : pw.FontWeight.normal,
|
|
||||||
),
|
|
||||||
textAlign: pw.TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
pw.Padding(
|
|
||||||
padding: const pw.EdgeInsets.all(1),
|
|
||||||
child: pw.Column(
|
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
if (detail.estCadeau) ...[
|
|
||||||
pw.Text('${detail.sousTotal.toStringAsFixed(0)}',
|
|
||||||
style: pw.TextStyle(fontSize: 4, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)),
|
|
||||||
pw.Text('GRATUIT', style: pw.TextStyle(fontSize: 5, fontWeight: pw.FontWeight.bold, color: PdfColors.green700)),
|
|
||||||
] else if (detail.aRemise) ...[
|
|
||||||
pw.Text('${detail.sousTotal.toStringAsFixed(0)}',
|
|
||||||
style: pw.TextStyle(fontSize: 4, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)),
|
|
||||||
pw.Text('${detail.prixFinal.toStringAsFixed(0)}', style: pw.TextStyle(fontSize: 6, fontWeight: pw.FontWeight.bold)),
|
|
||||||
] else
|
|
||||||
pw.Text('${detail.prixFinal.toStringAsFixed(0)}', style: smallTextStyle),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
pw.SizedBox(height: 4),
|
|
||||||
|
|
||||||
// Section finale - très compacte
|
|
||||||
pw.Row(
|
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Totaux
|
|
||||||
pw.Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: pw.Column(
|
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
if (totalRemises > 0 || totalCadeaux > 0) ...[
|
|
||||||
pw.Row(
|
|
||||||
mainAxisAlignment: pw.MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
pw.Text('SOUS-TOTAL:', style: smallTextStyle),
|
|
||||||
pw.SizedBox(width: 8),
|
|
||||||
pw.Text('${sousTotal.toStringAsFixed(0)}', style: smallTextStyle),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
pw.SizedBox(height: 1),
|
|
||||||
],
|
|
||||||
|
|
||||||
if (totalRemises > 0) ...[
|
|
||||||
pw.Row(
|
|
||||||
mainAxisAlignment: pw.MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
pw.Text('REMISES:', style: pw.TextStyle(color: PdfColors.orange, fontSize: 6)),
|
|
||||||
pw.SizedBox(width: 8),
|
|
||||||
pw.Text('-${totalRemises.toStringAsFixed(0)}', style: pw.TextStyle(color: PdfColors.orange, fontSize: 6)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
pw.SizedBox(height: 1),
|
|
||||||
],
|
|
||||||
|
|
||||||
if (totalCadeaux > 0) ...[
|
|
||||||
pw.Row(
|
|
||||||
mainAxisAlignment: pw.MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
pw.Text('CADEAUX ($nombreCadeaux):', style: pw.TextStyle(color: PdfColors.green700, fontSize: 6)),
|
|
||||||
pw.SizedBox(width: 8),
|
|
||||||
pw.Text('-${totalCadeaux.toStringAsFixed(0)}', style: pw.TextStyle(color: PdfColors.green700, fontSize: 6)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
pw.SizedBox(height: 1),
|
|
||||||
],
|
|
||||||
|
|
||||||
pw.Container(width: 100, height: 1, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 1)),
|
|
||||||
|
|
||||||
pw.Row(
|
|
||||||
mainAxisAlignment: pw.MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
pw.Text('TOTAL:', style: boldTextStyle),
|
|
||||||
pw.SizedBox(width: 8),
|
|
||||||
pw.Text('${commande.montantTotal.toStringAsFixed(0)} MGA', style: boldTextStyle),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
if (totalCadeaux > 0) ...[
|
|
||||||
pw.SizedBox(height: 3),
|
|
||||||
pw.Container(
|
|
||||||
padding: const pw.EdgeInsets.all(3),
|
|
||||||
decoration: pw.BoxDecoration(
|
|
||||||
color: PdfColors.green50,
|
|
||||||
borderRadius: pw.BorderRadius.circular(3),
|
|
||||||
),
|
|
||||||
child: pw.Text(
|
|
||||||
'🎁 $nombreCadeaux cadeau(s) offert(s) (${totalCadeaux.toStringAsFixed(0)} MGA)',
|
|
||||||
style: pw.TextStyle(fontSize: 5, color: PdfColors.green700),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
pw.SizedBox(width: 10),
|
|
||||||
|
|
||||||
// Informations vendeurs et signatures
|
|
||||||
pw.Expanded(
|
|
||||||
flex: 3,
|
|
||||||
child: pw.Column(
|
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Vendeurs
|
|
||||||
pw.Container(
|
pw.Container(
|
||||||
padding: const pw.EdgeInsets.all(3),
|
width: 100,
|
||||||
decoration: pw.BoxDecoration(
|
height: 100,
|
||||||
color: PdfColors.grey100,
|
child: pw.Image(image),
|
||||||
borderRadius: pw.BorderRadius.circular(3),
|
|
||||||
),
|
|
||||||
child: pw.Column(
|
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
pw.Text('VENDEURS', style: pw.TextStyle(fontSize: 6, fontWeight: pw.FontWeight.bold)),
|
|
||||||
pw.SizedBox(height: 1),
|
|
||||||
pw.Row(
|
|
||||||
children: [
|
|
||||||
pw.Expanded(
|
|
||||||
child: pw.Column(
|
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
pw.Text('Initiateur:', style: tinyTextStyle),
|
|
||||||
pw.Text(
|
|
||||||
commandeur != null ? '${commandeur.name} ${commandeur.lastName ?? ''}'.trim() : 'N/A',
|
|
||||||
style: pw.TextStyle(fontSize: 5),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
pw.Expanded(
|
|
||||||
child: pw.Column(
|
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
pw.Text('Validateur:', style: tinyTextStyle),
|
|
||||||
pw.Text(
|
|
||||||
validateur != null ? '${validateur.name} ${validateur.lastName ?? ''}'.trim() : 'N/A',
|
|
||||||
style: pw.TextStyle(fontSize: 5),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
pw.SizedBox(height: 3),
|
||||||
pw.SizedBox(height: 6),
|
pw.Text('NOTRE COMPETENCE, A VOTRE SERVICE', style: italicLogoStyle),
|
||||||
|
pw.SizedBox(height: 4),
|
||||||
// Signatures
|
pw.Column(
|
||||||
pw.Row(
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
children: [
|
||||||
pw.Column(
|
pw.Text('📍 REMAX Andravoangy', style: tinyTextStyle),
|
||||||
children: [
|
pw.Text('📍 SUPREME CENTER Behoririka \n BOX 405 | 416 | 119', style: tinyTextStyle),
|
||||||
pw.Text('Vendeur', style: pw.TextStyle(fontSize: 5, fontWeight: pw.FontWeight.bold)),
|
pw.Text('📍 Tripolisa analankely BOX 7', style: tinyTextStyle),
|
||||||
pw.SizedBox(height: 8),
|
pw.Text('📞 033 37 808 18', style: tinyTextStyle),
|
||||||
pw.Container(width: 50, height: 1, color: PdfColors.black),
|
pw.Text('🌐 www.guycom.mg', style: tinyTextStyle),
|
||||||
],
|
pw.SizedBox(height: 2),
|
||||||
),
|
pw.Text('NIF: 1026/GC78-20-02-22',
|
||||||
pw.Column(
|
style: pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold)),
|
||||||
children: [
|
|
||||||
pw.Text('Client', style: pw.TextStyle(fontSize: 5, fontWeight: pw.FontWeight.bold)),
|
|
||||||
pw.SizedBox(height: 8),
|
|
||||||
pw.Container(width: 50, height: 1, color: PdfColors.black),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Informations centrales
|
||||||
|
pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
pw.Text('Date: ${DateFormat('dd/MM/yyyy').format(DateTime.now())}', style: boldClientStyle),
|
||||||
|
pw.SizedBox(height: 4),
|
||||||
|
pw.Container(width: 100, height: 2, color: PdfColors.black),
|
||||||
|
pw.SizedBox(height: 4),
|
||||||
|
pw.Container(
|
||||||
|
padding: const pw.EdgeInsets.all(6),
|
||||||
|
decoration: pw.BoxDecoration(
|
||||||
|
border: pw.Border.all(color: PdfColors.black),
|
||||||
|
),
|
||||||
|
child: pw.Column(
|
||||||
|
children: [
|
||||||
|
pw.Text('Boutique:', style: frameTextStyle),
|
||||||
|
pw.Text('${pointDeVente?['nom'] ?? 'S405A'}', style: boldTextStyle),
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
pw.Text('Bon N°:', style: frameTextStyle),
|
||||||
|
pw.Text('${pointDeVente?['nom'] ?? 'S405A'}-P${commande.id}', style: boldTextStyle),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Informations client
|
||||||
|
pw.Container(
|
||||||
|
width: 120,
|
||||||
|
decoration: pw.BoxDecoration(
|
||||||
|
border: pw.Border.all(color: PdfColors.black, width: 1),
|
||||||
|
),
|
||||||
|
padding: const pw.EdgeInsets.all(6),
|
||||||
|
child: pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
pw.Text('CLIENT', style: frameTextStyle),
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
pw.Text('ID: ${pointDeVente?['nom'] ?? 'S405A'}-${client?.id ?? 'Non spécifié'}', style: smallTextStyle),
|
||||||
|
pw.Container(width: 100, height: 1, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 2)),
|
||||||
|
pw.Text('${client?.nom} \n ${client?.prenom}', style: boldTextStyle),
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
pw.Text(client?.telephone ?? 'Non spécifié', style: tinyTextStyle),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Tableau des produits (ajusté pour le mode paysage)
|
||||||
|
pw.Expanded(
|
||||||
|
child: pw.Table(
|
||||||
|
border: pw.TableBorder.all(width: 1),
|
||||||
|
columnWidths: {
|
||||||
|
0: const pw.FlexColumnWidth(5),
|
||||||
|
1: const pw.FlexColumnWidth(1.2),
|
||||||
|
2: const pw.FlexColumnWidth(1.5),
|
||||||
|
3: const pw.FlexColumnWidth(1.5),
|
||||||
|
4: const pw.FlexColumnWidth(1.5),
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
pw.TableRow(
|
||||||
|
decoration: const pw.BoxDecoration(color: PdfColors.grey200),
|
||||||
|
children: [
|
||||||
|
pw.Padding(padding: const pw.EdgeInsets.all(3),
|
||||||
|
child: pw.Text('Désignations', style: boldTextStyle)),
|
||||||
|
pw.Padding(padding: const pw.EdgeInsets.all(3),
|
||||||
|
child: pw.Text('Qté', style: boldTextStyle, textAlign: pw.TextAlign.center)),
|
||||||
|
pw.Padding(padding: const pw.EdgeInsets.all(3),
|
||||||
|
child: pw.Text('P.U.', style: boldTextStyle, textAlign: pw.TextAlign.right)),
|
||||||
|
// pw.Padding(padding: const pw.EdgeInsets.all(3),
|
||||||
|
// child: pw.Text('Remise/Cadeau', style: boldTextStyle, textAlign: pw.TextAlign.center)),
|
||||||
|
pw.Padding(padding: const pw.EdgeInsets.all(3),
|
||||||
|
child: pw.Text('Montant', style: boldTextStyle, textAlign: pw.TextAlign.right)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
...detailsAvecProduits.map((item) {
|
||||||
|
final detail = item['detail'] as DetailCommande;
|
||||||
|
final produit = item['produit'];
|
||||||
|
|
||||||
|
return pw.TableRow(
|
||||||
|
decoration: detail.estCadeau
|
||||||
|
? const pw.BoxDecoration(color: PdfColors.green50)
|
||||||
|
: detail.aRemise
|
||||||
|
? const pw.BoxDecoration(color: PdfColors.orange50)
|
||||||
|
: null,
|
||||||
|
children: [
|
||||||
|
pw.Padding(
|
||||||
|
padding: const pw.EdgeInsets.all(3),
|
||||||
|
child: pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
pw.Row(
|
||||||
|
children: [
|
||||||
|
pw.Expanded(
|
||||||
|
child: pw.Text(detail.produitNom ?? 'Produit inconnu',
|
||||||
|
style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold)),
|
||||||
|
),
|
||||||
|
if (detail.estCadeau)
|
||||||
|
pw.Container(
|
||||||
|
padding: const pw.EdgeInsets.symmetric(horizontal: 2, vertical: 1),
|
||||||
|
decoration: pw.BoxDecoration(
|
||||||
|
color: PdfColors.green,
|
||||||
|
borderRadius: pw.BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
child: pw.Text('🎁', style: pw.TextStyle(fontSize: 5, color: PdfColors.white)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (produit?.category != null && produit!.category.isNotEmpty)
|
||||||
|
pw.Text('${produit.category}${produit?.marque != null && produit!.marque.isNotEmpty ? ' - ${produit.marque}' : ''}',
|
||||||
|
style: tinyTextStyle),
|
||||||
|
if (produit?.imei != null && produit!.imei!.isNotEmpty)
|
||||||
|
pw.Text('IMEI: ${produit.imei}', style: tinyTextStyle),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pw.Padding(
|
||||||
|
padding: const pw.EdgeInsets.all(3),
|
||||||
|
child: pw.Text('${detail.quantite}', style: normalTextStyle, textAlign: pw.TextAlign.center),
|
||||||
|
),
|
||||||
|
pw.Padding(
|
||||||
|
padding: const pw.EdgeInsets.all(3),
|
||||||
|
child: pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
if (detail.estCadeau) ...[
|
||||||
|
pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}',
|
||||||
|
style: pw.TextStyle(fontSize: 8, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)),
|
||||||
|
pw.Text('GRATUIT', style: pw.TextStyle(fontSize: 9, color: PdfColors.green700, fontWeight: pw.FontWeight.bold)),
|
||||||
|
] else if (detail.aRemise) ...[
|
||||||
|
pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}',
|
||||||
|
style: pw.TextStyle(fontSize: 8, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)),
|
||||||
|
pw.Text('${(detail.prixFinal / detail.quantite).toStringAsFixed(0)}',
|
||||||
|
style: pw.TextStyle(fontSize: 9, color: PdfColors.orange)),
|
||||||
|
] else
|
||||||
|
pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}', style: smallTextStyle),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// pw.Padding(
|
||||||
|
// padding: const pw.EdgeInsets.all(3),
|
||||||
|
// child: pw.Text(
|
||||||
|
// detail.estCadeau
|
||||||
|
// ? 'CADEAU'
|
||||||
|
// : detail.aRemise
|
||||||
|
// ? 'REMISE'
|
||||||
|
// : '-',
|
||||||
|
// style: pw.TextStyle(
|
||||||
|
// fontSize: 9,
|
||||||
|
// color: detail.estCadeau ? PdfColors.green700 : detail.aRemise ? PdfColors.orange : PdfColors.grey600,
|
||||||
|
// fontWeight: detail.estCadeau ? pw.FontWeight.bold : pw.FontWeight.normal,
|
||||||
|
// ),
|
||||||
|
// textAlign: pw.TextAlign.center,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
pw.Padding(
|
||||||
|
padding: const pw.EdgeInsets.all(3),
|
||||||
|
child: pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
if (detail.estCadeau) ...[
|
||||||
|
pw.Text('${detail.sousTotal.toStringAsFixed(0)}',
|
||||||
|
style: pw.TextStyle(fontSize: 8, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)),
|
||||||
|
pw.Text('GRATUIT', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold, color: PdfColors.green700)),
|
||||||
|
] else if (detail.aRemise) ...[
|
||||||
|
pw.Text('${detail.sousTotal.toStringAsFixed(0)}',
|
||||||
|
style: pw.TextStyle(fontSize: 8, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)),
|
||||||
|
pw.Text('${detail.prixFinal.toStringAsFixed(0)}', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold)),
|
||||||
|
] else
|
||||||
|
pw.Text('${detail.prixFinal.toStringAsFixed(0)}', style: smallTextStyle),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
|
||||||
|
|
||||||
pw.SizedBox(height: 3),
|
pw.SizedBox(height: 8),
|
||||||
|
|
||||||
// Note finale
|
// Section finale (ajustée pour le mode paysage)
|
||||||
pw.Text(
|
pw.Row(
|
||||||
'Arrêté à la somme de: ${_numberToWords(commande.montantTotal.toInt())} Ariary',
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
style: italicTextStyle,
|
children: [
|
||||||
),
|
// Totaux
|
||||||
],
|
pw.Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
if (totalRemises > 0 || totalCadeaux > 0) ...[
|
||||||
|
pw.Row(
|
||||||
|
mainAxisAlignment: pw.MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
pw.Text('SOUS-TOTAL:', style: smallTextStyle),
|
||||||
|
pw.SizedBox(width: 10),
|
||||||
|
pw.Text('${sousTotal.toStringAsFixed(0)}', style: smallTextStyle),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
],
|
||||||
|
|
||||||
|
if (totalRemises > 0) ...[
|
||||||
|
pw.Row(
|
||||||
|
mainAxisAlignment: pw.MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
pw.Text('REMISES:', style: pw.TextStyle(color: PdfColors.orange, fontSize: 10)),
|
||||||
|
pw.SizedBox(width: 10),
|
||||||
|
pw.Text('-${totalRemises.toStringAsFixed(0)}', style: pw.TextStyle(color: PdfColors.orange, fontSize: 10)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
],
|
||||||
|
|
||||||
|
if (totalCadeaux > 0) ...[
|
||||||
|
pw.Row(
|
||||||
|
mainAxisAlignment: pw.MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
pw.Text('CADEAUX ($nombreCadeaux):', style: pw.TextStyle(color: PdfColors.green700, fontSize: 10)),
|
||||||
|
pw.SizedBox(width: 10),
|
||||||
|
pw.Text('-${totalCadeaux.toStringAsFixed(0)}', style: pw.TextStyle(color: PdfColors.green700, fontSize: 10)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
],
|
||||||
|
|
||||||
|
pw.Container(width: 120, height: 1.5, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 2)),
|
||||||
|
|
||||||
|
pw.Row(
|
||||||
|
mainAxisAlignment: pw.MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
pw.Text('TOTAL:', style: boldTextStyle),
|
||||||
|
pw.SizedBox(width: 10),
|
||||||
|
pw.Text('${commande.montantTotal.toStringAsFixed(0)} MGA', style: boldTextStyle),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
if (totalCadeaux > 0) ...[
|
||||||
|
pw.SizedBox(height: 3),
|
||||||
|
pw.Container(
|
||||||
|
padding: const pw.EdgeInsets.all(3),
|
||||||
|
decoration: pw.BoxDecoration(
|
||||||
|
color: PdfColors.green50,
|
||||||
|
borderRadius: pw.BorderRadius.circular(3),
|
||||||
|
),
|
||||||
|
child: pw.Text(
|
||||||
|
'🎁 $nombreCadeaux cadeau(s) offert(s) (${totalCadeaux.toStringAsFixed(0)} MGA)',
|
||||||
|
style: pw.TextStyle(fontSize: 9, color: PdfColors.green700),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(width: 15),
|
||||||
|
|
||||||
|
// Informations vendeurs et signatures
|
||||||
|
pw.Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Vendeurs
|
||||||
|
pw.Container(
|
||||||
|
padding: const pw.EdgeInsets.all(4),
|
||||||
|
decoration: pw.BoxDecoration(
|
||||||
|
color: PdfColors.grey100,
|
||||||
|
borderRadius: pw.BorderRadius.circular(3),
|
||||||
|
),
|
||||||
|
child: pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
pw.Text('VENDEURS', style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold)),
|
||||||
|
pw.SizedBox(height: 3),
|
||||||
|
pw.Row(
|
||||||
|
children: [
|
||||||
|
pw.Expanded(
|
||||||
|
child: pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
pw.Text('Initiateur:', style: tinyTextStyle),
|
||||||
|
pw.Text(
|
||||||
|
commandeur != null ? '${commandeur.name} ${commandeur.lastName ?? ''}'.trim() : 'N/A',
|
||||||
|
style: pw.TextStyle(fontSize: 9),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pw.Expanded(
|
||||||
|
child: pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
pw.Text('Validateur:', style: tinyTextStyle),
|
||||||
|
pw.Text(
|
||||||
|
validateur != null ? '${validateur.name} ${validateur.lastName ?? ''}'.trim() : 'N/A',
|
||||||
|
style: pw.TextStyle(fontSize: 9),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Signatures
|
||||||
|
pw.Row(
|
||||||
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
pw.Column(
|
||||||
|
children: [
|
||||||
|
pw.Text('Vendeur', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold)),
|
||||||
|
pw.SizedBox(height: 15),
|
||||||
|
pw.Container(width: 70, height: 1, color: PdfColors.black),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
pw.Column(
|
||||||
|
children: [
|
||||||
|
pw.Text('Client', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold)),
|
||||||
|
pw.SizedBox(height: 15),
|
||||||
|
pw.Container(width: 70, height: 1, color: PdfColors.black),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 4),
|
||||||
|
|
||||||
|
// Note finale
|
||||||
|
pw.Text(
|
||||||
|
'Arrêté à la somme de: ${_numberToWords(commande.montantTotal.toInt())} Ariary',
|
||||||
|
style: italicTextStyle,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -721,42 +736,45 @@ Future<void> _generateBonLivraison(Commande commande) async {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PAGE EN MODE PAYSAGE : Les deux exemplaires sur une seule page
|
||||||
pdf.addPage(
|
pdf.addPage(
|
||||||
pw.Page(
|
pw.Page(
|
||||||
pageFormat: PdfPageFormat.a4.landscape,
|
pageFormat: PdfPageFormat.a4.landscape, // Mode paysage
|
||||||
margin: const pw.EdgeInsets.all(10),
|
margin: const pw.EdgeInsets.all(12),
|
||||||
build: (pw.Context context) {
|
build: (pw.Context context) {
|
||||||
return pw.Row(
|
return pw.Row( // Utilisation de Row au lieu de Column pour placer côte à côte
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
// Exemplaire CLIENT (à gauche)
|
// Premier exemplaire (CLIENT)
|
||||||
pw.Expanded(
|
pw.Expanded(
|
||||||
child: buildExemplaire("CLIENT"),
|
child: buildExemplaire("CLIENT"),
|
||||||
),
|
),
|
||||||
|
|
||||||
pw.SizedBox(width: 10),
|
pw.SizedBox(width: 15),
|
||||||
|
|
||||||
// Ligne de séparation verticale avec ciseaux
|
// Trait de séparation vertical
|
||||||
pw.Column(
|
pw.Container(
|
||||||
mainAxisAlignment: pw.MainAxisAlignment.center,
|
width: 2,
|
||||||
children: [
|
height: double.infinity,
|
||||||
pw.Transform.rotate(
|
child: pw.Column(
|
||||||
angle: 3.14159 / 2, // 90 degrés en radians
|
mainAxisAlignment: pw.MainAxisAlignment.center,
|
||||||
child: pw.Text('✂️ DÉCOUPER ICI ✂️', style: pw.TextStyle(fontSize: 8, color: PdfColors.grey600)),
|
children: [
|
||||||
),
|
pw.Text('✂️', style: pw.TextStyle(fontSize: 14)),
|
||||||
pw.Container(
|
pw.SizedBox(height: 10),
|
||||||
width: 1,
|
pw.Transform.rotate(
|
||||||
height: 200,
|
angle: 1.5708, // 90 degrés en radians (π/2)
|
||||||
color: PdfColors.grey400,
|
child: pw.Text('DÉCOUPER ICI', style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold)),
|
||||||
),
|
),
|
||||||
],
|
pw.SizedBox(height: 10),
|
||||||
|
pw.Text('✂️', style: pw.TextStyle(fontSize: 14)),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
pw.SizedBox(width: 10),
|
pw.SizedBox(width: 15),
|
||||||
|
|
||||||
// Exemplaire MAGASIN (à droite)
|
// Deuxième exemplaire (MAGASIN)
|
||||||
pw.Expanded(
|
pw.Expanded(
|
||||||
child: buildExemplaire("MAGASIN", isSecond: true),
|
child: buildExemplaire("MAGASIN"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -851,7 +869,7 @@ Future<void> _generateInvoice(Commande commande) async {
|
|||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: 120,
|
width: 200,
|
||||||
height: 120,
|
height: 120,
|
||||||
child: pw.Image(image),
|
child: pw.Image(image),
|
||||||
),
|
),
|
||||||
@ -874,6 +892,7 @@ Future<void> _generateInvoice(Commande commande) async {
|
|||||||
pw.SizedBox(height: 8),
|
pw.SizedBox(height: 8),
|
||||||
pw.Row(children: [iconPhone, pw.SizedBox(width: 4), pw.Text('033 37 808 18', style: smallTextStyle)]),
|
pw.Row(children: [iconPhone, pw.SizedBox(width: 4), pw.Text('033 37 808 18', style: smallTextStyle)]),
|
||||||
pw.Row(children: [iconGlobe, pw.SizedBox(width: 4), pw.Text('www.guycom.mg', style: smallTextStyle)]),
|
pw.Row(children: [iconGlobe, pw.SizedBox(width: 4), pw.Text('www.guycom.mg', style: smallTextStyle)]),
|
||||||
|
pw.Row(children: [iconGlobe, pw.SizedBox(width: 4), pw.Text('NIF: 1026/GC78-20-02-22', style: smallTextStyle)]),
|
||||||
pw.Text('Facebook: GuyCom', style: smallTextStyle),
|
pw.Text('Facebook: GuyCom', style: smallTextStyle),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -946,7 +965,7 @@ Future<void> _generateInvoice(Commande commande) async {
|
|||||||
pw.SizedBox(height: 6),
|
pw.SizedBox(height: 6),
|
||||||
pw.Container(width: 180, height: 1, color: PdfColors.black),
|
pw.Container(width: 180, height: 1, color: PdfColors.black),
|
||||||
pw.SizedBox(height: 4),
|
pw.SizedBox(height: 4),
|
||||||
pw.Text(client?.nom ?? 'Non spécifié', style: boldClientTextStyle),
|
pw.Text('${client?.nom} \n ${client?.prenom}', style: boldTextStyle),
|
||||||
pw.SizedBox(height: 4),
|
pw.SizedBox(height: 4),
|
||||||
pw.Text(client?.telephone ?? 'Non spécifié', style: frameTextStyle),
|
pw.Text(client?.telephone ?? 'Non spécifié', style: frameTextStyle),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -608,99 +608,291 @@ void _modifierQuantite(int productId, int nouvelleQuantite) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showProductFoundAndAddedDialog(Product product, int newQuantity) {
|
void _showProductFoundAndAddedDialog(Product product, int newQuantity) {
|
||||||
Get.dialog(
|
final isProduitCommandable = _isProduitCommandable(product);
|
||||||
AlertDialog(
|
final canRequestTransfer = product.stock != null && product.stock! >= 1;
|
||||||
title: Row(
|
|
||||||
children: [
|
Get.dialog(
|
||||||
Container(
|
Dialog(
|
||||||
padding: const EdgeInsets.all(8),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
decoration: BoxDecoration(
|
child: Container(
|
||||||
color: Colors.green.shade100,
|
padding: const EdgeInsets.all(20),
|
||||||
borderRadius: BorderRadius.circular(8),
|
child: Column(
|
||||||
),
|
|
||||||
child: Icon(Icons.check_circle, color: Colors.green.shade700),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
const Expanded(child: Text('Produit identifié et ajouté !')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
// Header avec icône de succès
|
||||||
product.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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),
|
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.green.shade50,
|
color: Colors.green.shade50,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: Colors.green.shade200),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.auto_awesome,
|
Container(
|
||||||
color: Colors.green.shade700, size: 16),
|
padding: const EdgeInsets.all(8),
|
||||||
const SizedBox(width: 8),
|
decoration: BoxDecoration(
|
||||||
const Expanded(
|
color: Colors.green.shade100,
|
||||||
child: Text(
|
shape: BoxShape.circle,
|
||||||
'Produit identifié automatiquement',
|
),
|
||||||
style: TextStyle(
|
child: Icon(
|
||||||
fontSize: 12,
|
Icons.check_circle,
|
||||||
fontWeight: FontWeight.w500,
|
color: Colors.green.shade700,
|
||||||
),
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
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: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
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.blue.shade700, size: 16),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'Identifié automatiquement',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.blue.shade700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
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.shade600,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.qr_code_scanner, size: 18),
|
||||||
|
label: const Text('Scanner'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue.shade600,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
elevation: 1,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
child: const Text('Continuer'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
_showCartBottomSheet();
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.green.shade700,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
),
|
|
||||||
child: const Text('Voir le panier'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
_startAutomaticScanning(); // Scanner un autre produit
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.blue.shade700,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
),
|
|
||||||
child: const Text('Scanner encore'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _showProductNotFoundDialog(String scannedData) {
|
void _showProductNotFoundDialog(String scannedData) {
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
@ -1899,9 +2091,9 @@ Widget _buildUserPointDeVenteInfo() {
|
|||||||
// 6. Ajoutez cette méthode pour filtrer les produits par point de vente
|
// 6. Ajoutez cette méthode pour filtrer les produits par point de vente
|
||||||
// 🎯 MODIFIÉ: Dropdown avec gestion améliorée
|
// 🎯 MODIFIÉ: Dropdown avec gestion améliorée
|
||||||
Widget _buildPointDeVenteFilter() {
|
Widget _buildPointDeVenteFilter() {
|
||||||
if (!_isUserSuperAdmin()) {
|
// if (!_isUserSuperAdmin()) {
|
||||||
return const SizedBox.shrink(); // Cacher pour les non-admins
|
// return const SizedBox.shrink(); // Cacher pour les non-admins
|
||||||
}
|
// }
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
@ -2630,7 +2822,7 @@ Widget _buildProductListItem(Product product, int quantity, bool isMobile) {
|
|||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.swap_horiz, size: 14),
|
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(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: (product.stock != null && product.stock! >= 1)
|
backgroundColor: (product.stock != null && product.stock! >= 1)
|
||||||
? Colors.blue.shade700
|
? Colors.blue.shade700
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
// Config/database_config.dart - Version améliorée
|
// Config/database_config.dart - Version améliorée
|
||||||
class DatabaseConfig {
|
class DatabaseConfig {
|
||||||
static const String host = 'localhost';
|
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 int port = 3306;
|
||||||
static const String username = 'root';
|
static const String username = 'root';
|
||||||
static const String? password = null;
|
static const String? password = null;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user