Commit 2030a58d60aca87a461237747e803e9bf328ddb5

Authored by “wangming”
1 parent 0fc6b403

优化考勤数据导入接口:支持按列名匹配、多种年份月份格式解析

- 修改ImportAttendanceDataFromExcel方法,支持按列名动态匹配字段
- 支持多种年份格式:纯数字、日期格式、中文格式(如:2025年)
- 支持多种月份格式:纯数字、日期格式、中文格式(如:11月)
- 处理DateTime、double、string等多种数据类型
- 改进错误提示信息,显示实际读取的值和类型
- 修复编译错误:添加System.Text.RegularExpressions命名空间
netcore/src/Modularity/Extend/NCC.Extend/LqAttendanceSummaryService.cs
... ... @@ -2,6 +2,7 @@ using System;
2 2 using System.Collections.Generic;
3 3 using System.IO;
4 4 using System.Linq;
  5 +using System.Text.RegularExpressions;
5 6 using System.Threading.Tasks;
6 7 using Mapster;
7 8 using Microsoft.AspNetCore.Http;
... ... @@ -110,20 +111,87 @@ namespace NCC.Extend
110 111 throw NCCException.Oh("Excel文件中没有数据行");
111 112 }
112 113  
  114 + // 根据列名查找列索引(支持多种可能的列名)
  115 + int GetColumnIndex(string[] possibleNames)
  116 + {
  117 + foreach (var name in possibleNames)
  118 + {
  119 + for (int col = 0; col < dataTable.Columns.Count; col++)
  120 + {
  121 + var columnName = dataTable.Columns[col].ColumnName?.Trim();
  122 + if (columnName == name || columnName?.Contains(name) == true)
  123 + {
  124 + return col;
  125 + }
  126 + }
  127 + }
  128 + return -1;
  129 + }
  130 +
  131 + var nameColIndex = GetColumnIndex(new[] { "员工姓名", "姓名", "员工", "姓名" });
  132 + var phoneColIndex = GetColumnIndex(new[] { "员工电话", "电话", "手机", "手机号", "联系电话" });
  133 + var yearColIndex = GetColumnIndex(new[] { "年份", "年" });
  134 + var monthColIndex = GetColumnIndex(new[] { "月份", "月" });
  135 + var workDaysColIndex = GetColumnIndex(new[] { "出勤天数", "出勤", "工作天数", "上班天数" });
  136 + var leaveDaysColIndex = GetColumnIndex(new[] { "请假天数", "请假", "事假天数" });
  137 + var restDaysColIndex = GetColumnIndex(new[] { "休息天数", "休息", "休假天数" });
  138 + var remarkColIndex = GetColumnIndex(new[] { "备注", "说明", "备注信息" });
  139 +
  140 + // 验证必需的列是否存在
  141 + if (nameColIndex == -1) throw NCCException.Oh("Excel文件中未找到'员工姓名'列");
  142 + if (phoneColIndex == -1) throw NCCException.Oh("Excel文件中未找到'员工电话'列");
  143 + if (yearColIndex == -1) throw NCCException.Oh("Excel文件中未找到'年份'列");
  144 + if (monthColIndex == -1) throw NCCException.Oh("Excel文件中未找到'月份'列");
  145 +
