diff --git a/AttendanceMachineFaceTemplateDAO.cs b/AttendanceMachineFaceTemplateDAO.cs
index 7c1798d..ef3187c 100644
--- a/AttendanceMachineFaceTemplateDAO.cs
+++ b/AttendanceMachineFaceTemplateDAO.cs
@@ -69,6 +69,7 @@ namespace HanvonF710XAttendanceService
using (var cmd = new MySqlCommand(query, connection))
{
+ cmd.CommandTimeout = 300;
for (int i = 0; i < chunk.Length; i++)
{
cmd.Parameters.AddWithValue(parameterNames[i], chunk[i]);
@@ -178,6 +179,89 @@ namespace HanvonF710XAttendanceService
return result.ToList();
}
+ ///
+ /// Active template serial numbers for employees in the given departments (HRMS employees table).
+ /// When department lookup is unavailable, returns an empty list and the caller should fall back.
+ ///
+ public List GetActiveSerialNumbersByDepartmentIds(
+ MySqlConnection connection,
+ IReadOnlyList departmentIds,
+ IReadOnlyList allowedMachineIds)
+ {
+ var result = new HashSet(StringComparer.OrdinalIgnoreCase);
+ if (departmentIds == null || departmentIds.Count == 0)
+ {
+ return new List();
+ }
+
+ var machineIds = (allowedMachineIds ?? Array.Empty())
+ .Where(id => !string.IsNullOrWhiteSpace(id))
+ .Select(id => id.Trim())
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ const int chunkSize = 80;
+ for (int offset = 0; offset < departmentIds.Count; offset += chunkSize)
+ {
+ var deptChunk = departmentIds.Skip(offset).Take(chunkSize).ToArray();
+ var deptParamNames = new List(deptChunk.Length);
+ for (int i = 0; i < deptChunk.Length; i++)
+ {
+ deptParamNames.Add("@Dept" + i);
+ }
+
+ string deptInClause = string.Join(",", deptParamNames);
+ string machineJoin = "";
+ string machineFilter = "";
+ if (machineIds.Count > 0)
+ {
+ var machineParamNames = new List(machineIds.Count);
+ for (int i = 0; i < machineIds.Count; i++)
+ {
+ machineParamNames.Add("@Mid" + i);
+ }
+ machineJoin = " INNER JOIN hrms.attendance_machine_user u ON u.serial_number = t.serial_no AND u.is_deleted = 0 ";
+ machineFilter = " AND u.machine_id IN (" + string.Join(",", machineParamNames) + ") ";
+ }
+
+ string query =
+ "SELECT DISTINCT t.serial_no " +
+ "FROM hrms.attendance_machine_face_templates t " +
+ "INNER JOIN hrms.employee e ON CAST(e.emp_id AS CHAR) = t.serial_no " +
+ machineJoin +
+ "WHERE t.is_active = 1 AND e.department_id IN (" + deptInClause + ")" +
+ machineFilter;
+
+ using (var cmd = new MySqlCommand(query, connection))
+ {
+ cmd.CommandTimeout = 120;
+ for (int i = 0; i < deptChunk.Length; i++)
+ {
+ cmd.Parameters.AddWithValue(deptParamNames[i], deptChunk[i]);
+ }
+ for (int i = 0; i < machineIds.Count; i++)
+ {
+ cmd.Parameters.AddWithValue("@Mid" + i, machineIds[i]);
+ }
+
+ using (var reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ if (reader.IsDBNull(0)) continue;
+ var s = reader.GetString(0);
+ if (!string.IsNullOrWhiteSpace(s))
+ {
+ result.Add(s.Trim());
+ }
+ }
+ }
+ }
+ }
+
+ return result.ToList();
+ }
+
///
/// Active templates for employees registered on a specific Nedo machine.
/// Registration-based target selection: attendance_machine_user (is_deleted=0) drives which employees are allowed.