feat : update name grouping
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
BIN
test_output.xlsx
BIN
test_output.xlsx
Binary file not shown.
Reference in New Issue
Block a user