ldc-boutique-cnc-sable/app/Services/EmployeeSyncService.php
2026-10-27 09:58:59 +01:00

198 lines
6.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services;
use App\Contracts\QueryInputEmpty;
use App\Queries\Settings\GetSettingsQuery;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Domain\Models\ValueObjects\EmployeeUpsertData;
use Domain\Models\ValueObjects\Matricule;
use Domain\Ports\Services\EmployeeImportStorageService;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
use Illuminate\Support\Carbon;
use Psr\Log\LoggerInterface;
class EmployeeSyncService
{
private const SYNC_TIMESTAMP_CACHE = 'EMPLOYEE_LAST_SYNC';
public function __construct(
private readonly LoggerInterface $logger,
private readonly CacheRepository $cacheRepository,
private readonly ArcadiaEmployeeService $arcadiaEmployeeService,
private readonly CodeGenerationService $codeService,
private readonly EmployeeImportStorageService $employeeImportStorageService,
private readonly GetSettingsQuery $getSettingsQuery
) {
}
/**
* @param array<int, \Domain\Models\ValueObjects\ShopperSiteCode>|null $sites
*/
public function synchronize(
?\DateTimeInterface $since = null,
?array $sites = null
): void {
$sitesToSync = $sites ?? ($this->getSettingsQuery)(new QueryInputEmpty())->getShopperSiteCodes();
$sinceDate = $since ?? Carbon::createFromTimestamp(0);
$this->logger->info('Starting employee synchronization.', [
'since' => $sinceDate->format(Carbon::RFC3339),
'sites_count' => count($sitesToSync),
]);
$totalInserted = 0;
$totalUpdated = 0;
foreach ($sitesToSync as $siteCode) {
$siteIdentifier = method_exists($siteCode, 'value') ? $siteCode->value : (string) $siteCode;
$this->logger->debug("Fetching employees for site: {$siteIdentifier}", [
'site' => $siteIdentifier,
'since' => $sinceDate->format(Carbon::RFC3339),
]);
$employees = $this->arcadiaEmployeeService->getEmployeesOfSiteSince(
$siteCode,
$sinceDate
);
if ($employees === false) {
$this->logger->warning("Failed to fetch employees from Arcadia for site: {$siteIdentifier}", [
'site' => $siteIdentifier,
]);
continue;
}
$this->logger->debug("Retrieved employees for site {$siteIdentifier}.", [
'site' => $siteIdentifier,
'count' => count($employees),
]);
$result = $this->upsertEmployees($employees);
$totalInserted += $result->insertedCount;
$totalUpdated += $result->updatedCount;
}
$this->markLastSync();
$this->logger->info('Employee synchronization completed successfully.', [
'total_inserted' => $totalInserted,
'total_updated' => $totalUpdated,
]);
}
public function getLastSync(): ?CarbonInterface
{
$timestamp = $this->cacheRepository->get(self::SYNC_TIMESTAMP_CACHE);
if (! is_string($timestamp)) {
$this->logger->debug('No last synchronization timestamp found in cache.');
return null;
}
return Carbon::parse($timestamp);
}
private function markLastSync(): void
{
$now = Carbon::now()->format(Carbon::RFC3339);
$this->cacheRepository->put(
self::SYNC_TIMESTAMP_CACHE,
$now,
3600 * 36 // Cache for 36 hours
);
$this->logger->debug('Last sync timestamp updated in cache.', [
'timestamp' => $now,
]);
}
/** @param \App\Services\ArcadiaEmployee[] $employeeList */
private function upsertEmployees(array $employeeList): EmployeeSyncServiceResult
{
$matriculesToInsert = [];
$errors = [];
$upsertData = [];
$importStartTime = CarbonImmutable::now()->subSecond();
foreach ($employeeList as $employeeImport) {
$matricule = $employeeImport->matricule->value;
if (in_array($matricule, $matriculesToInsert, true)) {
$errorMsg = "Duplicate detected in the file: The matricule {$matricule} appears multiple times.";
$errors[] = $errorMsg;
$this->logger->warning($errorMsg, [
'matricule' => $matricule,
]);
continue;
}
$matriculesToInsert[] = $matricule;
// NOTE: We generate a new code for each row, but only insert it for new records.
$code = $this->codeService->generateFrom(new Matricule($matricule));
$upsertData[] = new EmployeeUpsertData(
matricule: $employeeImport->matricule,
firstname: $employeeImport->firstname,
lastname: $employeeImport->lastname,
etablissement: $employeeImport->etablissement,
service: $employeeImport->service,
site: $employeeImport->site,
startDate: $employeeImport->startDate,
endDate: $employeeImport->endDate,
code: $code
);
}
if (empty($upsertData) && empty($errors)) {
$errors[] = 'The file contains no valid data.';
$this->logger->notice('Upsert skipped: No valid employee data found in list.');
}
if (! empty($upsertData)) {
$this->logger->debug('Dispatching upsert batch to storage service.', [
'batch_size' => count($upsertData),
]);
$this->employeeImportStorageService->upsertEmployees($upsertData);
$result = $this->employeeImportStorageService
->getImportedAfter(
array_map(
fn ($m) => new Matricule($m),
$matriculesToInsert
),
$importStartTime
);
$insertedCount = $result->insertedCount;
$updatedCount = $result->updatedCount;
$this->logger->info('Batch upsert completed.', [
'inserted' => $insertedCount,
'updated' => $updatedCount,
'errors' => count($errors),
]);
} else {
$insertedCount = 0;
$updatedCount = 0;
}
return new EmployeeSyncServiceResult(
insertedCount: $insertedCount,
updatedCount: $updatedCount,
errors: $errors,
);
}
}