feat : update name grouping

This commit is contained in:
2025-12-03 11:07:32 +08:00
parent 067e5b0462
commit eb6f2b18f7
5 changed files with 65 additions and 37 deletions

BIN
req.xlsx

Binary file not shown.

View File

@@ -3,10 +3,11 @@ use chrono::Datelike;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MonthlyRecord { pub struct MonthlyRecord {
pub month: u32, pub month: u32,
pub month_name: String, // e.g., "9月" pub teacher: Option<String>,
pub month_name: String, // e.g., "9月(老师名)"
pub count: i32, pub count: i32,
pub price: f64, pub price: f64,
pub total: f64, pub total: f64,
@@ -37,22 +38,32 @@ pub fn process_data(classes: Vec<ClassData>) -> Vec<ClassReport> {
let mut student_reports = Vec::new(); let mut student_reports = Vec::new();
for record in class_data.records { for record in class_data.records {
let mut month_counts: HashMap<u32, i32> = HashMap::new(); // Group by (Month, Teacher)
let mut month_teacher_counts: HashMap<(u32, Option<String>), i32> = HashMap::new();
for (date, _) in record.attendance { for (date, teacher, _) in record.attendance {
let month = date.month(); let month = date.month();
*month_counts.entry(month).or_insert(0) += 1; *month_teacher_counts.entry((month, teacher)).or_insert(0) += 1;
} }
let mut monthly_records = Vec::new(); let mut monthly_records = Vec::new();
let mut months: Vec<u32> = month_counts.keys().cloned().collect(); let mut keys: Vec<(u32, Option<String>)> = month_teacher_counts.keys().cloned().collect();
months.sort(); // Sort by month then teacher
keys.sort();
for (month, teacher) in keys {
let count = month_teacher_counts[&(month, teacher.clone())];
let month_name = if let Some(t) = &teacher {
format!("{}月({}", month, t)
} else {
format!("{}", month)
};
for month in months {
let count = month_counts[&month];
monthly_records.push(MonthlyRecord { monthly_records.push(MonthlyRecord {
month, month,
month_name: format!("{}月", month), teacher,
month_name,
count, count,
price, price,
total: count as f64 * price, total: count as f64 * price,

View File

@@ -16,8 +16,8 @@ pub struct Student {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttendanceRecord { pub struct AttendanceRecord {
pub student: Student, pub student: Student,
// Date and attendance count (1.0 for present) // Date, Teacher Name, and attendance count (1.0 for present)
pub attendance: Vec<(NaiveDate, f64)>, pub attendance: Vec<(NaiveDate, Option<String>, f64)>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -67,6 +67,30 @@ pub fn read_attendance_file<P: AsRef<Path>>(path: P) -> Result<Vec<ClassData>> {
continue; continue;
} }
// Find Teacher Row
// Scan all rows to find "授课教师确认签名"
let mut teacher_names = vec![None; dates.len()];
for row in range.rows() {
if let Some(first_cell) = row.first() {
if let Data::String(s) = first_cell {
if s.contains("授课教师确认签名") {
// Found teacher row
for (i, &col_idx) in date_col_indices.iter().enumerate() {
if col_idx < row.len() {
if let Data::String(name) = &row[col_idx] {
let trimmed = name.trim();
if !trimmed.is_empty() {
teacher_names[i] = Some(trimmed.to_string());
}
}
}
}
break;
}
}
}
}
// Iterate over data rows (starting from row 4, index 3) // Iterate over data rows (starting from row 4, index 3)
// Row 1: Title, Row 2: Headers, Row 3: Weekdays, Row 4: Data // Row 1: Title, Row 2: Headers, Row 3: Weekdays, Row 4: Data
for row in range.rows().skip(3) { for row in range.rows().skip(3) {
@@ -78,7 +102,7 @@ pub fn read_attendance_file<P: AsRef<Path>>(path: P) -> Result<Vec<ClassData>> {
_ => continue, // Skip if no name _ => continue, // Skip if no name
}; };
if name.is_empty() || name == "姓名" { if name.is_empty() || name == "姓名" || name == "到班人数" || name.contains("授课教师") {
continue; continue;
} }
@@ -110,7 +134,7 @@ pub fn read_attendance_file<P: AsRef<Path>>(path: P) -> Result<Vec<ClassData>> {
if is_present { if is_present {
if i < dates.len() { if i < dates.len() {
attendance_entries.push((dates[i], 1.0)); attendance_entries.push((dates[i], teacher_names[i].clone(), 1.0));
} }
} }
} }

View File

@@ -68,38 +68,37 @@ pub fn write_report<P: AsRef<Path>>(path: P, reports: Vec<ClassReport>) -> Resul
worksheet.write_string_with_format(current_row, i as u16, *header, &main_header_format)?; worksheet.write_string_with_format(current_row, i as u16, *header, &main_header_format)?;
} }
// Collect all months // Collect all unique (month, teacher) pairs
let mut all_months = std::collections::BTreeSet::new(); let mut all_month_teachers = std::collections::BTreeSet::new();
for student in &report.students { for student in &report.students {
for record in &student.monthly_records { for record in &student.monthly_records {
all_months.insert(record.month); all_month_teachers.insert((record.month, record.teacher.clone()));
} }
} }
// Map month to start column index // Map (month, teacher) to start column index
let mut month_col_map = std::collections::HashMap::new(); let mut month_col_map = std::collections::HashMap::new();
let mut col_idx = 5; // Start after "姓名" let mut col_idx = 5; // Start after "姓名"
for month in &all_months { for (month, teacher) in &all_month_teachers {
// Write Month Header (merged 3 cells) // Write Month Header (merged 3 cells)
let month_str = format!("{}", month); let month_str = if let Some(t) = teacher {
format!("{}月({}", month, t)
} else {
format!("{}", month)
};
worksheet.merge_range(current_row, col_idx, current_row, col_idx + 2, &month_str, &main_header_format)?; worksheet.merge_range(current_row, col_idx, current_row, col_idx + 2, &month_str, &main_header_format)?;
month_col_map.insert(*month, col_idx); month_col_map.insert((*month, teacher.clone()), col_idx);
col_idx += 3; col_idx += 3;
} }
current_row += 1; current_row += 1;
// Write Sub-headers (次数, 单价, 费用合计) // Write Sub-headers (次数, 单价, 费用合计)
// Columns A-E are empty in this row in the sample?
// Sample Row 149: [None, None, None, None, None, '次数', ...]
// So we leave A-E empty or apply a default format?
// Let's apply data format to keep borders consistent if needed, or just leave blank.
// Sample shows blank.
let mut sub_col_idx = 5; let mut sub_col_idx = 5;
for _ in &all_months { for _ in &all_month_teachers {
worksheet.write_string_with_format(current_row, sub_col_idx, "次数", &sub_header_format)?; worksheet.write_string_with_format(current_row, sub_col_idx, "次数", &sub_header_format)?;
worksheet.write_string_with_format(current_row, sub_col_idx + 1, "单价", &sub_header_format)?; worksheet.write_string_with_format(current_row, sub_col_idx + 1, "单价", &sub_header_format)?;
worksheet.write_string_with_format(current_row, sub_col_idx + 2, "费用合计", &sub_header_format)?; worksheet.write_string_with_format(current_row, sub_col_idx + 2, "费用合计", &sub_header_format)?;
@@ -133,7 +132,7 @@ pub fn write_report<P: AsRef<Path>>(path: P, reports: Vec<ClassReport>) -> Resul
worksheet.write_string_with_format(current_row, 4, &student.student.name, &data_format)?; worksheet.write_string_with_format(current_row, 4, &student.student.name, &data_format)?;
for record in &student.monthly_records { for record in &student.monthly_records {
if let Some(&start_col) = month_col_map.get(&record.month) { if let Some(&start_col) = month_col_map.get(&(record.month, record.teacher.clone())) {
worksheet.write_number_with_format(current_row, start_col, record.count as f64, &data_format)?; worksheet.write_number_with_format(current_row, start_col, record.count as f64, &data_format)?;
worksheet.write_number_with_format(current_row, start_col + 1, record.price, &data_format)?; worksheet.write_number_with_format(current_row, start_col + 1, record.price, &data_format)?;
@@ -148,14 +147,8 @@ pub fn write_report<P: AsRef<Path>>(path: P, reports: Vec<ClassReport>) -> Resul
} }
// Add Total Row // Add Total Row
// Sample Row 162: [None, ..., =SUM(...)] for (month, teacher) in &all_month_teachers {
// We need to sum the columns. if let Some(&start_col) = month_col_map.get(&(*month, teacher.clone())) {
// Let's add a "Total" row.
// Columns F, H, I, K, etc. (Count and Total Cost)
// We need to iterate through all month columns again
for month in &all_months {
if let Some(&start_col) = month_col_map.get(month) {
// Sum Count (start_col) // Sum Count (start_col)
let start_cell = rust_xlsxwriter::utility::row_col_to_cell(current_row - report.students.len() as u32, start_col); let start_cell = rust_xlsxwriter::utility::row_col_to_cell(current_row - report.students.len() as u32, start_col);
let end_cell = rust_xlsxwriter::utility::row_col_to_cell(current_row - 1, start_col); let end_cell = rust_xlsxwriter::utility::row_col_to_cell(current_row - 1, start_col);

Binary file not shown.