Files
gestion_prix_produit/lib/services/etiquette_pdf.dart
T
Mathew 4d936d2ee3 Version initiale de Gestion Prix
App Flutter de gestion de stock/prix pour tablette Android (100% local) :
scan code-barres (OpenFoodFacts), prix au kilo/litre, packs, inventaire,
prix d'achat + marge, filtres de tri, étiquettes PDF, design noir & blanc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 21:28:33 +02:00

107 lines
3.2 KiB
Dart

import 'dart:typed_data';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import '../models/produit.dart';
/// Génère une planche A4 d'étiquettes de prix (nom, prix TTC, prix HT, code-barres),
/// imprimable sur une imprimante classique (ex : Canon TS4550).
class EtiquettePdf {
/// Construit le document PDF pour les [produits] sélectionnés.
static Future<Uint8List> construire(List<Produit> produits) async {
final doc = pw.Document();
doc.addPage(
pw.MultiPage(
pageFormat: PdfPageFormat.a4,
margin: const pw.EdgeInsets.all(12),
build: (context) {
return [
pw.Wrap(
spacing: 6,
runSpacing: 6,
children: produits.map(_etiquette).toList(),
),
];
},
),
);
return doc.save();
}
static pw.Widget _etiquette(Produit p) {
// 3 colonnes sur A4 (~186mm utiles / 3 ≈ 62mm).
const largeur = 185.0; // points (~65mm)
const hauteur = 110.0; // points (~39mm)
return pw.Container(
width: largeur,
height: hauteur,
padding: const pw.EdgeInsets.all(8),
decoration: pw.BoxDecoration(
border: pw.Border.all(color: PdfColors.grey400, width: 0.7),
borderRadius: pw.BorderRadius.circular(6),
),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text(
p.titre,
maxLines: 2,
overflow: pw.TextOverflow.clip,
style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold),
),
pw.Container(
width: double.infinity,
alignment: pw.Alignment.center,
child: pw.Column(
mainAxisSize: pw.MainAxisSize.min,
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
pw.Text(
_prix(p.prix),
style: pw.TextStyle(
fontSize: 22, fontWeight: pw.FontWeight.bold),
),
if (p.prixMesure != null)
pw.Text(
_prixMesure(p),
style: const pw.TextStyle(
fontSize: 9, color: PdfColors.grey700),
),
],
),
),
_codeBarres(p.codeBarres),
],
),
);
}
static pw.Widget _codeBarres(String? code) {
if (code == null || code.isEmpty) {
return pw.SizedBox(height: 22);
}
final estEan13 = code.length == 13 && int.tryParse(code) != null;
return pw.BarcodeWidget(
barcode: estEan13 ? pw.Barcode.ean13() : pw.Barcode.code128(),
data: code,
width: 150,
height: 26,
drawText: true,
textStyle: const pw.TextStyle(fontSize: 6),
);
}
static String _prix(double v) => '${v.toStringAsFixed(2).replaceAll('.', ',')} EUR';
static String _prixMesure(Produit p) {
final m = p.prixMesure!;
final unite = m.suffixe == '/kg' ? 'kg' : 'L';
return 'soit ${m.valeur.toStringAsFixed(2).replaceAll('.', ',')} EUR / $unite';
}
}