$modelClass HasUserOverrides 적용 모델 클래스 * @param array $finder updateOrCreate 의 unique 키 조건 * @param array $attributes 갱신할 속성 * @return T 동기화된 모델 인스턴스 */ public function sync(string $modelClass, array $finder, array $attributes): Model { return $modelClass::syncOrCreateFromUpgrade($finder, $attributes); } /** * 주어진 scope 내에서 currentKeys 에 없는 row 를 삭제합니다 (완전 동기화). * * 정책: `user_overrides` 무관 — seeder/config 에 없는 row 는 삭제. * * @param class-string $modelClass 대상 모델 클래스 * @param array $scopeFilter 삭제 대상 한정 조건 (예: ['extension_identifier' => 'x', 'type' => 'refund']). 빈 배열이면 전체 테이블 대상. * @param string $keyField 비교 기준 컬럼명 (예: 'code', 'slug', 'type') * @param array $currentKeys 현재 유효한 키 목록 * @return int 삭제된 행 수 */ public function cleanupStale( string $modelClass, array $scopeFilter, string $keyField, array $currentKeys, ): int { $query = $modelClass::query(); foreach ($scopeFilter as $column => $value) { $query->where($column, $value); } $query->whereNotIn($keyField, $currentKeys); $targets = $query->get(['id', $keyField]); foreach ($targets as $row) { $row->delete(); } $count = $targets->count(); if ($count > 0) { Log::info('stale 엔티티 정리 완료', [ 'model' => $modelClass, 'scope' => $scopeFilter, 'key_field' => $keyField, 'deleted' => $count, 'keys' => $targets->pluck($keyField)->all(), ]); } return $count; } }