Implement multi-batch invoice generation
- Add read_csv_file_by_batch() to group transactions by batch_number - Modify generate command to create separate directories per batch - Each batch directory contains index.html and customer_XXX.html files - Add 3 unit tests for batch grouping logic - Fixes issue #1: CSV with multiple batches now generates separate invoices Closes: #1
This commit is contained in:
+67
-56
@@ -8,7 +8,7 @@ use chrono::{NaiveDateTime, Utc};
|
||||
use config::{Config, Env};
|
||||
use csv::ReaderBuilder;
|
||||
use db::{create_pool, Repository};
|
||||
use invoice_generator::{group_by_customer, read_csv_file, Customer};
|
||||
use invoice_generator::{group_by_customer, read_csv_file_by_batch, Customer};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
@@ -330,72 +330,83 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let temp_cleaned_path =
|
||||
base_output_dir.join(format!("{}.temp.csv", filename.trim_end_matches(".txt")));
|
||||
let batch_number = clean_csv_file(input_path, &temp_cleaned_path)?;
|
||||
clean_csv_file(input_path, &temp_cleaned_path)?;
|
||||
|
||||
let output_dir = base_output_dir.join(&batch_number);
|
||||
fs::create_dir_all(&output_dir)?;
|
||||
|
||||
fs::copy(input_path, output_dir.join(format!("{}.txt", batch_number)))?;
|
||||
fs::rename(
|
||||
&temp_cleaned_path,
|
||||
output_dir.join(format!("{}.csv", batch_number)),
|
||||
)?;
|
||||
|
||||
println!(
|
||||
"Converted {} transactions",
|
||||
fs::read_to_string(output_dir.join(format!("{}.csv", batch_number)))?
|
||||
.lines()
|
||||
.count()
|
||||
- 1
|
||||
);
|
||||
|
||||
let batch = read_csv_file(&output_dir.join(format!("{}.csv", batch_number)))?;
|
||||
println!("Loaded {} transactions", batch.transactions.len());
|
||||
|
||||
let first_date = batch.transactions.first().map(|t| t.date).unwrap();
|
||||
let last_date = batch.transactions.last().map(|t| t.date).unwrap();
|
||||
let period = format!(
|
||||
"{} - {}",
|
||||
first_date.format("%Y-%m-%d"),
|
||||
last_date.format("%Y-%m-%d")
|
||||
);
|
||||
|
||||
let customers = group_by_customer(&[batch]);
|
||||
|
||||
let index_customers: Vec<(String, usize)> = customers
|
||||
.iter()
|
||||
.map(|(num, c)| (num.clone(), c.cards.len()))
|
||||
.collect();
|
||||
|
||||
let html = IndexTemplate {
|
||||
customers: index_customers.clone(),
|
||||
period: period.clone(),
|
||||
}
|
||||
.render()
|
||||
.unwrap();
|
||||
fs::write(output_dir.join("index.html"), html)?;
|
||||
let mut batches = read_csv_file_by_batch(&temp_cleaned_path)?;
|
||||
println!("Found {} batches in CSV", batches.len());
|
||||
|
||||
let mut total_customers = 0usize;
|
||||
let generated_date = Utc::now().format("%Y-%m-%d %H:%M").to_string();
|
||||
|
||||
let customer_count = customers.len();
|
||||
for (customer_num, customer) in customers {
|
||||
let prepared = PreparedCustomer::from_customer(customer);
|
||||
let customer_html = CustomerTemplate {
|
||||
customer: prepared,
|
||||
batch_number: batch_number.clone(),
|
||||
let mut batch_numbers: Vec<_> = batches.keys().cloned().collect();
|
||||
batch_numbers.sort();
|
||||
|
||||
for batch_number in batch_numbers {
|
||||
let batch = batches.remove(&batch_number).unwrap();
|
||||
let output_dir = base_output_dir.join(&batch_number);
|
||||
|
||||
fs::create_dir_all(&output_dir)?;
|
||||
|
||||
let csv_path = output_dir.join(format!("{}.csv", batch_number));
|
||||
let txt_path = output_dir.join(format!("{}.txt", batch_number));
|
||||
|
||||
fs::copy(&temp_cleaned_path, &csv_path)?;
|
||||
fs::copy(input_path, &txt_path)?;
|
||||
|
||||
println!(
|
||||
"Batch {}: {} transactions",
|
||||
batch_number,
|
||||
batch.transactions.len()
|
||||
);
|
||||
|
||||
let first_date = batch.transactions.first().map(|t| t.date).unwrap();
|
||||
let last_date = batch.transactions.last().map(|t| t.date).unwrap();
|
||||
let period = format!(
|
||||
"{} - {}",
|
||||
first_date.format("%Y-%m-%d"),
|
||||
last_date.format("%Y-%m-%d")
|
||||
);
|
||||
|
||||
let customers = group_by_customer(&[batch]);
|
||||
|
||||
let index_customers: Vec<(String, usize)> = customers
|
||||
.iter()
|
||||
.map(|(num, c)| (num.clone(), c.cards.len()))
|
||||
.collect();
|
||||
|
||||
let html = IndexTemplate {
|
||||
customers: index_customers.clone(),
|
||||
period: period.clone(),
|
||||
generated_date: generated_date.clone(),
|
||||
}
|
||||
.render()
|
||||
.unwrap();
|
||||
let filename = format!("customer_{}.html", customer_num);
|
||||
fs::write(output_dir.join(&filename), customer_html)?;
|
||||
println!("Generated {}", filename);
|
||||
fs::write(output_dir.join("index.html"), html)?;
|
||||
|
||||
for (customer_num, customer) in customers {
|
||||
let prepared = PreparedCustomer::from_customer(customer);
|
||||
let customer_html = CustomerTemplate {
|
||||
customer: prepared,
|
||||
batch_number: batch_number.clone(),
|
||||
period: period.clone(),
|
||||
generated_date: generated_date.clone(),
|
||||
}
|
||||
.render()
|
||||
.unwrap();
|
||||
let customer_filename = format!("customer_{}.html", customer_num);
|
||||
fs::write(output_dir.join(&customer_filename), customer_html)?;
|
||||
println!(" Generated {}", customer_filename);
|
||||
}
|
||||
|
||||
total_customers += index_customers.len();
|
||||
}
|
||||
|
||||
fs::remove_file(temp_cleaned_path)?;
|
||||
|
||||
println!(
|
||||
"\nGenerated {} customer invoices in {:?}",
|
||||
customer_count, output_dir
|
||||
"\nGenerated {} customer invoices across {} batches in {:?}",
|
||||
total_customers,
|
||||
batches.len(),
|
||||
base_output_dir
|
||||
);
|
||||
}
|
||||
"db" => {
|
||||
|
||||
Reference in New Issue
Block a user