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