3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-18 11:15:42 +00:00

Fixed #9430 - make next audit date sortable

This commit is contained in:
snipe
2026-07-30 09:04:09 +01:00
parent c94402dc98
commit 01fb459dd2
4 changed files with 97 additions and 2 deletions

View File

@ -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);

View File

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

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Speeds up two query paths that sort/filter on assets.next_audit_date:
*
* - Audit report sort (#9430) via Actionlog::scopeOrderByAssetNextAuditDate,
* which leftJoins assets on the polymorphic item_id/item_type and
* orders by next_audit_date. Without an index, larger installs
* filesort the entire filtered set per paged click.
* - SendUpcomingAuditReport's dueOrOverdueForAudit scope, which
* filters assets by next_audit_date within a window. Runs
* nightly via the scheduler; on installs with 100k+ assets the
* full-scan cost showed up in slow-query logs.
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('assets', function (Blueprint $table) {
$table->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');
});
}
};

View File

@ -35,8 +35,8 @@
<th scope="col" class="col-sm-2" data-field="item" data-sortable="true" data-searchable="true" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
<th scope="col" class="col-sm-1" data-field="location" data-sortable="true" data-searchable="true" data-formatter="locationsLinkObjFormatter">{{ trans('general.location') }}</th>
<th scope="col" class="col-sm-2" data-field="created_at" data-formatter="dateDisplayFormatter" data-sortable="true" data-searchable="true">{{ trans('general.last_audit') }}</th>
<th scope="col" class="col-sm-2" data-field="next_audit_date" data-formatter="dateDisplayFormatter">{{ trans('general.next_audit_date') }}</th>
<th scope="col" class="col-sm-1" data-field="days_to_next_audit">{{ trans('general.days_to_next_audit') }}</th>
<th scope="col" class="col-sm-2" data-field="next_audit_date" data-sortable="true" data-formatter="dateDisplayFormatter">{{ trans('general.next_audit_date') }}</th>
<th scope="col" class="col-sm-1" data-field="days_to_next_audit" data-sortable="true">{{ trans('general.days_to_next_audit') }}</th>
<th scope="col" class="col-sm-2" data-field="note" data-sortable="true" data-searchable="true">{{ trans('general.notes') }}</th>
</tr>
</thead>