Fix attendance_log inserts and extend face-template/user deletion DB APIs
Replace REPLACE/empty ac_no inserts with guarded InsertAttendance (avoids generated ac_no_int cast failures); extend face-template upsert metadata and pending-deletion user queries for machine-scoped delete requests.main
parent
a136fe587c
commit
9ae838888a
|
|
@ -31,4 +31,6 @@ internal sealed class AttendanceMachineFaceTemplateRow
|
||||||
public byte[] Template { get; set; } = Array.Empty<byte>();
|
public byte[] Template { get; set; } = Array.Empty<byte>();
|
||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
|
public string SourceMachineId { get; set; } = "";
|
||||||
|
public string SourceMachineIp { get; set; } = "";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,20 @@ internal interface IAttendanceMachineRepository
|
||||||
|
|
||||||
internal interface IAttendanceLogRepository
|
internal interface IAttendanceLogRepository
|
||||||
{
|
{
|
||||||
bool UpsertAttendance(
|
/// <summary>
|
||||||
|
/// Inserts one attendance_log row. Does not use REPLACE until mapping is confirmed.
|
||||||
|
/// <paramref name="acNo"/> must be a non-empty numeric employee string (never "").
|
||||||
|
/// </summary>
|
||||||
|
bool InsertAttendance(
|
||||||
string acNo,
|
string acNo,
|
||||||
|
int acNoInt,
|
||||||
DateTime checkTime,
|
DateTime checkTime,
|
||||||
int processed,
|
int processed,
|
||||||
string machineId,
|
string machineId,
|
||||||
int inOutTypeId,
|
int inOutTypeId,
|
||||||
string machineIp,
|
string machineIp,
|
||||||
DateTime dateOnly,
|
DateTime dateOnly,
|
||||||
|
Action<string>? log,
|
||||||
out string error);
|
out string error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,6 +46,6 @@ internal interface IAttendanceMachineUserRepository
|
||||||
|
|
||||||
internal interface IAttendanceMachineFaceTemplateRepository
|
internal interface IAttendanceMachineFaceTemplateRepository
|
||||||
{
|
{
|
||||||
bool UpsertFaceTemplate(string serialNo, byte[] template, DateTime createdDate, bool isActive, out string error);
|
bool UpsertFaceTemplate(string serialNo, byte[] template, DateTime createdDate, bool isActive, string? sourceMachineId, string? sourceMachineIp, out bool createdNew, out string error);
|
||||||
bool TryGetActiveFaceTemplateBySerial(string serialNo, out AttendanceMachineFaceTemplateRow? row, out string error);
|
bool TryGetActiveFaceTemplateBySerial(string serialNo, out AttendanceMachineFaceTemplateRow? row, out string error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ internal sealed class MySqlAttendanceMachineRepository : IAttendanceMachineRepos
|
||||||
string sql = "SELECT machine_id,machine_ip,port_number,machine_name,site_id,machine_status,machine_type,status,last_sync_date,total_users " +
|
string sql = "SELECT machine_id,machine_ip,port_number,machine_name,site_id,machine_status,machine_type,status,last_sync_date,total_users " +
|
||||||
"FROM attendance_machine WHERE (machine_status='active' OR machine_status='1' OR machine_status=1)";
|
"FROM attendance_machine WHERE (machine_status='active' OR machine_status='1' OR machine_status=1)";
|
||||||
if (!string.IsNullOrWhiteSpace(machineTypeFilter))
|
if (!string.IsNullOrWhiteSpace(machineTypeFilter))
|
||||||
sql += " AND machine_type=@machineType";
|
sql += " AND UPPER(TRIM(machine_type))=UPPER(TRIM(@machineType))";
|
||||||
using var cmd = new MySqlCommand(sql, conn);
|
using var cmd = new MySqlCommand(sql, conn);
|
||||||
if (!string.IsNullOrWhiteSpace(machineTypeFilter))
|
if (!string.IsNullOrWhiteSpace(machineTypeFilter))
|
||||||
cmd.Parameters.AddWithValue("@machineType", machineTypeFilter);
|
cmd.Parameters.AddWithValue("@machineType", machineTypeFilter);
|
||||||
|
|
@ -126,30 +126,94 @@ internal sealed class MySqlAttendanceLogRepository : IAttendanceLogRepository
|
||||||
private readonly IHrmsDbConnectionFactory _factory;
|
private readonly IHrmsDbConnectionFactory _factory;
|
||||||
public MySqlAttendanceLogRepository(IHrmsDbConnectionFactory factory) { _factory = factory; }
|
public MySqlAttendanceLogRepository(IHrmsDbConnectionFactory factory) { _factory = factory; }
|
||||||
|
|
||||||
public bool UpsertAttendance(string acNo, DateTime checkTime, int processed, string machineId, int inOutTypeId, string machineIp, DateTime dateOnly, out string error)
|
// Confirmed via SHOW CREATE TABLE / DESCRIBE hrms.attendance_log
|
||||||
|
private static readonly (string Param, string Column, string MySqlType)[] AttendanceLogParamMeta =
|
||||||
|
{
|
||||||
|
("@ac_no", "ac_no", "varchar(24) NOT NULL"),
|
||||||
|
("@checktime", "checktime", "datetime NOT NULL"),
|
||||||
|
("@processed", "processed", "int NULL"),
|
||||||
|
("@machine_id", "machine_id", "varchar(45) NULL"),
|
||||||
|
("@in_out_type_id", "in_out_type_id", "int NULL"),
|
||||||
|
("@machine_ip", "machine_ip", "varchar(50) NULL"),
|
||||||
|
("@date", "date", "datetime NULL"),
|
||||||
|
};
|
||||||
|
|
||||||
|
public bool InsertAttendance(
|
||||||
|
string acNo,
|
||||||
|
int acNoInt,
|
||||||
|
DateTime checkTime,
|
||||||
|
int processed,
|
||||||
|
string machineId,
|
||||||
|
int inOutTypeId,
|
||||||
|
string machineIp,
|
||||||
|
DateTime dateOnly,
|
||||||
|
Action<string>? log,
|
||||||
|
out string error)
|
||||||
{
|
{
|
||||||
error = "";
|
error = "";
|
||||||
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Guard: empty ac_no breaks generated column ac_no_int = CAST(ac_no AS UNSIGNED)
|
||||||
|
// ("Truncated incorrect INTEGER value: ''").
|
||||||
|
if (string.IsNullOrWhiteSpace(acNo))
|
||||||
|
{
|
||||||
|
error = "refused insert: ac_no is empty (would break generated ac_no_int CAST)";
|
||||||
|
log?.Invoke("[AttendanceInsert] REFUSED: " + error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var conn = new MySqlConnection(cs);
|
using var conn = new MySqlConnection(cs);
|
||||||
conn.Open();
|
conn.Open();
|
||||||
using var cmd = new MySqlCommand(
|
// Plain INSERT (not REPLACE) until field mapping is confirmed.
|
||||||
"REPLACE INTO attendance_log (ac_no,checktime,processed,machine_id,in_out_type_id,machine_ip,date) " +
|
const string sql =
|
||||||
"VALUES (@ac_no,@checktime,@processed,@machine_id,@in_out_type_id,@machine_ip,@date)",
|
"INSERT INTO attendance_log (ac_no,checktime,processed,machine_id,in_out_type_id,machine_ip,date) " +
|
||||||
conn);
|
"VALUES (@ac_no,@checktime,@processed,@machine_id,@in_out_type_id,@machine_ip,@date)";
|
||||||
cmd.Parameters.AddWithValue("@ac_no", acNo ?? "");
|
|
||||||
cmd.Parameters.AddWithValue("@checktime", checkTime);
|
using var cmd = new MySqlCommand(sql, conn);
|
||||||
cmd.Parameters.AddWithValue("@processed", processed);
|
cmd.Parameters.Add("@ac_no", MySqlDbType.VarChar, 24).Value = acNo;
|
||||||
cmd.Parameters.AddWithValue("@machine_id", machineId ?? "");
|
cmd.Parameters.Add("@checktime", MySqlDbType.DateTime).Value = checkTime;
|
||||||
cmd.Parameters.AddWithValue("@in_out_type_id", inOutTypeId);
|
cmd.Parameters.Add("@processed", MySqlDbType.Int32).Value = processed;
|
||||||
cmd.Parameters.AddWithValue("@machine_ip", machineIp ?? "");
|
cmd.Parameters.Add("@machine_id", MySqlDbType.VarChar, 45).Value =
|
||||||
cmd.Parameters.AddWithValue("@date", dateOnly.Date);
|
string.IsNullOrWhiteSpace(machineId) ? (object)DBNull.Value : machineId;
|
||||||
|
cmd.Parameters.Add("@in_out_type_id", MySqlDbType.Int32).Value = inOutTypeId;
|
||||||
|
cmd.Parameters.Add("@machine_ip", MySqlDbType.VarChar, 50).Value =
|
||||||
|
string.IsNullOrWhiteSpace(machineIp) ? (object)DBNull.Value : machineIp;
|
||||||
|
cmd.Parameters.Add("@date", MySqlDbType.DateTime).Value = dateOnly.Date;
|
||||||
|
|
||||||
|
log?.Invoke("[AttendanceInsert] SQL=" + sql);
|
||||||
|
log?.Invoke("[AttendanceInsert] NOTE: for_date is GENERATED STORED from checktime; ac_no_int is GENERATED VIRTUAL CAST(ac_no AS UNSIGNED). Neither is bound. cardNo is NOT inserted.");
|
||||||
|
log?.Invoke("[AttendanceInsert] Parsed employee int (source employeeNoString) acNoInt=" + acNoInt +
|
||||||
|
" (not a bound param; drives @ac_no string value)");
|
||||||
|
foreach (var meta in AttendanceLogParamMeta)
|
||||||
|
{
|
||||||
|
var p = cmd.Parameters[meta.Param];
|
||||||
|
var val = p.Value;
|
||||||
|
var runtimeType = val == null || val == DBNull.Value ? "DBNull" : val.GetType().FullName;
|
||||||
|
var display = val == null || val == DBNull.Value ? "<DBNull>" : Convert.ToString(val, System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
log?.Invoke(
|
||||||
|
"[AttendanceInsert] param=" + meta.Param +
|
||||||
|
" value=\"" + display + "\"" +
|
||||||
|
" csharpType=" + runtimeType +
|
||||||
|
" column=" + meta.Column +
|
||||||
|
" mysqlType=" + meta.MySqlType);
|
||||||
|
}
|
||||||
|
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex) { error = ex.Message; return false; }
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = ex.Message;
|
||||||
|
log?.Invoke("[AttendanceInsert] FAILED err=" + ex.Message +
|
||||||
|
" ac_no=\"" + (acNo ?? "") + "\" acNoInt=" + acNoInt +
|
||||||
|
" machine_id=\"" + (machineId ?? "") + "\" machine_ip=\"" + (machineIp ?? "") + "\"" +
|
||||||
|
" checktime=" + checkTime.ToString("yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture) +
|
||||||
|
" in_out_type_id=" + inOutTypeId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,23 +309,65 @@ internal sealed class MySqlAttendanceMachineFaceTemplateRepository : IAttendance
|
||||||
private readonly IHrmsDbConnectionFactory _factory;
|
private readonly IHrmsDbConnectionFactory _factory;
|
||||||
public MySqlAttendanceMachineFaceTemplateRepository(IHrmsDbConnectionFactory factory) { _factory = factory; }
|
public MySqlAttendanceMachineFaceTemplateRepository(IHrmsDbConnectionFactory factory) { _factory = factory; }
|
||||||
|
|
||||||
public bool UpsertFaceTemplate(string serialNo, byte[] template, DateTime createdDate, bool isActive, out string error)
|
public bool UpsertFaceTemplate(string serialNo, byte[] template, DateTime createdDate, bool isActive, string? sourceMachineId, string? sourceMachineIp, out bool createdNew, out string error)
|
||||||
{
|
{
|
||||||
error = "";
|
error = "";
|
||||||
|
createdNew = true;
|
||||||
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
if (!_factory.TryBuildConnectionString(out var cs, out error))
|
||||||
return false;
|
return false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var conn = new MySqlConnection(cs);
|
using var conn = new MySqlConnection(cs);
|
||||||
conn.Open();
|
conn.Open();
|
||||||
using var cmd = new MySqlCommand(
|
|
||||||
"REPLACE INTO attendance_machine_face_templates (serial_no,template,created_date,is_active) " +
|
using (var existsCmd = new MySqlCommand(
|
||||||
"VALUES (@serial_no,@template,@created_date,@is_active)", conn);
|
"SELECT 1 FROM attendance_machine_face_templates WHERE serial_no=@serial_no LIMIT 1", conn))
|
||||||
cmd.Parameters.AddWithValue("@serial_no", serialNo ?? "");
|
{
|
||||||
cmd.Parameters.Add("@template", MySqlDbType.Blob).Value = (object?)template ?? Array.Empty<byte>();
|
existsCmd.Parameters.AddWithValue("@serial_no", serialNo ?? "");
|
||||||
cmd.Parameters.AddWithValue("@created_date", createdDate);
|
createdNew = existsCmd.ExecuteScalar() == null;
|
||||||
cmd.Parameters.AddWithValue("@is_active", isActive ? 1 : 0);
|
}
|
||||||
cmd.ExecuteNonQuery();
|
|
||||||
|
if (createdNew)
|
||||||
|
{
|
||||||
|
using var insert = new MySqlCommand(
|
||||||
|
"INSERT INTO attendance_machine_face_templates (serial_no,template,created_date,is_active) " +
|
||||||
|
"VALUES (@serial_no,@template,@created_date,@is_active)", conn);
|
||||||
|
insert.Parameters.AddWithValue("@serial_no", serialNo ?? "");
|
||||||
|
insert.Parameters.Add("@template", MySqlDbType.Blob).Value = (object?)template ?? Array.Empty<byte>();
|
||||||
|
insert.Parameters.AddWithValue("@created_date", createdDate);
|
||||||
|
insert.Parameters.AddWithValue("@is_active", isActive ? 1 : 0);
|
||||||
|
insert.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var update = new MySqlCommand(
|
||||||
|
"UPDATE attendance_machine_face_templates SET template=@template, created_date=@created_date, is_active=@is_active " +
|
||||||
|
"WHERE serial_no=@serial_no", conn);
|
||||||
|
update.Parameters.AddWithValue("@serial_no", serialNo ?? "");
|
||||||
|
update.Parameters.Add("@template", MySqlDbType.Blob).Value = (object?)template ?? Array.Empty<byte>();
|
||||||
|
update.Parameters.AddWithValue("@created_date", createdDate);
|
||||||
|
update.Parameters.AddWithValue("@is_active", isActive ? 1 : 0);
|
||||||
|
update.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Best-effort source metadata update for schemas that include these columns.
|
||||||
|
if (!string.IsNullOrWhiteSpace(sourceMachineId) || !string.IsNullOrWhiteSpace(sourceMachineIp))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var metaCmd = new MySqlCommand(
|
||||||
|
"UPDATE attendance_machine_face_templates SET source_machine_id=@source_machine_id, source_machine_ip=@source_machine_ip WHERE serial_no=@serial_no",
|
||||||
|
conn);
|
||||||
|
metaCmd.Parameters.AddWithValue("@source_machine_id", (object?)sourceMachineId ?? DBNull.Value);
|
||||||
|
metaCmd.Parameters.AddWithValue("@source_machine_ip", (object?)sourceMachineIp ?? DBNull.Value);
|
||||||
|
metaCmd.Parameters.AddWithValue("@serial_no", serialNo ?? "");
|
||||||
|
metaCmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Older schemas may not have source metadata columns; keep core template write successful.
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex) { error = ex.Message; return false; }
|
catch (Exception ex) { error = ex.Message; return false; }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue