HanvonAttendanceService/AttendanceMachineFaceTempla...

358 lines
15 KiB
C#

using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Linq;
namespace HanvonF710XAttendanceService
{
internal sealed class AttendanceMachineFaceTemplateDAO
{
public bool ExistsBySerialNo(MySqlConnection connection, string serialNo)
{
const string checkQuery = "SELECT COUNT(*) FROM attendance_machine_face_templates WHERE serial_no = @SerialNumber";
using (var cmd = new MySqlCommand(checkQuery, connection))
{
cmd.Parameters.AddWithValue("@SerialNumber", serialNo);
object result = cmd.ExecuteScalar();
if (result == null || result == DBNull.Value)
{
return false;
}
if (int.TryParse(result.ToString(), out int count))
{
return count > 0;
}
return false;
}
}
public int InsertTemplate(MySqlConnection connection, string serialNo, byte[] templateBytes, DateTime createdDate)
{
const string insertQuery =
"INSERT INTO attendance_machine_face_templates (serial_no, template, created_date) " +
"VALUES (@SerialNumber, @FaceTemplate, @created_date)";
using (var cmd = new MySqlCommand(insertQuery, connection))
{
cmd.Parameters.AddWithValue("@SerialNumber", serialNo);
cmd.Parameters.Add("@FaceTemplate", MySqlDbType.Blob).Value = templateBytes ?? Array.Empty<byte>();
cmd.Parameters.AddWithValue("@created_date", createdDate);
return cmd.ExecuteNonQuery();
}
}
public Dictionary<string, byte[]> GetActiveTemplatesBySerialNos(MySqlConnection connection, IReadOnlyList<string> serialNos)
{
var result = new Dictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase);
if (serialNos == null || serialNos.Count == 0)
{
return result;
}
// Chunk to keep query size reasonable.
const int chunkSize = 200;
for (int offset = 0; offset < serialNos.Count; offset += chunkSize)
{
var chunk = serialNos.Skip(offset).Take(chunkSize).ToArray();
var parameterNames = new List<string>(chunk.Length);
for (int i = 0; i < chunk.Length; i++)
{
parameterNames.Add("@SerialNumber" + i);
}
string inClause = string.Join(",", parameterNames);
string query =
$"SELECT serial_no, template FROM attendance_machine_face_templates " +
$"WHERE serial_no IN ({inClause}) AND is_active = 1";
using (var cmd = new MySqlCommand(query, connection))
{
for (int i = 0; i < chunk.Length; i++)
{
cmd.Parameters.AddWithValue(parameterNames[i], chunk[i]);
}
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var serial = reader.IsDBNull(0) ? null : reader.GetString(0);
if (string.IsNullOrWhiteSpace(serial))
{
continue;
}
byte[] blob = null;
if (!reader.IsDBNull(1))
{
blob = (byte[])reader["template"];
}
result[serial] = blob ?? Array.Empty<byte>();
}
}
}
}
return result;
}
/// <summary>All active template serial numbers (CENTRAL hub scope).</summary>
public List<string> GetActiveSerialNumbersAll(MySqlConnection connection)
{
var list = new List<string>();
const string query = "SELECT serial_no FROM hrms.attendance_machine_face_templates WHERE is_active = 1";
using (var cmd = new MySqlCommand(query, connection))
{
cmd.CommandTimeout = 120;
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
if (reader.IsDBNull(0)) continue;
var s = reader.GetString(0);
if (!string.IsNullOrWhiteSpace(s))
{
list.Add(s.Trim());
}
}
}
}
return list;
}
/// <summary>
/// Serial numbers that have an active template and appear on scoped machines (SITE deployment).
/// </summary>
public List<string> GetActiveSerialNumbersForScopedMachines(MySqlConnection connection, IReadOnlyList<string> machineIds)
{
var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (machineIds == null || machineIds.Count == 0)
{
return new List<string>();
}
const int chunkSize = 80;
for (int offset = 0; offset < machineIds.Count; offset += chunkSize)
{
var chunk = machineIds.Skip(offset).Take(chunkSize).ToArray();
var parameterNames = new List<string>(chunk.Length);
for (int i = 0; i < chunk.Length; i++)
{
parameterNames.Add("@Mid" + i);
}
string inClause = string.Join(",", parameterNames);
string query =
"SELECT DISTINCT t.serial_no " +
"FROM hrms.attendance_machine_face_templates t " +
"INNER JOIN hrms.attendance_machine_user u ON u.serial_number = t.serial_no AND u.is_deleted = 0 " +
"WHERE t.is_active = 1 AND u.machine_id IN (" + inClause + ")";
using (var cmd = new MySqlCommand(query, connection))
{
cmd.CommandTimeout = 120;
for (int i = 0; i < chunk.Length; i++)
{
cmd.Parameters.AddWithValue(parameterNames[i], chunk[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();
}
/// <summary>
/// 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.
/// </summary>
public Dictionary<string, byte[]> GetActiveTemplatesForRegisteredMachine(MySqlConnection connection, string machineId)
{
var result = new Dictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(machineId))
{
return result;
}
const string query =
"SELECT t.serial_no, t.template " +
"FROM hrms.attendance_machine_face_templates t " +
"INNER JOIN hrms.attendance_machine_user u ON u.serial_number = t.serial_no " +
"WHERE t.is_active = 1 AND u.is_deleted = 0 AND u.machine_id = @MachineId";
using (var cmd = new MySqlCommand(query, connection))
{
cmd.CommandTimeout = 120;
cmd.Parameters.AddWithValue("@MachineId", machineId);
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
if (reader.IsDBNull(0)) continue;
var serial = reader.GetString(0);
if (string.IsNullOrWhiteSpace(serial)) continue;
byte[] blob = Array.Empty<byte>();
if (!reader.IsDBNull(1))
{
blob = (byte[])reader["template"];
}
result[serial.Trim()] = blob;
}
}
}
return result;
}
/// <summary>
/// For each serial_no, return machine_ids where the employee is registered (attendance_machine_user),
/// restricted to the allowedMachineIds list (scope) and to Nedo machines (exclude ZKTECO by machine_type).
/// </summary>
public Dictionary<string, List<string>> GetRegisteredTargetMachineIdsForSerialNos(
MySqlConnection connection,
IReadOnlyList<string> serialNos,
IReadOnlyList<string> allowedMachineIds)
{
var result = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
if (serialNos == null || serialNos.Count == 0)
{
return result;
}
var allowedSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (allowedMachineIds != null)
{
foreach (var id in allowedMachineIds)
{
if (!string.IsNullOrWhiteSpace(id))
{
allowedSet.Add(id.Trim());
}
}
}
if (allowedSet.Count == 0)
{
return result;
}
const int serialChunkSize = 200;
var allowedList = allowedSet.ToList();
// Keep allowed-machine IN clause reasonably small.
const int allowedChunkSize = 100;
for (int offset = 0; offset < serialNos.Count; offset += serialChunkSize)
{
var serialChunk = serialNos.Skip(offset).Take(serialChunkSize).ToArray();
var serialParamNames = new List<string>(serialChunk.Length);
for (int i = 0; i < serialChunk.Length; i++)
{
serialParamNames.Add("@S" + i);
}
string serialInClause = string.Join(",", serialParamNames);
for (int ao = 0; ao < allowedList.Count; ao += allowedChunkSize)
{
var allowedChunk = allowedList.Skip(ao).Take(allowedChunkSize).ToArray();
var allowedParamNames = new List<string>(allowedChunk.Length);
for (int i = 0; i < allowedChunk.Length; i++)
{
allowedParamNames.Add("@A" + i);
}
string allowedInClause = string.Join(",", allowedParamNames);
const string query =
"SELECT u.serial_number, u.machine_id " +
"FROM hrms.attendance_machine_user u " +
"INNER JOIN hrms.attendance_machine m ON m.machine_id = u.machine_id " +
"WHERE u.is_deleted = 0 " +
"AND (m.machine_type IS NULL OR m.machine_type <> 'ZKTECO') " +
"AND u.serial_number IN ({SERIAL_IN}) " +
"AND u.machine_id IN ({ALLOWED_IN})";
string q = query
.Replace("{SERIAL_IN}", serialInClause)
.Replace("{ALLOWED_IN}", allowedInClause);
using (var cmd = new MySqlCommand())
{
cmd.Connection = connection;
cmd.CommandTimeout = 120;
string typeClause = MachineScope.BuildMachineTypeWhereClause(cmd, "m.machine_type", "mtreg" + offset + ao);
cmd.CommandText = q.Replace("AND (m.machine_type IS NULL OR m.machine_type <> 'ZKTECO')", "AND " + typeClause);
for (int i = 0; i < serialChunk.Length; i++)
{
cmd.Parameters.AddWithValue(serialParamNames[i], serialChunk[i]);
}
for (int i = 0; i < allowedChunk.Length; i++)
{
cmd.Parameters.AddWithValue(allowedParamNames[i], allowedChunk[i]);
}
using (var reader = cmd.ExecuteReader())
{
// temp set per serial to avoid duplicates from allowed-chunk partitioning
var serialToSet = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
while (reader.Read())
{
if (reader.IsDBNull(0) || reader.IsDBNull(1)) continue;
var serial = reader.GetString(0);
var mid = reader.GetString(1);
if (string.IsNullOrWhiteSpace(serial) || string.IsNullOrWhiteSpace(mid)) continue;
if (!serialToSet.TryGetValue(serial, out var set))
{
set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
serialToSet[serial] = set;
}
set.Add(mid.Trim());
}
foreach (var kvp in serialToSet)
{
if (!result.TryGetValue(kvp.Key, out var list))
{
list = new List<string>();
result[kvp.Key] = list;
}
foreach (var mid in kvp.Value)
{
if (!list.Contains(mid))
{
list.Add(mid);
}
}
}
}
}
}
}
return result;
}
}
}