From 4119ad70598b9a2e4912e6b3155a68139e669747 Mon Sep 17 00:00:00 2001 From: Jakob Date: Mon, 23 Mar 2026 10:57:35 +0100 Subject: [PATCH] Add summary section with product breakdown - Show total volume, total amount, and average price per product - Include grand total row - Products sorted alphabetically --- src/main.rs | 58 ++++++++++++++++++++++++++++++++++-- templates/customer.html | 66 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 57403fa..98da45f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use askama::Template; use chrono::Utc; +use std::collections::HashMap; use std::env; use std::fs; use std::path::Path; @@ -12,6 +13,21 @@ fn fmt(v: f64) -> String { format!("{:.2}", v) } +#[derive(Clone)] +struct ProductSummary { + name: String, + volume: String, + amount: String, + avg_price: String, +} + +#[derive(Clone)] +struct Summary { + total_volume: String, + grand_total: String, + products: Vec, +} + #[derive(Clone)] struct CardData { card_number: String, @@ -34,7 +50,7 @@ struct FormattedTransaction { struct PreparedCustomer { customer_number: String, cards: Vec, - grand_total: String, + summary: Summary, } impl PreparedCustomer { @@ -78,10 +94,48 @@ impl PreparedCustomer { .map(|c| c.total_amount.parse::().unwrap()) .sum(); + let mut product_totals: HashMap = HashMap::new(); + for card in &cards { + for tx in &card.transactions { + let volume: f64 = tx.volume.parse().unwrap(); + let amount: f64 = tx.amount.parse().unwrap(); + let entry = product_totals + .entry(tx.quality_name.clone()) + .or_insert((0.0, 0.0)); + entry.0 += volume; + entry.1 += amount; + } + } + + let mut products: Vec = product_totals + .into_iter() + .map(|(name, (volume, amount))| { + let avg_price = if volume > 0.0 { amount / volume } else { 0.0 }; + ProductSummary { + name, + volume: fmt(volume), + amount: fmt(amount), + avg_price: fmt(avg_price), + } + }) + .collect(); + products.sort_by(|a, b| a.name.cmp(&b.name)); + + let total_volume: f64 = products + .iter() + .map(|p| p.volume.parse::().unwrap()) + .sum(); + + let summary = Summary { + total_volume: fmt(total_volume), + grand_total: fmt(grand_total), + products, + }; + PreparedCustomer { customer_number: customer.customer_number, cards, - grand_total: fmt(grand_total), + summary, } } } diff --git a/templates/customer.html b/templates/customer.html index 471aaaa..35cc0cb 100644 --- a/templates/customer.html +++ b/templates/customer.html @@ -43,6 +43,40 @@ 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; + border-bottom: 1px solid #ccc; + padding-bottom: 5px; + } + .summary-table { + width: 100%; + border-collapse: collapse; + font-size: 11px; + } + .summary-table th, .summary-table td { + padding: 5px 8px; + text-align: left; + } + .summary-table th { + background: #e0e0e0; + font-weight: bold; + } + .summary-table td:not(:first-child), + .summary-table th:not(:first-child) { + text-align: right; + } + .grand-total-row td { + font-weight: bold; + border-top: 2px solid #333; + background: #f0f0f0; + } .card-section { margin-bottom: 15px; border: 1px solid #ccc; @@ -111,6 +145,36 @@ +
+

Sammanfattning

+ + + + + + + + + + + {% for product in customer.summary.products %} + + + + + + + {% endfor %} + + + + + + + +
ProduktVolym (L)BeloppSnittpris/L
{{ product.name }}{{ product.volume }}{{ product.amount }} Kr{{ product.avg_price }} Kr
Totalt{{ customer.summary.total_volume }}{{ customer.summary.grand_total }} Kr
+
+ {% for card in customer.cards %}
@@ -154,7 +218,7 @@ {% endfor %}
- Totalsumma:{{ customer.grand_total }} Kr + Totalsumma:{{ customer.summary.grand_total }} Kr