diff --git a/req.xlsx b/req.xlsx index e0264ed..1810bcc 100644 Binary files a/req.xlsx and b/req.xlsx differ diff --git a/src-tauri/src/data_processor.rs b/src-tauri/src/data_processor.rs index ccf4c77..dcd2f75 100644 --- a/src-tauri/src/data_processor.rs +++ b/src-tauri/src/data_processor.rs @@ -3,10 +3,11 @@ use chrono::Datelike; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct MonthlyRecord { pub month: u32, - pub month_name: String, // e.g., "9月" + pub teacher: Option, + pub month_name: String, // e.g., "9月(老师名)" pub count: i32, pub price: f64, pub total: f64, @@ -37,22 +38,32 @@ pub fn process_data(classes: Vec) -> Vec { let mut student_reports = Vec::new(); for record in class_data.records { - let mut month_counts: HashMap = HashMap::new(); + // Group by (Month, Teacher) + let mut month_teacher_counts: HashMap<(u32, Option), i32> = HashMap::new(); - for (date, _) in record.attendance { + for (date, teacher, _) in record.attendance { 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 months: Vec = month_counts.keys().cloned().collect(); - months.sort(); + let mut keys: Vec<(u32, Option)> = month_teacher_counts.keys().cloned().collect(); + // 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 { month, - month_name: format!("{}月", month), + teacher, + month_name, count, price, total: count as f64 * price, diff --git a/src-tauri/src/excel_reader.rs b/src-tauri/src/excel_reader.rs index 73e4d53..104afb8 100644 --- a/src-tauri/src/excel_reader.rs +++ b/src-tauri/src/excel_reader.rs @@ -16,8 +16,8 @@ pub struct Student { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AttendanceRecord { pub student: Student, - // Date and attendance count (1.0 for present) - pub attendance: Vec<(NaiveDate, f64)>, + // Date, Teacher Name, and attendance count (1.0 for present) + pub attendance: Vec<(NaiveDate, Option, f64)>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -67,6 +67,30 @@ pub fn read_attendance_file>(path: P) -> Result> { 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) // Row 1: Title, Row 2: Headers, Row 3: Weekdays, Row 4: Data for row in range.rows().skip(3) { @@ -78,7 +102,7 @@ pub fn read_attendance_file>(path: P) -> Result> { _ => continue, // Skip if no name }; - if name.is_empty() || name == "姓名" { + if name.is_empty() || name == "姓名" || name == "到班人数" || name.contains("授课教师") { continue; } @@ -110,7 +134,7 @@ pub fn read_attendance_file>(path: P) -> Result> { if is_present { if i < dates.len() { - attendance_entries.push((dates[i], 1.0)); + attendance_entries.push((dates[i], teacher_names[i].clone(), 1.0)); } } } diff --git a/src-tauri/src/excel_writer.rs b/src-tauri/src/excel_writer.rs index 1503398..db73de3 100644 --- a/src-tauri/src/excel_writer.rs +++ b/src-tauri/src/excel_writer.rs @@ -68,38 +68,37 @@ pub fn write_report>(path: P, reports: Vec) -> Resul worksheet.write_string_with_format(current_row, i as u16, *header, &main_header_format)?; } - // Collect all months - let mut all_months = std::collections::BTreeSet::new(); + // Collect all unique (month, teacher) pairs + let mut all_month_teachers = std::collections::BTreeSet::new(); for student in &report.students { 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 col_idx = 5; // Start after "姓名" - for month in &all_months { + for (month, teacher) in &all_month_teachers { // 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)?; - month_col_map.insert(*month, col_idx); + month_col_map.insert((*month, teacher.clone()), col_idx); col_idx += 3; } current_row += 1; // 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; - 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 + 1, "单价", &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>(path: P, reports: Vec) -> Resul worksheet.write_string_with_format(current_row, 4, &student.student.name, &data_format)?; 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 + 1, record.price, &data_format)?; @@ -148,14 +147,8 @@ pub fn write_report>(path: P, reports: Vec) -> Resul } // Add Total Row - // Sample Row 162: [None, ..., =SUM(...)] - // We need to sum the columns. - // 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) { + for (month, teacher) in &all_month_teachers { + if let Some(&start_col) = month_col_map.get(&(*month, teacher.clone())) { // Sum Count (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); diff --git a/test_output.xlsx b/test_output.xlsx index 88bfcb0..6a7938f 100644 Binary files a/test_output.xlsx and b/test_output.xlsx differ