impression
This commit is contained in:
parent
ea3be45c06
commit
f65ab1e397
@ -1,4 +1,6 @@
|
|||||||
// pages/facture_screen.dart
|
// pages/facture_screen.dart
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import '../models/command_detail.dart';
|
import '../models/command_detail.dart';
|
||||||
@ -255,51 +257,183 @@ class _FactureScreenState extends State<FactureScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _printReceipt() async {
|
void _printReceipt() async {
|
||||||
try {
|
bool isPrinting;
|
||||||
HapticFeedback.lightImpact();
|
setState(() => isPrinting = true);
|
||||||
|
|
||||||
final success = await PdfService.printFacture(
|
try {
|
||||||
|
// Vérifier si l'impression est disponible
|
||||||
|
final canPrint = await PlatformPrintService.canPrint();
|
||||||
|
|
||||||
|
if (!canPrint) {
|
||||||
|
// Si pas d'imprimante, proposer seulement la sauvegarde
|
||||||
|
_showSaveOnlyDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher les options d'impression
|
||||||
|
final action = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.print, color: Theme.of(context).primaryColor),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Text('Options d\'impression'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Plateforme: ${_getPlatformName()}',
|
||||||
|
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text('Que souhaitez-vous faire ?'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () => Navigator.of(context).pop('print'),
|
||||||
|
icon: const Icon(Icons.print),
|
||||||
|
label: const Text('Imprimer'),
|
||||||
|
),
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () => Navigator.of(context).pop('save'),
|
||||||
|
icon: const Icon(Icons.save),
|
||||||
|
label: const Text('Sauvegarder PDF'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop('cancel'),
|
||||||
|
child: const Text('Annuler'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (action == null || action == 'cancel') return;
|
||||||
|
|
||||||
|
HapticFeedback.lightImpact();
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
if (action == 'print') {
|
||||||
|
success = await PlatformPrintService.printFacture(
|
||||||
|
commande: widget.commande,
|
||||||
|
paymentMethod: widget.paymentMethod,
|
||||||
|
);
|
||||||
|
} else if (action == 'save') {
|
||||||
|
success = await PlatformPrintService.saveFacturePdf(
|
||||||
|
commande: widget.commande,
|
||||||
|
paymentMethod: widget.paymentMethod,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
_showSuccessMessage(
|
||||||
|
action == 'print'
|
||||||
|
? 'Facture envoyée à l\'imprimante ${_getPlatformName()}'
|
||||||
|
: 'PDF sauvegardé et partagé',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_showErrorMessage(
|
||||||
|
'Erreur lors de ${action == 'print' ? 'l\'impression' : 'la sauvegarde'}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_showErrorMessage('Erreur: $e');
|
||||||
|
} finally {
|
||||||
|
setState(() => isPrinting = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showSaveOnlyDialog() async {
|
||||||
|
final shouldSave = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Aucune imprimante'),
|
||||||
|
content: const Text(
|
||||||
|
'Aucune imprimante détectée. Voulez-vous sauvegarder le PDF ?',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
icon: const Icon(Icons.save),
|
||||||
|
label: const Text('Sauvegarder'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
child: const Text('Annuler'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldSave == true) {
|
||||||
|
final success = await PlatformPrintService.saveFacturePdf(
|
||||||
commande: widget.commande,
|
commande: widget.commande,
|
||||||
paymentMethod: widget.paymentMethod,
|
paymentMethod: widget.paymentMethod,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
|
_showSuccessMessage('PDF sauvegardé avec succès');
|
||||||
|
} else {
|
||||||
|
_showErrorMessage('Erreur lors de la sauvegarde');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showSuccessMessage(String message) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: const Row(
|
content: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.check_circle, color: Colors.white),
|
const Icon(Icons.check_circle, color: Colors.white),
|
||||||
SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text('Facture envoyée à l\'impression'),
|
Expanded(child: Text(message)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: const Color(0xFF28A745),
|
backgroundColor: const Color(0xFF28A745),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 3),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
),
|
|
||||||
margin: const EdgeInsets.all(16),
|
margin: const EdgeInsets.all(16),
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Future.delayed(const Duration(seconds: 2), () {
|
Future.delayed(const Duration(seconds: 3), () {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
|
void _showErrorMessage(String message) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Erreur impression: $e'),
|
content: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.error, color: Colors.white),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(child: Text(message)),
|
||||||
|
],
|
||||||
|
),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
margin: const EdgeInsets.all(16),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} finally {
|
}
|
||||||
if (mounted) {
|
|
||||||
Navigator.of(context).pop();
|
String _getPlatformName() {
|
||||||
}
|
if (Platform.isAndroid) return 'Android';
|
||||||
}
|
if (Platform.isMacOS) return 'macOS';
|
||||||
|
if (Platform.isWindows) return 'Windows';
|
||||||
|
return 'cette plateforme';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,188 +1,290 @@
|
|||||||
// services/pdf_service.dart
|
// services/platform_print_service.dart
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:itrimobe/models/command_detail.dart';
|
||||||
import 'package:pdf/pdf.dart';
|
import 'package:pdf/pdf.dart';
|
||||||
import 'package:pdf/widgets.dart' as pw;
|
import 'package:pdf/widgets.dart' as pw;
|
||||||
import 'package:printing/printing.dart';
|
import 'package:printing/printing.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import '../models/command_detail.dart';
|
import '../models/command_detail.dart';
|
||||||
|
|
||||||
class PdfService {
|
class PlatformPrintService {
|
||||||
static Future<Uint8List> generateFacturePdf({
|
// Format spécifique 58mm pour petites imprimantes
|
||||||
|
static const PdfPageFormat ticket58mmFormat = PdfPageFormat(
|
||||||
|
58 * PdfPageFormat.mm, // Largeur exacte 58mm
|
||||||
|
double.infinity, // Hauteur automatique
|
||||||
|
marginLeft: 1 * PdfPageFormat.mm,
|
||||||
|
marginRight: 1 * PdfPageFormat.mm,
|
||||||
|
marginTop: 2 * PdfPageFormat.mm,
|
||||||
|
marginBottom: 2 * PdfPageFormat.mm,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Vérifier les permissions
|
||||||
|
static Future<bool> _checkPermissions() async {
|
||||||
|
if (!Platform.isAndroid) return true;
|
||||||
|
|
||||||
|
final storagePermission = await Permission.storage.request();
|
||||||
|
return storagePermission == PermissionStatus.granted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si l'impression est possible
|
||||||
|
static Future<bool> canPrint() async {
|
||||||
|
try {
|
||||||
|
return await Printing.info().then((info) => info.canPrint);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Générer PDF optimisé pour 58mm
|
||||||
|
static Future<Uint8List> _generate58mmTicketPdf({
|
||||||
required CommandeDetail commande,
|
required CommandeDetail commande,
|
||||||
required String paymentMethod,
|
required String paymentMethod,
|
||||||
}) async {
|
}) async {
|
||||||
final pdf = pw.Document();
|
final pdf = pw.Document();
|
||||||
|
|
||||||
// Informations du restaurant
|
// Configuration pour 58mm (très petit)
|
||||||
|
const double titleSize = 9;
|
||||||
|
const double headerSize = 8;
|
||||||
|
const double bodySize = 7;
|
||||||
|
const double smallSize = 6;
|
||||||
|
const double lineHeight = 1.2;
|
||||||
|
|
||||||
final restaurantInfo = {
|
final restaurantInfo = {
|
||||||
'nom': 'RESTAURANT',
|
'nom': 'RESTAURANT',
|
||||||
'adresse': 'Moramanga, Antananarivo',
|
'adresse': '123 Rue de la Paix',
|
||||||
'contact': '+261 34 12 34 56',
|
'ville': '75000 PARIS',
|
||||||
|
'contact': '01.23.45.67.89',
|
||||||
|
'email': 'contact@restaurant.fr',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Générer numéro de facture
|
|
||||||
final factureNumber =
|
final factureNumber =
|
||||||
'F${DateTime.now().millisecondsSinceEpoch.toString().substring(7)}';
|
'T${DateTime.now().millisecondsSinceEpoch.toString().substring(8)}';
|
||||||
final dateTime = DateTime.now();
|
final dateTime = DateTime.now();
|
||||||
|
|
||||||
pdf.addPage(
|
pdf.addPage(
|
||||||
pw.Page(
|
pw.Page(
|
||||||
pageFormat: PdfPageFormat.a4,
|
pageFormat: ticket58mmFormat,
|
||||||
margin: const pw.EdgeInsets.all(32),
|
|
||||||
build: (pw.Context context) {
|
build: (pw.Context context) {
|
||||||
return pw.Column(
|
return pw.Column(
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
||||||
children: [
|
|
||||||
// En-tête Restaurant
|
|
||||||
pw.Center(
|
|
||||||
child: pw.Column(
|
|
||||||
children: [
|
children: [
|
||||||
|
// En-tête Restaurant (centré et compact)
|
||||||
pw.Text(
|
pw.Text(
|
||||||
restaurantInfo['nom']!,
|
restaurantInfo['nom']!,
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
fontSize: 24,
|
fontSize: titleSize,
|
||||||
fontWeight: pw.FontWeight.bold,
|
fontWeight: pw.FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
textAlign: pw.TextAlign.center,
|
||||||
pw.SizedBox(height: 8),
|
|
||||||
pw.Text(
|
|
||||||
'Adresse: ${restaurantInfo['adresse']}',
|
|
||||||
style: const pw.TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
pw.Text(
|
|
||||||
'Contact: ${restaurantInfo['contact']}',
|
|
||||||
style: const pw.TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
pw.SizedBox(height: 30),
|
pw.SizedBox(height: 1),
|
||||||
|
|
||||||
// Informations facture
|
pw.Text(
|
||||||
pw.Center(
|
restaurantInfo['adresse']!,
|
||||||
child: pw.Column(
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
|
textAlign: pw.TextAlign.center,
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.Text(
|
||||||
|
restaurantInfo['ville']!,
|
||||||
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
|
textAlign: pw.TextAlign.center,
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.Text(
|
||||||
|
'Tel: ${restaurantInfo['contact']!}',
|
||||||
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
|
textAlign: pw.TextAlign.center,
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 3),
|
||||||
|
|
||||||
|
// Ligne de séparation
|
||||||
|
pw.Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 0.5,
|
||||||
|
color: PdfColors.black,
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
|
// Informations ticket
|
||||||
|
pw.Row(
|
||||||
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
pw.Text(
|
pw.Text(
|
||||||
'Facture n° $factureNumber',
|
'Ticket: $factureNumber',
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
fontSize: 14,
|
fontSize: bodySize,
|
||||||
fontWeight: pw.FontWeight.bold,
|
fontWeight: pw.FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
pw.SizedBox(height: 4),
|
// pw.Text(
|
||||||
pw.Text(
|
// 'Table: ${commande.tableName}',
|
||||||
'Date: ${_formatDateTime(dateTime)}',
|
// style: pw.TextStyle(fontSize: bodySize),
|
||||||
style: const pw.TextStyle(fontSize: 12),
|
// ),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
pw.SizedBox(height: 4),
|
|
||||||
|
pw.SizedBox(height: 1),
|
||||||
|
|
||||||
|
pw.Row(
|
||||||
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
pw.Text(
|
pw.Text(
|
||||||
'Table: ${commande.numeroCommande}',
|
_formatDate(dateTime),
|
||||||
style: const pw.TextStyle(fontSize: 12),
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
),
|
),
|
||||||
pw.SizedBox(height: 4),
|
|
||||||
pw.Text(
|
pw.Text(
|
||||||
'Paiement: ${_getPaymentMethodText(paymentMethod)}',
|
_formatTime(dateTime),
|
||||||
style: const pw.TextStyle(fontSize: 12),
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
|
// Ligne de séparation
|
||||||
|
pw.Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 0.5,
|
||||||
|
color: PdfColors.black,
|
||||||
),
|
),
|
||||||
|
|
||||||
pw.SizedBox(height: 30),
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
// Tableau des articles
|
// Articles (format très compact)
|
||||||
pw.Table(
|
|
||||||
border: pw.TableBorder.all(color: PdfColors.grey300),
|
|
||||||
columnWidths: {
|
|
||||||
0: const pw.FlexColumnWidth(3),
|
|
||||||
1: const pw.FlexColumnWidth(1),
|
|
||||||
2: const pw.FlexColumnWidth(1),
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
// En-tête du tableau
|
|
||||||
pw.TableRow(
|
|
||||||
decoration: const pw.BoxDecoration(
|
|
||||||
color: PdfColors.grey100,
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
pw.Padding(
|
|
||||||
padding: const pw.EdgeInsets.all(8),
|
|
||||||
child: pw.Text(
|
|
||||||
'Qté Désignation',
|
|
||||||
style: pw.TextStyle(fontWeight: pw.FontWeight.bold),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
pw.Padding(
|
|
||||||
padding: const pw.EdgeInsets.all(8),
|
|
||||||
child: pw.Text(
|
|
||||||
'Prix',
|
|
||||||
style: pw.TextStyle(fontWeight: pw.FontWeight.bold),
|
|
||||||
textAlign: pw.TextAlign.right,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// Lignes des articles
|
|
||||||
...commande.items
|
...commande.items
|
||||||
.map(
|
.map(
|
||||||
(item) => pw.TableRow(
|
(item) => pw.Container(
|
||||||
|
margin: const pw.EdgeInsets.only(bottom: 1),
|
||||||
|
child: pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
pw.Padding(
|
// Nom du plat
|
||||||
padding: const pw.EdgeInsets.all(8),
|
pw.Text(
|
||||||
child: pw.Text(
|
"NOMPLAT",
|
||||||
'${item.quantite} TESTNOMCOMMANDE',
|
style: pw.TextStyle(fontSize: bodySize),
|
||||||
|
maxLines: 2,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Quantité, prix unitaire et total sur une ligne
|
||||||
|
pw.Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
pw.MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
pw.Text(
|
||||||
|
'${item.quantite}x ${item.prixUnitaire.toStringAsFixed(2)}€',
|
||||||
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
),
|
),
|
||||||
pw.Padding(
|
pw.Text(
|
||||||
padding: const pw.EdgeInsets.all(8),
|
'${(item.prixUnitaire * item.quantite).toStringAsFixed(2)}€',
|
||||||
child: pw.Text(
|
style: pw.TextStyle(
|
||||||
'${item.prixUnitaire.toStringAsFixed(2)} €',
|
fontSize: bodySize,
|
||||||
textAlign: pw.TextAlign.right,
|
fontWeight: pw.FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
],
|
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
|
// Ligne de séparation
|
||||||
|
pw.Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 0.5,
|
||||||
|
color: PdfColors.black,
|
||||||
),
|
),
|
||||||
|
|
||||||
pw.SizedBox(height: 20),
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
// Total
|
// Total
|
||||||
pw.Container(
|
pw.Row(
|
||||||
alignment: pw.Alignment.centerRight,
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||||
child: pw.Container(
|
children: [
|
||||||
padding: const pw.EdgeInsets.all(12),
|
pw.Text(
|
||||||
decoration: pw.BoxDecoration(
|
'TOTAL',
|
||||||
border: pw.Border.all(color: PdfColors.grey400),
|
|
||||||
color: PdfColors.grey50,
|
|
||||||
),
|
|
||||||
child: pw.Text(
|
|
||||||
'Total: ${commande.totalTtc.toStringAsFixed(2)} €',
|
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
fontSize: 16,
|
fontSize: titleSize,
|
||||||
fontWeight: pw.FontWeight.bold,
|
fontWeight: pw.FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
pw.Text(
|
||||||
|
'${commande.totalTtc.toStringAsFixed(2)}€',
|
||||||
|
style: pw.TextStyle(
|
||||||
|
fontSize: titleSize,
|
||||||
|
fontWeight: pw.FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
pw.Spacer(),
|
pw.SizedBox(height: 3),
|
||||||
|
|
||||||
|
// Mode de paiement
|
||||||
|
pw.Text(
|
||||||
|
'Paiement: ${_getPaymentMethodText(paymentMethod)}',
|
||||||
|
style: pw.TextStyle(fontSize: bodySize),
|
||||||
|
textAlign: pw.TextAlign.center,
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 3),
|
||||||
|
|
||||||
|
// Ligne de séparation
|
||||||
|
pw.Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 0.5,
|
||||||
|
color: PdfColors.black,
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
// Message de remerciement
|
// Message de remerciement
|
||||||
pw.Center(
|
pw.Text(
|
||||||
child: pw.Text(
|
'Merci de votre visite !',
|
||||||
'Merci et à bientôt !',
|
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
fontSize: 12,
|
fontSize: bodySize,
|
||||||
fontStyle: pw.FontStyle.italic,
|
fontStyle: pw.FontStyle.italic,
|
||||||
),
|
),
|
||||||
|
textAlign: pw.TextAlign.center,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
pw.Text(
|
||||||
|
'A bientôt !',
|
||||||
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
|
textAlign: pw.TextAlign.center,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 3),
|
||||||
|
|
||||||
|
// Code de suivi (optionnel)
|
||||||
|
pw.Text(
|
||||||
|
'Code: ${factureNumber}',
|
||||||
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
|
textAlign: pw.TextAlign.center,
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 4),
|
||||||
|
|
||||||
|
// Ligne de découpe
|
||||||
|
pw.Text(
|
||||||
|
'- - - - - - - - - - - - - - - -',
|
||||||
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
|
textAlign: pw.TextAlign.center,
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -192,75 +294,120 @@ class PdfService {
|
|||||||
return pdf.save();
|
return pdf.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
static String _formatDateTime(DateTime dateTime) {
|
// Imprimer ticket 58mm
|
||||||
return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
|
static Future<bool> printTicket({
|
||||||
}
|
|
||||||
|
|
||||||
static String _getPaymentMethodText(String method) {
|
|
||||||
switch (method) {
|
|
||||||
case 'mvola':
|
|
||||||
return 'MVola';
|
|
||||||
case 'carte':
|
|
||||||
return 'CB';
|
|
||||||
case 'especes':
|
|
||||||
return 'Espèces';
|
|
||||||
default:
|
|
||||||
return 'CB';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Imprimer directement
|
|
||||||
static Future<bool> printFacture({
|
|
||||||
required CommandeDetail commande,
|
required CommandeDetail commande,
|
||||||
required String paymentMethod,
|
required String paymentMethod,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final pdfData = await generateFacturePdf(
|
final hasPermission = await _checkPermissions();
|
||||||
|
if (!hasPermission) {
|
||||||
|
throw Exception('Permissions requises pour l\'impression');
|
||||||
|
}
|
||||||
|
|
||||||
|
final pdfData = await _generate58mmTicketPdf(
|
||||||
commande: commande,
|
commande: commande,
|
||||||
paymentMethod: paymentMethod,
|
paymentMethod: paymentMethod,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final fileName =
|
||||||
|
'Ticket_${commande.numeroCommande}_${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
|
||||||
await Printing.layoutPdf(
|
await Printing.layoutPdf(
|
||||||
onLayout: (PdfPageFormat format) async => pdfData,
|
onLayout: (PdfPageFormat format) async => pdfData,
|
||||||
name:
|
name: fileName,
|
||||||
'Facture_${commande.numeroCommande}_${DateTime.now().millisecondsSinceEpoch}',
|
format: ticket58mmFormat,
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur impression: $e');
|
print('Erreur impression 58mm: $e');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sauvegarder et partager le PDF
|
// Sauvegarder ticket 58mm
|
||||||
static Future<bool> saveAndShareFacture({
|
static Future<bool> saveTicketPdf({
|
||||||
required CommandeDetail commande,
|
required CommandeDetail commande,
|
||||||
required String paymentMethod,
|
required String paymentMethod,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final pdfData = await generateFacturePdf(
|
final hasPermission = await _checkPermissions();
|
||||||
|
if (!hasPermission) return false;
|
||||||
|
|
||||||
|
final pdfData = await _generate58mmTicketPdf(
|
||||||
commande: commande,
|
commande: commande,
|
||||||
paymentMethod: paymentMethod,
|
paymentMethod: paymentMethod,
|
||||||
);
|
);
|
||||||
|
|
||||||
final directory = await getApplicationDocumentsDirectory();
|
Directory directory;
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
directory = Directory('/storage/emulated/0/Download');
|
||||||
|
if (!directory.existsSync()) {
|
||||||
|
directory =
|
||||||
|
await getExternalStorageDirectory() ??
|
||||||
|
await getApplicationDocumentsDirectory();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
directory = await getApplicationDocumentsDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
final fileName =
|
final fileName =
|
||||||
'Facture_${commande.numeroCommande}_${DateTime.now().millisecondsSinceEpoch}.pdf';
|
'Ticket_58mm_${commande.numeroCommande}_${DateTime.now().millisecondsSinceEpoch}.pdf';
|
||||||
final file = File('${directory.path}/$fileName');
|
final file = File('${directory.path}/$fileName');
|
||||||
|
|
||||||
await file.writeAsBytes(pdfData);
|
await file.writeAsBytes(pdfData);
|
||||||
|
|
||||||
await Share.shareXFiles(
|
await Share.shareXFiles(
|
||||||
[XFile(file.path)],
|
[XFile(file.path)],
|
||||||
subject: 'Facture ${commande.numeroCommande}',
|
subject: 'Ticket ${commande.numeroCommande}',
|
||||||
text: 'Facture de votre commande au restaurant',
|
text: 'Ticket de caisse 58mm',
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur sauvegarde/partage: $e');
|
print('Erreur sauvegarde 58mm: $e');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Méthodes pour compatibilité
|
||||||
|
static Future<bool> saveFacturePdf({
|
||||||
|
required CommandeDetail commande,
|
||||||
|
required String paymentMethod,
|
||||||
|
}) async {
|
||||||
|
return await saveTicketPdf(
|
||||||
|
commande: commande,
|
||||||
|
paymentMethod: paymentMethod,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> printFacture({
|
||||||
|
required CommandeDetail commande,
|
||||||
|
required String paymentMethod,
|
||||||
|
}) async {
|
||||||
|
return await printTicket(commande: commande, paymentMethod: paymentMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utilitaires de formatage
|
||||||
|
static String _formatDate(DateTime dateTime) {
|
||||||
|
return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year}';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _formatTime(DateTime dateTime) {
|
||||||
|
return '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _getPaymentMethodText(String method) {
|
||||||
|
switch (method) {
|
||||||
|
case 'cash':
|
||||||
|
return 'Espèces';
|
||||||
|
case 'card':
|
||||||
|
return 'Carte bancaire';
|
||||||
|
case 'mobile':
|
||||||
|
return 'Paiement mobile';
|
||||||
|
default:
|
||||||
|
return 'Non spécifié';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ dependencies:
|
|||||||
printing: ^5.11.1
|
printing: ^5.11.1
|
||||||
path_provider: ^2.1.1
|
path_provider: ^2.1.1
|
||||||
share_plus: ^7.2.1
|
share_plus: ^7.2.1
|
||||||
|
permission_handler: ^11.1.0
|
||||||
|
|
||||||
# Dépendances de développement/test
|
# Dépendances de développement/test
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user