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:
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user