198 lines
6.5 KiB
PHP
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,
|
|
);
|
|
}
|
|
}
|