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:
58
src/main.rs
58
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<ProductSummary>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CardData {
|
||||
card_number: String,
|
||||
@@ -34,7 +50,7 @@ struct FormattedTransaction {
|
||||
struct PreparedCustomer {
|
||||
customer_number: String,
|
||||
cards: Vec<CardData>,
|
||||
grand_total: String,
|
||||
summary: Summary,
|
||||
}
|
||||
|
||||
impl PreparedCustomer {
|
||||
@@ -78,10 +94,48 @@ impl PreparedCustomer {
|
||||
.map(|c| c.total_amount.parse::<f64>().unwrap())
|
||||
.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 {
|
||||
customer_number: customer.customer_number,
|
||||
cards,
|
||||
grand_total: fmt(grand_total),
|
||||
summary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
</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 %}
|
||||
<div class="card-section">
|
||||
<div class="card-header">
|
||||
@@ -154,7 +218,7 @@
|
||||
{% endfor %}
|
||||
|
||||
<div class="grand-total">
|
||||
Totalsumma:<span>{{ customer.grand_total }} Kr</span>
|
||||
Totalsumma:<span>{{ customer.summary.grand_total }} Kr</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user