Browse Source

changement au nivaue de scan

28062025_02
b.razafimandimbihery 5 months ago
parent
commit
55e896775d
  1. 18
      lib/Services/stock_managementDatabase.dart
  2. 6
      lib/Views/DemandeTransfert.dart
  3. 536
      lib/Views/HandleProduct.dart
  4. 761
      lib/Views/commandManagement.dart
  5. 338
      lib/Views/newCommand.dart
  6. 4
      lib/config/DatabaseConfig.dart

18
lib/Services/stock_managementDatabase.dart

@ -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

6
lib/Views/DemandeTransfert.dart

@ -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),
), ),
), ),

536
lib/Views/HandleProduct.dart

@ -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(

761
lib/Views/commandManagement.dart

@ -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
pw.Widget buildExemplaire(String typeExemplaire, {bool isSecond = false}) { // Fonction pour créer un exemplaire en mode paysage
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: [ crossAxisAlignment: pw.CrossAxisAlignment.start,
pw.Container( children: [
width: 45, pw.Container(
height: 45, width: 100,
child: pw.Image(image), height: 100,
), 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( pw.SizedBox(height: 3),
pw.Text('NOTRE COMPETENCE, A VOTRE SERVICE', style: italicLogoStyle),
pw.SizedBox(height: 4),
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [ children: [
pw.Text('Boutique:', style: frameTextStyle), pw.Text('📍 REMAX Andravoangy', style: tinyTextStyle),
pw.Text('${pointDeVente?['nom'] ?? 'S405A'}', style: boldTextStyle), pw.Text('📍 SUPREME CENTER Behoririka \n BOX 405 | 416 | 119', style: tinyTextStyle),
pw.SizedBox(height: 1), pw.Text('📍 Tripolisa analankely BOX 7', style: tinyTextStyle),
pw.Text('Bon N°:', style: frameTextStyle), pw.Text('📞 033 37 808 18', style: tinyTextStyle),
pw.Text('${pointDeVente?['nom'] ?? 'S405A'}-P${commande.id}', style: boldTextStyle), pw.Text('🌐 www.guycom.mg', style: tinyTextStyle),
pw.SizedBox(height: 2),
pw.Text('NIF: 1026/GC78-20-02-22',
style: pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold)),
], ],
), ),
), ],
],
),
// 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( // Informations centrales
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.center, crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [ children: [
pw.Text('CLIENT', style: frameTextStyle), pw.Text('Date: ${DateFormat('dd/MM/yyyy').format(DateTime.now())}', style: boldClientStyle),
pw.SizedBox(height: 1), pw.SizedBox(height: 4),
pw.Text('ID: ${pointDeVente?['nom'] ?? 'S405A'}-${client?.id ?? 'Non spécifié'}', style: smallTextStyle), pw.Container(width: 100, height: 2, color: PdfColors.black),
pw.Container(width: 80, height: 1, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 1)), pw.SizedBox(height: 4),
pw.Text(client?.nom ?? 'Non spécifié', style: boldTextStyle), pw.Container(
pw.SizedBox(height: 1), padding: const pw.EdgeInsets.all(6),
pw.Text(client?.telephone ?? 'Non spécifié', style: tinyTextStyle), 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),
],
),
),
], ],
), ),
),
],
),
pw.SizedBox(height: 4), // Informations client
pw.Container(
// Tableau des produits - très compact width: 120,
pw.Table( decoration: pw.BoxDecoration(
border: pw.TableBorder.all(width: 0.5), border: pw.Border.all(color: PdfColors.black, width: 1),
columnWidths: { ),
0: const pw.FlexColumnWidth(3.5), padding: const pw.EdgeInsets.all(6),
1: const pw.FlexColumnWidth(0.8), child: pw.Column(
2: const pw.FlexColumnWidth(1.2), crossAxisAlignment: pw.CrossAxisAlignment.center,
3: const pw.FlexColumnWidth(1.5), children: [
4: const pw.FlexColumnWidth(1.2), pw.Text('CLIENT', style: frameTextStyle),
}, pw.SizedBox(height: 2),
children: [ pw.Text('ID: ${pointDeVente?['nom'] ?? 'S405A'}-${client?.id ?? 'Non spécifié'}', style: smallTextStyle),
pw.TableRow( pw.Container(width: 100, height: 1, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 2)),
decoration: const pw.BoxDecoration(color: PdfColors.grey200), 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: [ children: [
pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('Désignations', style: boldTextStyle)), pw.TableRow(
pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('Qté', style: boldTextStyle, textAlign: pw.TextAlign.center)), decoration: const pw.BoxDecoration(color: PdfColors.grey200),
pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('P.U.', style: boldTextStyle, textAlign: pw.TextAlign.right)), children: [
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(3),
pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('Montant', style: boldTextStyle, textAlign: pw.TextAlign.right)), 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) { ...detailsAvecProduits.map((item) {
final detail = item['detail'] as DetailCommande; final detail = item['detail'] as DetailCommande;
final produit = item['produit']; final produit = item['produit'];
return pw.TableRow( return pw.TableRow(
decoration: detail.estCadeau decoration: detail.estCadeau
? const pw.BoxDecoration(color: PdfColors.green50) ? const pw.BoxDecoration(color: PdfColors.green50)
: detail.aRemise : detail.aRemise
? const pw.BoxDecoration(color: PdfColors.orange50) ? const pw.BoxDecoration(color: PdfColors.orange50)
: null, : null,
children: [ children: [
pw.Padding( pw.Padding(
padding: const pw.EdgeInsets.all(1), padding: const pw.EdgeInsets.all(3),
child: pw.Column( child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Row(
children: [ children: [
pw.Expanded( pw.Row(
child: pw.Text(detail.produitNom ?? 'Produit inconnu', children: [
style: pw.TextStyle(fontSize: 6, fontWeight: pw.FontWeight.bold)), pw.Expanded(
), child: pw.Text(detail.produitNom ?? 'Produit inconnu',
if (detail.estCadeau) style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold)),
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 (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),
], ],
), ),
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(3),
), child: pw.Text('${detail.quantite}', style: normalTextStyle, textAlign: pw.TextAlign.center),
pw.Padding( ),
padding: const pw.EdgeInsets.all(1), pw.Padding(
child: pw.Column( padding: const pw.EdgeInsets.all(3),
crossAxisAlignment: pw.CrossAxisAlignment.end, child: pw.Column(
children: [ crossAxisAlignment: pw.CrossAxisAlignment.end,
if (detail.estCadeau) ...[ children: [
pw.Text('${detail.sousTotal.toStringAsFixed(0)}', if (detail.estCadeau) ...[
style: pw.TextStyle(fontSize: 4, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)), pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}',
pw.Text('GRATUIT', style: pw.TextStyle(fontSize: 5, fontWeight: pw.FontWeight.bold, color: PdfColors.green700)), style: pw.TextStyle(fontSize: 8, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)),
] else if (detail.aRemise) ...[ pw.Text('GRATUIT', style: pw.TextStyle(fontSize: 9, color: PdfColors.green700, fontWeight: pw.FontWeight.bold)),
pw.Text('${detail.sousTotal.toStringAsFixed(0)}', ] else if (detail.aRemise) ...[
style: pw.TextStyle(fontSize: 4, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)), pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}',
pw.Text('${detail.prixFinal.toStringAsFixed(0)}', style: pw.TextStyle(fontSize: 6, fontWeight: pw.FontWeight.bold)), style: pw.TextStyle(fontSize: 8, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)),
] else pw.Text('${(detail.prixFinal / detail.quantite).toStringAsFixed(0)}',
pw.Text('${detail.prixFinal.toStringAsFixed(0)}', style: smallTextStyle), style: pw.TextStyle(fontSize: 9, color: PdfColors.orange)),
], ] else
), pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}', style: smallTextStyle),
), ],
], ),
); ),
}).toList(), // 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: 4), pw.SizedBox(height: 8),
// Section finale - très compacte // Section finale (ajustée pour le mode paysage)
pw.Row( pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [ children: [
// Totaux // Totaux
pw.Expanded( pw.Expanded(
flex: 2, flex: 2,
child: pw.Column( child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.end, crossAxisAlignment: pw.CrossAxisAlignment.end,
children: [ children: [
if (totalRemises > 0 || totalCadeaux > 0) ...[ if (totalRemises > 0 || totalCadeaux > 0) ...[
pw.Row( pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.end, mainAxisAlignment: pw.MainAxisAlignment.end,
children: [ children: [
pw.Text('SOUS-TOTAL:', style: smallTextStyle), pw.Text('SOUS-TOTAL:', style: smallTextStyle),
pw.SizedBox(width: 8), pw.SizedBox(width: 10),
pw.Text('${sousTotal.toStringAsFixed(0)}', style: smallTextStyle), pw.Text('${sousTotal.toStringAsFixed(0)}', style: smallTextStyle),
], ],
), ),
pw.SizedBox(height: 1), pw.SizedBox(height: 2),
], ],
if (totalRemises > 0) ...[ if (totalRemises > 0) ...[
pw.Row( pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.end, mainAxisAlignment: pw.MainAxisAlignment.end,
children: [ children: [
pw.Text('REMISES:', style: pw.TextStyle(color: PdfColors.orange, fontSize: 6)), pw.Text('REMISES:', style: pw.TextStyle(color: PdfColors.orange, fontSize: 10)),
pw.SizedBox(width: 8), pw.SizedBox(width: 10),
pw.Text('-${totalRemises.toStringAsFixed(0)}', style: pw.TextStyle(color: PdfColors.orange, fontSize: 6)), pw.Text('-${totalRemises.toStringAsFixed(0)}', style: pw.TextStyle(color: PdfColors.orange, fontSize: 10)),
], ],
), ),
pw.SizedBox(height: 1), 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)),
if (totalCadeaux > 0) ...[
pw.Row( pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.end, mainAxisAlignment: pw.MainAxisAlignment.end,
children: [ children: [
pw.Text('CADEAUX ($nombreCadeaux):', style: pw.TextStyle(color: PdfColors.green700, fontSize: 6)), pw.Text('TOTAL:', style: boldTextStyle),
pw.SizedBox(width: 8), pw.SizedBox(width: 10),
pw.Text('-${totalCadeaux.toStringAsFixed(0)}', style: pw.TextStyle(color: PdfColors.green700, fontSize: 6)), pw.Text('${commande.montantTotal.toStringAsFixed(0)} MGA', style: boldTextStyle),
], ],
), ),
pw.SizedBox(height: 1),
],
pw.Container(width: 100, height: 1, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 1)), if (totalCadeaux > 0) ...[
pw.SizedBox(height: 3),
pw.Row( pw.Container(
mainAxisAlignment: pw.MainAxisAlignment.end, padding: const pw.EdgeInsets.all(3),
children: [ decoration: pw.BoxDecoration(
pw.Text('TOTAL:', style: boldTextStyle), color: PdfColors.green50,
pw.SizedBox(width: 8), borderRadius: pw.BorderRadius.circular(3),
pw.Text('${commande.montantTotal.toStringAsFixed(0)} MGA', style: boldTextStyle), ),
child: pw.Text(
'🎁 $nombreCadeaux cadeau(s) offert(s) (${totalCadeaux.toStringAsFixed(0)} MGA)',
style: pw.TextStyle(fontSize: 9, color: PdfColors.green700),
),
),
], ],
), ],
),
),
if (totalCadeaux > 0) ...[ pw.SizedBox(width: 15),
pw.SizedBox(height: 3),
// Informations vendeurs et signatures
pw.Expanded(
flex: 3,
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
// Vendeurs
pw.Container( pw.Container(
padding: const pw.EdgeInsets.all(3), padding: const pw.EdgeInsets.all(4),
decoration: pw.BoxDecoration( decoration: pw.BoxDecoration(
color: PdfColors.green50, color: PdfColors.grey100,
borderRadius: pw.BorderRadius.circular(3), borderRadius: pw.BorderRadius.circular(3),
), ),
child: pw.Text( child: pw.Column(
'🎁 $nombreCadeaux cadeau(s) offert(s) (${totalCadeaux.toStringAsFixed(0)} MGA)', crossAxisAlignment: pw.CrossAxisAlignment.start,
style: pw.TextStyle(fontSize: 5, color: PdfColors.green700), 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(width: 10), pw.SizedBox(height: 8),
// Informations vendeurs et signatures // Signatures
pw.Expanded( pw.Row(
flex: 3, mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
// Vendeurs
pw.Container(
padding: const pw.EdgeInsets.all(3),
decoration: pw.BoxDecoration(
color: PdfColors.grey100,
borderRadius: pw.BorderRadius.circular(3),
),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [ children: [
pw.Text('VENDEURS', style: pw.TextStyle(fontSize: 6, fontWeight: pw.FontWeight.bold)), pw.Column(
pw.SizedBox(height: 1),
pw.Row(
children: [ children: [
pw.Expanded( pw.Text('Vendeur', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold)),
child: pw.Column( pw.SizedBox(height: 15),
crossAxisAlignment: pw.CrossAxisAlignment.start, pw.Container(width: 70, height: 1, color: PdfColors.black),
children: [ ],
pw.Text('Initiateur:', style: tinyTextStyle), ),
pw.Text( pw.Column(
commandeur != null ? '${commandeur.name} ${commandeur.lastName ?? ''}'.trim() : 'N/A', children: [
style: pw.TextStyle(fontSize: 5), 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.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: 6),
// Signatures
pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Column(
children: [
pw.Text('Vendeur', style: pw.TextStyle(fontSize: 5, fontWeight: pw.FontWeight.bold)),
pw.SizedBox(height: 8),
pw.Container(width: 50, height: 1, color: PdfColors.black),
],
),
pw.Column(
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),
],
),
],
),
],
), ),
), ],
], ),
),
pw.SizedBox(height: 3), pw.SizedBox(height: 4),
// Note finale // Note finale
pw.Text( pw.Text(
'Arrêté à la somme de: ${_numberToWords(commande.montantTotal.toInt())} Ariary', 'Arrêté à la somme de: ${_numberToWords(commande.montantTotal.toInt())} Ariary',
style: italicTextStyle, 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),
], ],

338
lib/Views/newCommand.dart

@ -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(
Get.dialog(
Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Container(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
// Header avec icône de succès
Container( Container(
padding: const EdgeInsets.all(8), width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.green.shade100, color: Colors.green.shade50,
borderRadius: BorderRadius.circular(8), 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,
shape: BoxShape.circle,
),
child: Icon(
Icons.check_circle,
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,
),
),
],
),
),
],
), ),
child: Icon(Icons.check_circle, color: Colors.green.shade700),
), ),
const SizedBox(width: 12),
const Expanded(child: Text('Produit identifié et ajouté !')), const SizedBox(height: 20),
],
), // Informations du produit
content: Column( Container(
mainAxisSize: MainAxisSize.min, width: double.infinity,
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.all(16),
children: [ decoration: BoxDecoration(
Text( color: Colors.grey.shade50,
product.name, borderRadius: BorderRadius.circular(12),
style: const TextStyle( border: Border.all(color: Colors.grey.shade200),
fontSize: 16, ),
fontWeight: FontWeight.bold, 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: 8),
if (product.imei != null && product.imei!.isNotEmpty) const SizedBox(height: 20),
Text('IMEI: ${product.imei}'),
if (product.reference != null && product.reference!.isNotEmpty) // Badge identification automatique
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), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.green.shade50, color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.blue.shade200),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(Icons.auto_awesome, Icon(Icons.auto_awesome,
color: Colors.green.shade700, size: 16), color: Colors.blue.shade700, size: 16),
const SizedBox(width: 8), const SizedBox(width: 6),
const Expanded( Text(
child: Text( 'Identifié automatiquement',
'Produit identifié automatiquement', style: TextStyle(
style: TextStyle( fontSize: 12,
fontSize: 12, fontWeight: FontWeight.w600,
fontWeight: FontWeight.w500, 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( // Widget helper pour les détails du produit
onPressed: () { Widget _buildProductDetailRow(String label, String value) {
Get.back(); return Padding(
_showCartBottomSheet(); padding: const EdgeInsets.only(bottom: 8),
}, child: Row(
style: ElevatedButton.styleFrom( crossAxisAlignment: CrossAxisAlignment.start,
backgroundColor: Colors.green.shade700, children: [
foregroundColor: Colors.white, SizedBox(
width: 100,
child: Text(
'$label:',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.grey.shade600,
), ),
child: const Text('Voir le panier'),
), ),
ElevatedButton( ),
onPressed: () { Expanded(
Get.back(); child: Text(
_startAutomaticScanning(); // Scanner un autre produit value,
}, style: const TextStyle(
style: ElevatedButton.styleFrom( fontSize: 14,
backgroundColor: Colors.blue.shade700, fontWeight: FontWeight.w600,
foregroundColor: Colors.white, color: Colors.black87,
), ),
child: const Text('Scanner encore'),
), ),
], ),
), ],
); ),
} );
}
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

4
lib/config/DatabaseConfig.dart

@ -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…
Cancel
Save