You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

414 lines
12 KiB

8 months ago
// services/platform_print_service.dart
8 months ago
import 'dart:io';
import 'dart:typed_data';
8 months ago
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:itrimobe/models/command_detail.dart';
8 months ago
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
8 months ago
import 'package:permission_handler/permission_handler.dart';
8 months ago
import '../models/command_detail.dart';
8 months ago
class PlatformPrintService {
// 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({
8 months ago
required CommandeDetail commande,
required String paymentMethod,
}) async {
final pdf = pw.Document();
8 months ago
// 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;
8 months ago
final restaurantInfo = {
8 months ago
'nom': 'RESTAURANT ITRIMOBE',
'adresse': 'Moramanga, Antananarivo',
'ville': 'Madagascar',
'contact': '261348415301',
'email': '[email protected]',
8 months ago
};
final factureNumber =
8 months ago
'T${DateTime.now().millisecondsSinceEpoch.toString().substring(8)}';
8 months ago
final dateTime = DateTime.now();
pdf.addPage(
pw.Page(
8 months ago
pageFormat: ticket58mmFormat,
8 months ago
build: (pw.Context context) {
return pw.Column(
8 months ago
crossAxisAlignment: pw.CrossAxisAlignment.center,
8 months ago
children: [
8 months ago
// En-tête Restaurant (centré et compact)
pw.Text(
restaurantInfo['nom']!,
style: pw.TextStyle(
fontSize: titleSize,
fontWeight: pw.FontWeight.bold,
8 months ago
),
8 months ago
textAlign: pw.TextAlign.center,
8 months ago
),
8 months ago
pw.SizedBox(height: 1),
pw.Text(
restaurantInfo['adresse']!,
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,
8 months ago
),
8 months ago
pw.SizedBox(height: 2),
8 months ago
8 months ago
// Informations ticket
pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
8 months ago
children: [
8 months ago
pw.Text(
'Ticket: $factureNumber',
style: pw.TextStyle(
fontSize: bodySize,
fontWeight: pw.FontWeight.bold,
8 months ago
),
),
8 months ago
pw.Text(
'Via: ${commande.tablename ?? 'Table inconnue'}',
style: pw.TextStyle(fontSize: bodySize),
),
8 months ago
],
),
pw.SizedBox(height: 1),
pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text(
_formatDate(dateTime),
style: pw.TextStyle(fontSize: smallSize),
),
pw.Text(
_formatTime(dateTime),
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: 2),
// Articles (format très compact)
...commande.items
.map(
(item) => pw.Container(
margin: const pw.EdgeInsets.only(bottom: 1),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
// Nom du plat
pw.Text(
8 months ago
'${item.menuNom}',
8 months ago
style: pw.TextStyle(fontSize: bodySize),
maxLines: 2,
),
// Quantité, prix unitaire et total sur une ligne
pw.Row(
mainAxisAlignment:
pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text(
8 months ago
'${item.quantite}x ${item.prixUnitaire.toStringAsFixed(2)}MGA',
8 months ago
style: pw.TextStyle(fontSize: smallSize),
8 months ago
),
8 months ago
pw.Text(
8 months ago
'${(item.prixUnitaire * item.quantite).toStringAsFixed(2)}MGA',
8 months ago
style: pw.TextStyle(
fontSize: bodySize,
fontWeight: pw.FontWeight.bold,
),
8 months ago
),
8 months ago
],
),
],
),
),
)
.toList(),
pw.SizedBox(height: 2),
// Ligne de séparation
pw.Container(
width: double.infinity,
height: 0.5,
color: PdfColors.black,
8 months ago
),
8 months ago
pw.SizedBox(height: 2),
8 months ago
// Total
8 months ago
pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text(
'TOTAL',
style: pw.TextStyle(
fontSize: titleSize,
fontWeight: pw.FontWeight.bold,
),
8 months ago
),
8 months ago
pw.Text(
8 months ago
'${commande.totalTtc.toStringAsFixed(2)}MGA',
8 months ago
style: pw.TextStyle(
8 months ago
fontSize: titleSize,
8 months ago
fontWeight: pw.FontWeight.bold,
),
),
8 months ago
],
8 months ago
),
8 months ago
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),
8 months ago
// Message de remerciement
8 months ago
pw.Text(
'Merci de votre visite !',
style: pw.TextStyle(
fontSize: bodySize,
fontStyle: pw.FontStyle.italic,
8 months ago
),
8 months ago
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,
8 months ago
),
8 months ago
pw.SizedBox(height: 2),
8 months ago
],
);
},
),
);
return pdf.save();
}
8 months ago
// Imprimer ticket 58mm
static Future<bool> printTicket({
8 months ago
required CommandeDetail commande,
required String paymentMethod,
}) async {
try {
8 months ago
final hasPermission = await _checkPermissions();
if (!hasPermission) {
throw Exception('Permissions requises pour l\'impression');
}
final pdfData = await _generate58mmTicketPdf(
8 months ago
commande: commande,
paymentMethod: paymentMethod,
);
8 months ago
final fileName =
'Ticket_${commande.numeroCommande}_${DateTime.now().millisecondsSinceEpoch}';
8 months ago
await Printing.layoutPdf(
onLayout: (PdfPageFormat format) async => pdfData,
8 months ago
name: fileName,
format: ticket58mmFormat,
8 months ago
);
return true;
} catch (e) {
8 months ago
print('Erreur impression 58mm: $e');
8 months ago
return false;
}
}
8 months ago
// Sauvegarder ticket 58mm
static Future<bool> saveTicketPdf({
8 months ago
required CommandeDetail commande,
required String paymentMethod,
}) async {
try {
8 months ago
final hasPermission = await _checkPermissions();
if (!hasPermission) return false;
final pdfData = await _generate58mmTicketPdf(
8 months ago
commande: commande,
paymentMethod: paymentMethod,
);
8 months ago
Directory directory;
if (Platform.isAndroid) {
directory = Directory('/storage/emulated/0/Download');
if (!directory.existsSync()) {
directory =
await getExternalStorageDirectory() ??
await getApplicationDocumentsDirectory();
}
} else {
directory = await getApplicationDocumentsDirectory();
}
8 months ago
final fileName =
8 months ago
'Ticket_58mm_${commande.numeroCommande}_${DateTime.now().millisecondsSinceEpoch}.pdf';
8 months ago
final file = File('${directory.path}/$fileName');
await file.writeAsBytes(pdfData);
await Share.shareXFiles(
[XFile(file.path)],
8 months ago
subject: 'Ticket ${commande.numeroCommande}',
text: 'Ticket de caisse 58mm',
8 months ago
);
return true;
} catch (e) {
8 months ago
print('Erreur sauvegarde 58mm: $e');
8 months ago
return false;
}
}
8 months ago
// 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);
}
8 months ago
// Utilitaires de formatageπ
8 months ago
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é';
}
}
8 months ago
}