Fix PDF generation with simplified HTML
Use simpler HTML structure compatible with printpdf renderer. Use inline styles and standard table elements for better rendering.
This commit is contained in:
255
src/pdf.rs
255
src/pdf.rs
@@ -14,145 +14,68 @@ pub fn generate_customer_pdf(
|
|||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let html = format!(
|
let html = format!(
|
||||||
r#"<!DOCTYPE html>
|
r#"<!DOCTYPE html>
|
||||||
<html lang="sv">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Faktura - Kund {}</title>
|
<style>
|
||||||
<style>
|
body {{ font-family: Arial; font-size: 10pt; margin: 20px; }}
|
||||||
@page {{
|
h1 {{ font-size: 14pt; margin: 0; }}
|
||||||
size: A4;
|
h2 {{ font-size: 11pt; margin: 15px 0 5px 0; border-bottom: 1px solid #000; }}
|
||||||
margin: 15mm;
|
table {{ width: 100%; border-collapse: collapse; font-size: 9pt; margin-top: 5px; }}
|
||||||
}}
|
th {{ background-color: #ddd; text-align: left; padding: 3px; border: 1px solid #000; }}
|
||||||
* {{ box-sizing: border-box; }}
|
td {{ padding: 3px; border: 1px solid #000; }}
|
||||||
body {{
|
.r {{ text-align: right; }}
|
||||||
font-family: Arial, sans-serif;
|
.b {{ font-weight: bold; border-top: 2px solid #000; background-color: #eee; }}
|
||||||
font-size: 12px;
|
.header {{ border-bottom: 2px solid #000; padding-bottom: 10px; margin-bottom: 15px; }}
|
||||||
margin: 0;
|
.meta {{ font-size: 8pt; color: #666; }}
|
||||||
padding: 20px;
|
.section {{ margin-bottom: 10px; border: 1px solid #ccc; }}
|
||||||
}}
|
.section-header {{ background-color: #eee; padding: 5px; font-weight: bold; }}
|
||||||
.header {{
|
.section-meta {{ padding: 3px 5px; font-size: 8pt; background-color: #f9f9f9; }}
|
||||||
display: flex;
|
.grand {{ background-color: #000; color: #fff; padding: 8px; font-size: 12pt; text-align: right; font-weight: bold; margin-top: 15px; }}
|
||||||
justify-content: space-between;
|
</style>
|
||||||
border-bottom: 2px solid #333;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}}
|
|
||||||
.header h1 {{
|
|
||||||
margin: 0;
|
|
||||||
font-size: 20px;
|
|
||||||
}}
|
|
||||||
.header .meta {{
|
|
||||||
text-align: right;
|
|
||||||
font-size: 11px;
|
|
||||||
color: #666;
|
|
||||||
}}
|
|
||||||
.summary {{
|
|
||||||
background: #f5f5f5;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
padding: 12px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}}
|
|
||||||
.summary h2 {{
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
}}
|
|
||||||
table {{
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: 11px;
|
|
||||||
}}
|
|
||||||
th, td {{
|
|
||||||
padding: 5px 8px;
|
|
||||||
text-align: left;
|
|
||||||
}}
|
|
||||||
th {{
|
|
||||||
background: #e0e0e0;
|
|
||||||
font-weight: bold;
|
|
||||||
}}
|
|
||||||
.grand-total-row td {{
|
|
||||||
font-weight: bold;
|
|
||||||
border-top: 2px solid #333;
|
|
||||||
background: #f0f0f0;
|
|
||||||
}}
|
|
||||||
.card-section {{
|
|
||||||
margin-bottom: 15px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}}
|
|
||||||
.card-header {{
|
|
||||||
background: #f0f0f0;
|
|
||||||
padding: 8px 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
}}
|
|
||||||
.card-summary {{
|
|
||||||
padding: 6px 12px;
|
|
||||||
font-size: 11px;
|
|
||||||
background: #fafafa;
|
|
||||||
}}
|
|
||||||
.grand-total {{
|
|
||||||
background: #333;
|
|
||||||
color: white;
|
|
||||||
padding: 12px 15px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: right;
|
|
||||||
}}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div>
|
<h1>Faktura - Kund {customer_num}</h1>
|
||||||
<h1>Faktura - Kund {}</h1>
|
<div class="meta">Period: {period} | Genererad: {gen_date}</div>
|
||||||
<div>Period: {}</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="meta">
|
|
||||||
<div>Genererad: {}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="summary">
|
<h2>Sammanfattning</h2>
|
||||||
<h2>Sammanfattning</h2>
|
<table>
|
||||||
<table>
|
<tr><th>Produkt</th><th class="r">Volym (L)</th><th class="r">Belopp</th><th class="r">Snittpris/L</th></tr>
|
||||||
<thead>
|
{products}
|
||||||
<tr>
|
<tr class="b"><td>Totalt</td><td class="r">{total_vol}</td><td class="r">{total_amt} Kr</td><td></td></tr>
|
||||||
<th>Produkt</th>
|
</table>
|
||||||
<th style="text-align:right">Volym (L)</th>
|
|
||||||
<th style="text-align:right">Belopp</th>
|
|
||||||
<th style="text-align:right">Snittpris/L</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{}
|
|
||||||
<tr class="grand-total-row">
|
|
||||||
<td>Totalt</td>
|
|
||||||
<td style="text-align:right">{}</td>
|
|
||||||
<td style="text-align:right">{} Kr</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{}
|
{cards}
|
||||||
|
|
||||||
|
<div class="grand">Totalsumma: {grand_total} Kr</div>
|
||||||
</body>
|
</body>
|
||||||
</html>"#,
|
</html>"#,
|
||||||
customer.customer_number,
|
customer_num = customer.customer_number,
|
||||||
customer.customer_number,
|
period = period,
|
||||||
period,
|
gen_date = generated_date,
|
||||||
generated_date,
|
products = generate_products_table(&customer.summary.products),
|
||||||
generate_product_rows(&customer.summary.products),
|
total_vol = customer.summary.total_volume,
|
||||||
customer.summary.total_volume,
|
total_amt = customer.summary.grand_total,
|
||||||
customer.summary.grand_total,
|
cards = generate_cards(&customer.cards),
|
||||||
generate_card_sections(&customer.cards),
|
grand_total = customer.summary.grand_total,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut warnings = Vec::new();
|
let mut warnings = Vec::new();
|
||||||
let doc = PdfDocument::from_html(
|
let doc = match PdfDocument::from_html(
|
||||||
&html,
|
&html,
|
||||||
&BTreeMap::new(),
|
&BTreeMap::new(),
|
||||||
&BTreeMap::new(),
|
&BTreeMap::new(),
|
||||||
&GeneratePdfOptions::default(),
|
&GeneratePdfOptions::default(),
|
||||||
&mut warnings,
|
&mut warnings,
|
||||||
)?;
|
) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("PDF generation warning: {}", e);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let file = File::create(output_path)?;
|
let file = File::create(output_path)?;
|
||||||
let mut writer = BufWriter::new(file);
|
let mut writer = BufWriter::new(file);
|
||||||
@@ -162,83 +85,49 @@ pub fn generate_customer_pdf(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_product_rows(products: &[crate::ProductSummary]) -> String {
|
fn generate_products_table(products: &[crate::ProductSummary]) -> String {
|
||||||
products
|
products
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| {
|
.map(|p| format!(
|
||||||
format!(
|
"<tr><td>{}</td><td class=\"r\">{}</td><td class=\"r\">{} Kr</td><td class=\"r\">{} Kr</td></tr>",
|
||||||
r#"<tr>
|
|
||||||
<td>{}</td>
|
|
||||||
<td style="text-align:right">{}</td>
|
|
||||||
<td style="text-align:right">{} Kr</td>
|
|
||||||
<td style="text-align:right">{} Kr</td>
|
|
||||||
</tr>"#,
|
|
||||||
p.name, p.volume, p.amount, p.avg_price
|
p.name, p.volume, p.amount, p.avg_price
|
||||||
)
|
))
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n ")
|
.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_card_sections(cards: &[crate::CardData]) -> String {
|
fn generate_cards(cards: &[crate::CardData]) -> String {
|
||||||
cards
|
cards
|
||||||
.iter()
|
.iter()
|
||||||
.map(|card| {
|
.map(|card| format!(
|
||||||
format!(
|
r#"<div class="section">
|
||||||
r#"<div class="card-section">
|
<div class="section-header">Kort: {} ({} transaktioner)</div>
|
||||||
<div class="card-header">Kort: {} | {} transaktioner</div>
|
<div class="section-meta">Summa: {} Kr | Volym: {} L</div>
|
||||||
<div class="card-summary">Summa: {} Kr | Volym: {} L</div>
|
<table>
|
||||||
<table>
|
<tr><th>Datum</th><th>Produkt</th><th class="r">Pris/L</th><th class="r">Volym</th><th class="r">Belopp</th><th>Kvitto</th></tr>
|
||||||
<thead>
|
{}
|
||||||
<tr>
|
<tr class="b"><td colspan="3">Kortsumma</td><td class="r">{}</td><td class="r">{}</td><td></td></tr>
|
||||||
<th>Datum</th>
|
</table>
|
||||||
<th>Produkt</th>
|
</div>"#,
|
||||||
<th style="text-align:right">Pris/L</th>
|
|
||||||
<th style="text-align:right">Volym</th>
|
|
||||||
<th style="text-align:right">Belopp</th>
|
|
||||||
<th>Kvitto</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{}
|
|
||||||
<tr style="font-weight:bold;background:#f9f9f9">
|
|
||||||
<td colspan="3">Kortsumma</td>
|
|
||||||
<td style="text-align:right">{}</td>
|
|
||||||
<td style="text-align:right">{}</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>"#,
|
|
||||||
card.card_number,
|
card.card_number,
|
||||||
card.transactions.len(),
|
card.transactions.len(),
|
||||||
card.total_amount,
|
card.total_amount,
|
||||||
card.total_volume,
|
card.total_volume,
|
||||||
generate_transaction_rows(&card.transactions),
|
generate_transactions(&card.transactions),
|
||||||
card.total_volume,
|
card.total_volume,
|
||||||
card.total_amount,
|
card.total_amount,
|
||||||
)
|
))
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n\n ")
|
.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_transaction_rows(transactions: &[crate::FormattedTransaction]) -> String {
|
fn generate_transactions(txs: &[crate::FormattedTransaction]) -> String {
|
||||||
transactions
|
txs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tx| {
|
.map(|tx| format!(
|
||||||
format!(
|
"<tr><td>{}</td><td>{}</td><td class=\"r\">{}</td><td class=\"r\">{}</td><td class=\"r\">{}</td><td>{}</td></tr>",
|
||||||
r#"<tr>
|
|
||||||
<td>{}</td>
|
|
||||||
<td>{}</td>
|
|
||||||
<td style="text-align:right">{}</td>
|
|
||||||
<td style="text-align:right">{}</td>
|
|
||||||
<td style="text-align:right">{}</td>
|
|
||||||
<td>{}</td>
|
|
||||||
</tr>"#,
|
|
||||||
tx.date, tx.quality_name, tx.price, tx.volume, tx.amount, tx.receipt
|
tx.date, tx.quality_name, tx.price, tx.volume, tx.amount, tx.receipt
|
||||||
)
|
))
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n ")
|
.join("\n")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user