uind-hikvision-attendance-m.../src/HikvisionAttendanceManager.App/Services/AttendanceSyncService.cs

101 lines
3.8 KiB
C#

using HikvisionAttendanceManager.App.Models;
namespace HikvisionAttendanceManager.App.Services;
public sealed class AttendanceSyncService(
HikvisionIsapiClient hikvision,
AttendanceLogRepository attendanceLog,
AttendanceMachineRepository machineRepository,
OperationHistoryService history)
{
public async Task<SyncHistoryEntry> SyncAsync(Device device, DateTime fromLocal, DateTime toLocal, bool useLastSyncCursor,
IProgress<AttendanceSyncProgress>? progress, CancellationToken cancellationToken)
{
var entry = new SyncHistoryEntry
{
Operation = SyncOperationKind.AttendanceSync,
SourceDevice = device.DisplayName,
Context = $"{fromLocal:dd-MMM-yyyy HH:mm} → {toLocal:dd-MMM-yyyy HH:mm}",
Status = "Running"
};
await history.AddAsync(entry, cancellationToken);
var fetched = 0;
var inserted = 0;
var skipped = 0;
var failed = 0;
DateTime? maxEventTime = null;
var allInsertsSucceeded = true;
try
{
if (useLastSyncCursor)
{
var cursor = await machineRepository.GetLastSyncDateAsync(device.IpAddress, cancellationToken);
if (cursor.HasValue && cursor.Value > fromLocal)
fromLocal = cursor.Value;
}
progress?.Report(new AttendanceSyncProgress(0, 0, 0, 0, "Fetching attendance records from device…"));
var punches = await hikvision.FetchAcsEventsAsync(device, fromLocal, toLocal, cancellationToken);
fetched = punches.Count;
for (var index = 0; index < punches.Count; index++)
{
cancellationToken.ThrowIfCancellationRequested();
var punch = punches[index];
progress?.Report(new AttendanceSyncProgress(fetched, inserted, skipped, failed,
$"Processing {index + 1}/{punches.Count}: {punch.EmployeeNo}"));
try
{
if (await attendanceLog.ExistsAsync(punch.EmployeeNo, punch.CheckTime, device.MachineId, cancellationToken))
{
skipped++;
continue;
}
await attendanceLog.InsertAsync(punch.EmployeeNo, punch.CheckTime, device.MachineId, punch.InOutTypeId, device.IpAddress, cancellationToken);
inserted++;
if (!maxEventTime.HasValue || punch.CheckTime > maxEventTime.Value)
maxEventTime = punch.CheckTime;
}
catch (Exception ex)
{
failed++;
allInsertsSucceeded = false;
AppLogger.Warning($"Attendance insert failed for {punch.EmployeeNo}: {ex.Message}");
}
}
if (allInsertsSucceeded)
{
var cursorTime = maxEventTime ?? toLocal;
await machineRepository.UpdateLastSyncDateAsync(device.IpAddress, cursorTime, cancellationToken);
}
entry.CompletedAt = DateTime.Now;
entry.Total = fetched;
entry.Success = inserted;
entry.Skipped = skipped;
entry.Failed = failed;
entry.Status = failed > 0 ? "Completed with errors" : "Completed";
await history.UpdateAsync(entry, cancellationToken);
}
catch (Exception ex)
{
entry.CompletedAt = DateTime.Now;
entry.Total = fetched;
entry.Success = inserted;
entry.Skipped = skipped;
entry.Failed = failed + 1;
entry.Status = "Failed";
entry.Context += " — " + ex.Message;
await history.UpdateAsync(entry, cancellationToken);
throw;
}
return entry;
}
}