diff --git a/app/Http/Controllers/Api/ReportsController.php b/app/Http/Controllers/Api/ReportsController.php index 55537d55db..593afdebf6 100644 --- a/app/Http/Controllers/Api/ReportsController.php +++ b/app/Http/Controllers/Api/ReportsController.php @@ -111,6 +111,10 @@ class ReportsController extends Controller 'item_type', 'action_source', 'action_date', + 'location', + 'next_audit_date', + 'days_to_next_audit', + 'item', ]; $total = $actionlogs->count(); @@ -124,6 +128,19 @@ class ReportsController extends Controller case 'created_by': $actionlogs->OrderByCreatedBy($order); break; + case 'location': + $actionlogs->OrderByLocation($order); + break; + case 'next_audit_date': + case 'days_to_next_audit': + // days_to_next_audit = next_audit_date - today, so it + // sorts in the same order as next_audit_date. One + // scope covers both cases; no separate expression. + $actionlogs->OrderByAssetNextAuditDate($order); + break; + case 'item': + $actionlogs->OrderByItemName($order); + break; default: $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'action_logs.created_at'; $actionlogs = $actionlogs->orderBy($sort, $order); diff --git a/app/Models/Actionlog.php b/app/Models/Actionlog.php index 9ccdb045d3..d58941fc3f 100755 --- a/app/Models/Actionlog.php +++ b/app/Models/Actionlog.php @@ -655,4 +655,48 @@ class Actionlog extends SnipeModel { return $query->leftJoin('users as admin_sort', 'action_logs.created_by', '=', 'admin_sort.id')->select('action_logs.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); } + + // Audit report (issue #9430): the frontend has advertised location + // as sortable for a while but the API whitelist didn't include it, + // so clicks silently fell back to created_at. action_logs stores + // its own location_id snapshot (see the belongsTo above) so this + // is a plain leftJoin on locations, no polymorphic asset walk. + public function scopeOrderByLocation($query, $order) + { + return $query->leftJoin('locations as location_sort', 'action_logs.location_id', '=', 'location_sort.id') + ->select('action_logs.*') + ->orderBy('location_sort.name', $order); + } + + // Also for the audit report: next_audit_date lives on the asset + // (the log doesn't snapshot it), so we join to assets on the + // polymorphic item_id + item_type pair. Non-Asset item_types get + // NULL from the leftJoin and sort naturally at the end. + public function scopeOrderByAssetNextAuditDate($query, $order) + { + return $query->leftJoin('assets as asset_sort', function ($join) { + $join->on('action_logs.item_id', '=', 'asset_sort.id') + ->where('action_logs.item_type', '=', Asset::class); + }) + ->select('action_logs.*') + ->orderBy('asset_sort.next_audit_date', $order); + } + + // Item name sort for the audit report (#9430). Only Assets get + // audited, so the action_type=audit filter upstream guarantees + // item_type=Asset here and a straight Asset join is correct. + // Sibling activity reports that sort by item on other action_types + // (checkout/checkin/etc.) will get NULL names for non-Asset rows + // and sort them at the ends; that matches the pre-refactor state + // where item sort silently fell back to created_at, and fixing + // truly-polymorphic item sort is a bigger separate change. + public function scopeOrderByItemName($query, $order) + { + return $query->leftJoin('assets as asset_item_sort', function ($join) { + $join->on('action_logs.item_id', '=', 'asset_item_sort.id') + ->where('action_logs.item_type', '=', Asset::class); + }) + ->select('action_logs.*') + ->orderBy('asset_item_sort.name', $order); + } } diff --git a/database/migrations/2026_07_30_000001_add_next_audit_date_index_to_assets.php b/database/migrations/2026_07_30_000001_add_next_audit_date_index_to_assets.php new file mode 100644 index 0000000000..b5c06ce553 --- /dev/null +++ b/database/migrations/2026_07_30_000001_add_next_audit_date_index_to_assets.php @@ -0,0 +1,34 @@ +index('next_audit_date', 'assets_next_audit_date_index'); + }); + } + + public function down(): void + { + Schema::table('assets', function (Blueprint $table) { + $table->dropIndex('assets_next_audit_date_index'); + }); + } +}; diff --git a/resources/views/reports/audit.blade.php b/resources/views/reports/audit.blade.php index 415602d6f4..5c28bdd2f1 100644 --- a/resources/views/reports/audit.blade.php +++ b/resources/views/reports/audit.blade.php @@ -35,8 +35,8 @@ {{ trans('general.item') }} {{ trans('general.location') }} {{ trans('general.last_audit') }} - {{ trans('general.next_audit_date') }} - {{ trans('general.days_to_next_audit') }} + {{ trans('general.next_audit_date') }} + {{ trans('general.days_to_next_audit') }} {{ trans('general.notes') }}