feat(sync): update user sync engine and departmental sync for face outcomes
Wires the sync engine and departmental sync to the new result model and face-enrollment verification behavior.main
parent
f1e20ddf0b
commit
c4bd561811
|
|
@ -11,6 +11,8 @@ public sealed class DepartmentalUserSyncService(
|
|||
IProgress<UserSyncProgress>? progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = employees.ToList();
|
||||
AppLogger.TraceEnter("USER_SYNC", nameof(SyncAsync),
|
||||
$"device={device.IpAddress} count={list.Count} context={contextSummary}");
|
||||
var entry = new SyncHistoryEntry
|
||||
{
|
||||
Operation = SyncOperationKind.DepartmentalUserSync,
|
||||
|
|
@ -24,10 +26,12 @@ public sealed class DepartmentalUserSyncService(
|
|||
var results = await engine.RunAsync(device, list, initialEnrollment: true, progress, cancellationToken);
|
||||
entry.CompletedAt = DateTime.Now;
|
||||
entry.Success = results.Count(r => r.OverallResult is "SUCCESS" or "ALREADY_EXISTS");
|
||||
entry.Failed = results.Count(r => r.OverallResult == "FAILED");
|
||||
entry.Failed = results.Count(r => r.OverallResult is "FAILED" or "PARTIAL");
|
||||
entry.Skipped = results.Count(r => r.OverallResult == "SKIPPED");
|
||||
entry.Status = entry.Failed > 0 ? "Completed with errors" : "Completed";
|
||||
await history.UpdateAsync(entry, cancellationToken);
|
||||
AppLogger.TraceExit("USER_SYNC", nameof(SyncAsync),
|
||||
$"success={entry.Success} failed={entry.Failed} skipped={entry.Skipped}");
|
||||
return (entry, results);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,47 +7,103 @@ public sealed class UserSyncEngine(HikvisionIsapiClient hikvision, EmployeePhoto
|
|||
public async Task<IReadOnlyList<EmployeeSyncResult>> RunAsync(Device device, IEnumerable<HrmsEmployee> employees, bool initialEnrollment,
|
||||
IProgress<UserSyncProgress>? progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var results = new List<EmployeeSyncResult>();
|
||||
var source = employees.ToList();
|
||||
AppLogger.TraceEnter("USER_SYNC", nameof(RunAsync),
|
||||
$"device={device.IpAddress} count={source.Count} initialEnrollment={initialEnrollment}");
|
||||
var results = new List<EmployeeSyncResult>();
|
||||
for (var index = 0; index < source.Count; index++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var employee = source[index];
|
||||
AppLogger.TraceEnter("USER_SYNC", "ProcessEmployee", $"employee={employee.SerialNumber} index={index + 1}/{source.Count}");
|
||||
progress?.Report(CreateProgress(index, source.Count, employee.SerialNumber, "Creating user…", results));
|
||||
var result = new EmployeeSyncResult { Employee = employee, EmployeeNumber = employee.SerialNumber, EmployeeName = employee.Name, Department = employee.DepartmentName };
|
||||
var userOnDevice = false;
|
||||
try
|
||||
{
|
||||
if (await hikvision.UserExistsAsync(device, employee.SerialNumber, cancellationToken))
|
||||
var exists = await hikvision.UserExistsAsync(device, employee.SerialNumber, cancellationToken);
|
||||
if (exists)
|
||||
{
|
||||
userOnDevice = true;
|
||||
result.UserCreated = true;
|
||||
result.UserStatus = "Already exists";
|
||||
if (initialEnrollment)
|
||||
{
|
||||
var existingFaces = await hikvision.TryGetUserFaceCountAsync(device, employee.SerialNumber, cancellationToken);
|
||||
if (existingFaces is >= 1)
|
||||
{
|
||||
result.FaceStatus = "Already enrolled";
|
||||
result.VerificationStatus = "Verified";
|
||||
result.FaceEnrolled = true;
|
||||
result.OverallResult = "ALREADY_EXISTS";
|
||||
results.Add(result);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var user = await hikvision.CreateUserAsync(device, employee, cancellationToken);
|
||||
if (!user.Success) { result.UserStatus = "Failed"; result.OverallResult = "FAILED"; result.Reason = user.Reason; results.Add(result); continue; }
|
||||
if (!user.Success)
|
||||
{
|
||||
result.UserStatus = "Failed";
|
||||
result.OverallResult = "FAILED";
|
||||
result.Reason = user.Reason;
|
||||
results.Add(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
userOnDevice = true;
|
||||
result.UserCreated = true;
|
||||
result.UserStatus = "Created";
|
||||
}
|
||||
|
||||
if (!initialEnrollment) { result.OverallResult = result.UserStatus == "Already exists" ? "ALREADY_EXISTS" : "SUCCESS"; results.Add(result); continue; }
|
||||
if (!initialEnrollment)
|
||||
{
|
||||
result.OverallResult = result.UserStatus == "Already exists" ? "ALREADY_EXISTS" : "SUCCESS";
|
||||
results.Add(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
progress?.Report(CreateProgress(index, source.Count, employee.SerialNumber, "Downloading employee photo…", results));
|
||||
var photo = await photos.DownloadAndNormalizeAsync(employee, cancellationToken);
|
||||
if (!photo.Success) { result.FaceStatus = "Failed"; result.OverallResult = "FAILED"; result.Reason = photo.Reason; results.Add(result); continue; }
|
||||
if (!photo.Success)
|
||||
{
|
||||
FaceEnrollmentResults.Apply(result, userOnDevice, false, "Failed", "Not requested", photo.Reason);
|
||||
results.Add(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
progress?.Report(CreateProgress(index, source.Count, employee.SerialNumber, "Uploading face…", results));
|
||||
var face = await hikvision.UploadFaceAsync(device, employee.SerialNumber, photo.Bytes, cancellationToken);
|
||||
if (!face.Success) { result.FaceStatus = "Failed"; result.OverallResult = "FAILED"; result.Reason = face.Reason; results.Add(result); continue; }
|
||||
if (!face.Success)
|
||||
{
|
||||
FaceEnrollmentResults.Apply(result, userOnDevice, false, "Failed", "Not requested", face.Reason);
|
||||
results.Add(result);
|
||||
continue;
|
||||
}
|
||||
result.FaceStatus = "Uploaded";
|
||||
|
||||
progress?.Report(CreateProgress(index, source.Count, employee.SerialNumber, "Verifying face enrollment…", results));
|
||||
var verification = await hikvision.VerifyFaceAsync(device, employee.SerialNumber, cancellationToken);
|
||||
result.VerificationStatus = verification.Success ? "Verified" : "Failed";
|
||||
result.OverallResult = verification.Success ? "SUCCESS" : "FAILED";
|
||||
result.Reason = verification.Reason;
|
||||
FaceEnrollmentResults.Apply(result, userOnDevice, verification.Success, result.FaceStatus,
|
||||
verification.Success ? "Verified" : "Failed", verification.Reason);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.OverallResult = "FAILED";
|
||||
result.Reason = ex is InvalidOperationException ? ex.Message : "Device unavailable or operation failed.";
|
||||
FaceEnrollmentResults.Apply(result, userOnDevice, false, result.FaceStatus, result.VerificationStatus,
|
||||
ex is InvalidOperationException ? ex.Message : "Device unavailable or operation failed.");
|
||||
if (!userOnDevice)
|
||||
result.OverallResult = "FAILED";
|
||||
}
|
||||
|
||||
AppLogger.TraceExit("USER_SYNC", "ProcessEmployee",
|
||||
$"employee={employee.SerialNumber} result={result.OverallResult}");
|
||||
results.Add(result);
|
||||
}
|
||||
progress?.Report(CreateProgress(source.Count, source.Count, "", "Completed", results));
|
||||
AppLogger.TraceExit("USER_SYNC", nameof(RunAsync),
|
||||
$"success={results.Count(r => r.OverallResult is "SUCCESS" or "ALREADY_EXISTS")} partial={results.Count(r => r.OverallResult == "PARTIAL")} failed={results.Count(r => r.OverallResult == "FAILED")}");
|
||||
return results;
|
||||
}
|
||||
|
||||
|
|
@ -55,5 +111,39 @@ public sealed class UserSyncEngine(HikvisionIsapiClient hikvision, EmployeePhoto
|
|||
new(completed, total, employee, operation,
|
||||
results.Count(r => r.UserStatus == "Created"), results.Count(r => r.UserStatus == "Already exists"),
|
||||
results.Count(r => r.FaceStatus == "Uploaded"), results.Count(r => r.FaceStatus == "Failed"),
|
||||
results.Count(r => r.OverallResult == "SKIPPED"), results.Count(r => r.OverallResult == "FAILED"));
|
||||
results.Count(r => r.OverallResult == "SKIPPED"), results.Count(r => r.OverallResult is "FAILED" or "PARTIAL"));
|
||||
}
|
||||
|
||||
internal static class FaceEnrollmentResults
|
||||
{
|
||||
internal static void Apply(
|
||||
EmployeeSyncResult result,
|
||||
bool userOnDevice,
|
||||
bool faceEnrolled,
|
||||
string faceStatus,
|
||||
string verificationStatus,
|
||||
string? failureReason)
|
||||
{
|
||||
result.UserCreated = userOnDevice;
|
||||
result.FaceEnrolled = faceEnrolled;
|
||||
result.FaceStatus = faceStatus;
|
||||
result.VerificationStatus = verificationStatus;
|
||||
|
||||
if (faceEnrolled)
|
||||
{
|
||||
result.OverallResult = "SUCCESS";
|
||||
result.Reason = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (userOnDevice)
|
||||
{
|
||||
result.OverallResult = "PARTIAL";
|
||||
result.Reason = string.IsNullOrWhiteSpace(failureReason) ? "Face enrollment failed." : failureReason;
|
||||
return;
|
||||
}
|
||||
|
||||
result.OverallResult = "FAILED";
|
||||
result.Reason = string.IsNullOrWhiteSpace(failureReason) ? "User creation failed." : failureReason;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue