expires_at && $service->expires_at->isFuture() ? $service->expires_at->copy() : now(); $end = $start->copy()->addMonths($months); if ($end->lte(now())) { return 'Không thể gia hạn cho khoảng thời gian trong quá khứ.'; } $overlapping = ActivityLog::query() ->where('subject_type', Service::class) ->where('subject_id', $service->id) ->where('event', 'renewed') ->get(['old_values', 'new_values']) ->first(fn (ActivityLog $log) => self::overlaps($log, $start, $end)); if (! $overlapping) { return null; } return sprintf( 'Khoảng thời gian %s – %s đã được gia hạn trước đó, không thể gia hạn trùng.', $start->format('d/m/Y'), $end->format('d/m/Y'), ); } private static function overlaps(ActivityLog $log, Carbon $start, Carbon $end): bool { $loggedEnd = $log->new_values['expires_at'] ?? null; if (! $loggedEnd) { return false; } $loggedEnd = Carbon::parse($loggedEnd); $loggedStart = ($log->old_values['expires_at'] ?? null) ? Carbon::parse($log->old_values['expires_at']) : $loggedEnd->copy()->subMonths((int) ($log->new_values['months'] ?? 1)); return $loggedStart->lt($end) && $loggedEnd->gt($start); } }