113 146 // 从第1行开始读取数据(跳过标题行)
114 147 for (int i = 1; i < dataTable.Rows.Count; i++)
115 148 {
116 149 try
117 150 {
118 151 var row = dataTable.Rows[i];
119   - var employeeName = row[0]?.ToString()?.Trim();
120   - var employeePhone = row[1]?.ToString()?.Trim();
121   - var yearText = row[2]?.ToString()?.Trim();
122   - var monthText = row[3]?.ToString()?.Trim();
123   - var workDaysText = row[4]?.ToString()?.Trim();
124   - var leaveDaysText = row[5]?.ToString()?.Trim();
125   - var restDaysText = row[6]?.ToString()?.Trim();
126   - var remark = row[7]?.ToString()?.Trim();
  152 + var employeeName = row[nameColIndex]?.ToString()?.Trim();
  153 + var employeePhone = row[phoneColIndex]?.ToString()?.Trim();
  154 +
  155 + // 处理年份:可能是数字、日期或字符串
  156 + string yearText = null;
  157 + if (yearColIndex >= 0 && row[yearColIndex] != null && row[yearColIndex] != DBNull.Value)
  158 + {
  159 + if (row[yearColIndex] is DateTime dt)
  160 + {
  161 + yearText = dt.Year.ToString();
  162 + }
  163 + else if (row[yearColIndex] is double d)
  164 + {
  165 + yearText = ((int)d).ToString();
  166 + }
  167 + else
  168 + {
  169 + yearText = row[yearColIndex].ToString()?.Trim();
  170 + }
  171 + }
  172 +
  173 + // 处理月份:可能是数字、日期或字符串
  174 + string monthText = null;
  175 + if (monthColIndex >= 0 && row[monthColIndex] != null && row[monthColIndex] != DBNull.Value)
  176 + {
  177 + if (row[monthColIndex] is DateTime dt)
  178 + {
  179 + monthText = dt.Month.ToString();
  180 + }
  181 + else if (row[monthColIndex] is double d)
  182 + {
  183 + monthText = ((int)d).ToString();
  184 + }
  185 + else
  186 + {
  187 + monthText = row[monthColIndex].ToString()?.Trim();
  188 + }
  189 + }
  190 +
  191 + var workDaysText = workDaysColIndex >= 0 ? row[workDaysColIndex]?.ToString()?.Trim() : null;
  192 + var leaveDaysText = leaveDaysColIndex >= 0 ? row[leaveDaysColIndex]?.ToString()?.Trim() : null;
  193 + var restDaysText = restDaysColIndex >= 0 ? row[restDaysColIndex]?.ToString()?.Trim() : null;
  194 + var remark = remarkColIndex >= 0 ? row[remarkColIndex]?.ToString()?.Trim() : null;
127 195  
128 196 // 跳过空行
129 197 if (string.IsNullOrEmpty(employeeName) && string.IsNullOrEmpty(employeePhone))
... ... @@ -142,13 +210,100 @@ namespace NCC.Extend
142 210 }
143 211  
144 212 // 解析数值字段
145   - if (!int.TryParse(yearText, out int year))
  213 + int year = 0;
  214 + int month = 0;
  215 +
  216 + // 解析年份:支持纯数字、日期格式、中文格式
  217 + if (string.IsNullOrEmpty(yearText))
  218 + {
  219 + throw new Exception($"第{i + 1}行:年份不能为空");
  220 + }
  221 +
  222 + // 尝试直接解析为整数
  223 + if (int.TryParse(yearText, out year))
  224 + {
  225 + // 成功解析
  226 + }
  227 + // 尝试解析日期格式(如:2025-11-01 或 2025/11/01)
  228 + else if (DateTime.TryParse(yearText, out DateTime yearDate))
  229 + {
  230 + year = yearDate.Year;
  231 + }
  232 + // 尝试解析中文格式(如:2025年)
  233 + else if (yearText.Contains("年"))
  234 + {
  235 + var yearMatch = Regex.Match(yearText, @"(\d{4})年");
  236 + if (yearMatch.Success && int.TryParse(yearMatch.Groups[1].Value, out year))
  237 + {
  238 + // 成功解析
  239 + }
  240 + else
  241 + {
  242 + throw new Exception($"第{i + 1}行:年份格式错误,无法解析:{yearText}");
  243 + }
  244 + }
  245 + else
  246 + {
  247 + throw new Exception($"第{i + 1}行:年份格式错误,无法解析。实际值:\"{yearText}\"(类型:{yearText?.GetType().Name})");
  248 + }
  249 +
  250 + // 验证年份范围
  251 + if (year < 2020 || year > 2030)
  252 + {
  253 + throw new Exception($"第{i + 1}行:年份必须在2020-2030之间");
  254 + }
  255 +
  256 + // 解析月份:支持纯数字、日期格式、中文格式
  257 + if (string.IsNullOrEmpty(monthText))
  258 + {
  259 + throw new Exception($"第{i + 1}行:月份不能为空");
  260 + }
  261 +
  262 + // 尝试直接解析为整数
  263 + if (int.TryParse(monthText, out month))
  264 + {
  265 + // 成功解析
  266 + }
  267 + // 尝试解析日期格式(如:2025-11-01 或 2025/11/01)
  268 + else if (DateTime.TryParse(monthText, out DateTime monthDate))
  269 + {
  270 + month = monthDate.Month;
  271 + }
  272 + // 尝试解析中文格式(如:11月)
  273 + else if (monthText.Contains("月"))
  274 + {
  275 + var monthMatch = Regex.Match(monthText, @"(\d{1,2})月");
  276 + if (monthMatch.Success && int.TryParse(monthMatch.Groups[1].Value, out month))
  277 + {
  278 + // 成功解析
  279 + }
  280 + else
  281 + {
  282 + throw new Exception($"第{i + 1}行:月份格式错误,无法解析:{monthText}");
  283 + }
  284 + }
  285 + // 尝试解析"年-月"格式(如:2025-11)
  286 + else if (monthText.Contains("-") || monthText.Contains("/"))
  287 + {
  288 + var parts = monthText.Split(new[] { "-", "/" }, StringSplitOptions.RemoveEmptyEntries);
  289 + if (parts.Length >= 2 && int.TryParse(parts[1], out month))
  290 + {
  291 + // 成功解析
  292 + }
  293 + else
  294 + {
  295 + throw new Exception($"第{i + 1}行:月份格式错误,无法解析:{monthText}");
  296 + }
  297 + }
  298 + else
146 299 {
147   - throw new Exception($"第{i + 1}行:年份格式错误");
  300 + throw new Exception($"第{i + 1}行:月份格式错误,无法解析。实际值:\"{monthText}\"(类型:{monthText?.GetType().Name})");
148 301 }
149   - if (!int.TryParse(monthText, out int month))
  302 +
  303 + // 验证月份范围
  304 + if (month < 1 || month > 12)
150 305 {
151   - throw new Exception($"第{i + 1}行:月份格式错误");
  306 + throw new Exception($"第{i + 1}行:月份必须在1-12之间");
152 307 }
153 308  
154 309 decimal.TryParse(workDaysText, out decimal workDays);
... ...