feat : update name grouping
This commit is contained in:
@@ -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<String>,
|
||||
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<ClassData>) -> Vec<ClassReport> {
|
||||
let mut student_reports = Vec::new();
|
||||
|
||||
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();
|
||||
*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<u32> = month_counts.keys().cloned().collect();
|
||||
months.sort();
|
||||
let mut keys: Vec<(u32, Option<String>)> = 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,
|
||||
|
||||
@@ -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<String>, f64)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -67,6 +67,30 @@ pub fn read_attendance_file<P: AsRef<Path>>(path: P) -> Result<Vec<ClassData>> {
|
||||
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<P: AsRef<Path>>(path: P) -> Result<Vec<ClassData>> {
|
||||
_ => 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<P: AsRef<Path>>(path: P) -> Result<Vec<ClassData>> {
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
// 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<P: AsRef<Path>>(path: P, reports: Vec<ClassReport>) -> 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<P: AsRef<Path>>(path: P, reports: Vec<ClassReport>) -> 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);
|
||||
|
||||
Reference in New Issue
Block a user