Add summary section with product breakdown

- Show total volume, total amount, and average price per product
- Include grand total row
- Products sorted alphabetically
This commit is contained in:
2026-03-23 10:57:35 +01:00
parent 809f5d2a58
commit 4119ad7059
2 changed files with 121 additions and 3 deletions

View File

@@ -1,5 +1,6 @@
use askama::Template; use askama::Template;
use chrono::Utc; use chrono::Utc;
use std::collections::HashMap;
use std::env; use std::env;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
@@ -12,6 +13,21 @@ fn fmt(v: f64) -> String {
format!("{:.2}", v) 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<ProductSummary>,
}
#[derive(Clone)] #[derive(Clone)]
struct CardData { struct CardData {
card_number: String, card_number: String,
@@ -34,7 +50,7 @@ struct FormattedTransaction {
struct PreparedCustomer { struct PreparedCustomer {
customer_number: String, customer_number: String,
cards: Vec<CardData>, cards: Vec<CardData>,
grand_total: String, summary: Summary,
} }
impl PreparedCustomer { impl PreparedCustomer {
@@ -78,10 +94,48 @@ impl PreparedCustomer {
.map(|c| c.total_amount.parse::<f64>().unwrap()) .map(|c| c.total_amount.parse::<f64>().unwrap())
.sum(); .sum();
let mut product_totals: HashMap<String, (f64, f64)> = 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<ProductSummary> = 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::<f64>().unwrap())
.sum();
let summary = Summary {
total_volume: fmt(total_volume),
grand_total: fmt(grand_total),
products,
};
PreparedCustomer { PreparedCustomer {
customer_number: customer.customer_number, customer_number: customer.customer_number,
cards, cards,
grand_total: fmt(grand_total), summary,
} }
} }
} }

View File

@@ -43,6 +43,40 @@
font-size: 11px; font-size: 11px;
color: #666; 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 { .card-section {
margin-bottom: 15px; margin-bottom: 15px;
border: 1px solid #ccc; border: 1px solid #ccc;
@@ -111,6 +145,36 @@
</div> </div>
</div> </div>
<div class="summary">
<h2>Sammanfattning</h2>
<table class="summary-table">
<thead>
<tr>
<th>Produkt</th>
<th>Volym (L)</th>
<th>Belopp</th>
<th>Snittpris/L</th>
</tr>
</thead>
<tbody>
{% for product in customer.summary.products %}
<tr>
<td>{{ product.name }}</td>
<td>{{ product.volume }}</td>
<td>{{ product.amount }} Kr</td>
<td>{{ product.avg_price }} Kr</td>
</tr>
{% endfor %}
<tr class="grand-total-row">
<td>Totalt</td>
<td>{{ customer.summary.total_volume }}</td>
<td>{{ customer.summary.grand_total }} Kr</td>
<td></td>
</tr>
</tbody>
</table>
</div>
{% for card in customer.cards %} {% for card in customer.cards %}
<div class="card-section"> <div class="card-section">
<div class="card-header"> <div class="card-header">
@@ -154,7 +218,7 @@
{% endfor %} {% endfor %}
<div class="grand-total"> <div class="grand-total">
Totalsumma:<span>{{ customer.grand_total }} Kr</span> Totalsumma:<span>{{ customer.summary.grand_total }} Kr</span>
</div> </div>
</body> </body>
</html> </html>