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
SYED MUSTUFA AHMED NAQVI 2026-08-27 10:00:13 +05:00
parent f1e20ddf0b
commit c4bd561811
2 changed files with 107 additions and 13 deletions

View File

@ -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);
}
}

View File

@ -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)
{
FaceEnrollmentResults.Apply(result, userOnDevice, false, result.FaceStatus, result.VerificationStatus,
ex is InvalidOperationException ? ex.Message : "Device unavailable or operation failed.");
if (!userOnDevice)
result.OverallResult = "FAILED";
result.Reason = ex is InvalidOperationException ? ex.Message : "Device unavailable or operation 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;
}
}