Следующие действия должны помочь:
App\Models\Ticket::find(123)->load(['items' => function($query) {
$query->join('products', 'product_id', '=', 'products.id')
->orderBy('category_id', 'ASC');
})->load('items.product.category');
Это скажет Eloquent, что при загрузке элементов должны применяться дополнительные критерии обратного вызова.Там вы объединяете элементы с продуктами и сортируете элементы по category_id .
ОБНОВЛЕНИЕ: Чтобы избежать конфликтов в именах столбцов, вы можете использовать псевдонимы для имен таблиц:
App\Models\Ticket::find(123)->load(['items' => function($query) {
$query->from('ticket_items as tis')
->join('products as ps', DB::raw('its.product_id'), '=', DB::raw('ps.id'))
->select(DB::raw('tis.*'))
->orderBy('category_id', 'ASC');
})->load('items.product.category');