Files
snipe-it/app/Http/Controllers/Api/MaintenancesController.php

445 lines
17 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Enums\ActionType;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\FilterRequest;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Transformers\ActionlogsTransformer;
use App\Http\Transformers\MaintenancesTransformer;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Maintenance;
use App\Models\Setting;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* This controller handles all actions related to Asset Maintenance for
* the Snipe-IT Asset Management application.
*
* @version v2.0
*/
class MaintenancesController extends Controller
{
/**
* Generates the JSON response for asset maintenances listing view.
*
* @see MaintenancesController::getIndex() method that generates view
*
* @author Vincent Sposato <vincent.sposato@gmail.com>
*
* @version v1.0
*
* @since [v1.8]
*/
public function index(FilterRequest $request): JsonResponse|array
{
$this->authorize('view', Asset::class);
$maintenances = Maintenance::select('maintenances.*')
->whereHas('asset')
->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.status', 'adminuser', 'asset.assignedTo', 'maintenanceType', 'responsibleParty', 'completedByUser');
// This invokes the Searchable model trait scopeTextSearch and will handle input by search or by advanced search filter
if ($request->filled('filter') || $request->filled('search')) {
$maintenances->TextSearch($request->input('filter') ? $request->input('filter') : $request->input('search'));
}
if ($request->filled('asset_id')) {
$maintenances->where('asset_id', '=', $request->input('asset_id'));
}
if ($request->filled('supplier_id')) {
$maintenances->where('maintenances.supplier_id', '=', $request->input('supplier_id'));
}
if ($request->filled('created_by')) {
$maintenances->where('maintenances.created_by', '=', $request->input('created_by'));
}
if ($request->filled('url')) {
$maintenances->where('maintenances.url', '=', $request->input('url'));
}
if ($request->filled('maintenance_type')) {
$maintenances->where('maintenance_type', '=', $request->input('maintenance_type'));
}
if ($request->filled('maintenance_type_id')) {
$maintenances->where('maintenance_type_id', '=', $request->input('maintenance_type_id'));
}
if ($request->filled('responsible_party_id')) {
$maintenances->where('responsible_party_id', '=', $request->input('responsible_party_id'));
}
if ($request->filled('completed')) {
if ($request->input('completed') === 'true') {
$maintenances->completed();
} else {
$maintenances->active();
}
}
if ($request->filled('upcoming_status')) {
$settings = Setting::getSettings();
switch ($request->input('upcoming_status')) {
case 'due':
$maintenances->dueForCompletion($settings);
break;
case 'overdue':
$maintenances->overdueForCompletion();
break;
case 'due-or-overdue':
$maintenances->dueOrOverdueForCompletion($settings);
break;
}
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $maintenances->count()) ? $maintenances->count() : app('api_offset_value');
$limit = app('api_limit_value');
$allowed_columns = [
'id',
'name',
'asset_maintenance_time',
'cost',
'start_date',
'completion_date',
'completed_at',
'notes',
'asset_tag',
'asset_name',
'serial',
'created_by',
'supplier',
'location',
'is_warranty',
'status_label',
'model',
'model_number',
'maintenance_type',
];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
switch ($sort) {
case 'created_by':
$maintenances = $maintenances->orderByCreatedBy($order);
break;
case 'supplier':
$maintenances = $maintenances->orderBySupplier($order);
break;
case 'asset_tag':
$maintenances = $maintenances->orderByTag($order);
break;
case 'asset_name':
$maintenances = $maintenances->orderByAssetName($order);
break;
case 'model':
$maintenances = $maintenances->orderByAssetModelName($order);
break;
case 'model_number':
$maintenances = $maintenances->orderByAssetModelNumber($order);
break;
case 'serial':
$maintenances = $maintenances->orderByAssetSerial($order);
break;
case 'location':
$maintenances = $maintenances->orderLocationName($order);
break;
case 'status_label':
$maintenances = $maintenances->orderStatusName($order);
break;
case 'maintenance_type':
$maintenances = $maintenances->orderByMaintenanceType($order);
break;
case 'completed_at':
$maintenances = $maintenances->orderByCompletedAt($order);
break;
default:
$maintenances = $maintenances->orderBy($sort, $order);
break;
}
$total = $maintenances->count();
$maintenances = $maintenances->skip($offset)->take($limit)->get();
if (request()->input('format') == 'flat') {
return (new MaintenancesTransformer)->transformMaintenancesFlat($maintenances, $total);
}
return (new MaintenancesTransformer)->transformMaintenances($maintenances, $total);
}
/**
* Validates and stores the new asset maintenance
*
* @see MaintenancesController::getCreate() method for the form
*
* @author Vincent Sposato <vincent.sposato@gmail.com>
*
* @version v1.0
*
* @since [v1.8]
*/
public function store(ImageUploadRequest $request): JsonResponse|array
{
$this->authorize('update', Asset::class);
$isBulk = $request->has('asset_ids');
$assetIds = $isBulk
? array_values(array_filter((array) $request->input('asset_ids')))
: [$request->input('asset_id')];
$created = new EloquentCollection;
$errors = [];
foreach ($assetIds as $assetId) {
$asset = Asset::find($assetId);
if (! $asset) {
$errors[] = trans('general.item_not_found', ['item_type' => trans('general.asset'), 'id' => $assetId]);
continue;
}
if (! Company::isCurrentUserHasAccess($asset)) {
$errors[] = trans('general.action_permission_denied', ['item_type' => trans('general.asset'), 'id' => $assetId, 'action' => trans('general.create')]);
continue;
}
$maintenance = new Maintenance;
$maintenance->fill($request->except(['asset_id', 'asset_ids']));
$maintenance->asset_id = $assetId;
$maintenance->created_by = auth()->id();
$request->handleImages($maintenance);
if ($maintenance->save()) {
$created->push($maintenance->fresh());
} else {
$errors[] = $maintenance->getErrors();
}
}
if ($isBulk) {
if ($created->isEmpty()) {
return response()->json(Helper::formatStandardApiResponse('error', null, count($errors) === 1 ? $errors[0] : $errors));
}
return response()->json(Helper::formatStandardApiResponse(
'success',
(new MaintenancesTransformer)->transformMaintenances($created, $created->count()),
trans('admin/maintenances/message.create.success')
));
}
// Single asset_id path — backward compatible response shape
if ($created->isNotEmpty()) {
return response()->json(Helper::formatStandardApiResponse('success', $created->first(), trans('admin/maintenances/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, ! empty($errors) ? $errors[0] : null));
}
/**
* Validates and stores an update to an asset maintenance
*
* @author A. Gianotto <snipe@snipe.net>
*
* @param int $id
* @param int $request
*
* @version v1.0
*
* @since [v4.0]
*/
public function update(Request $request, $id): JsonResponse|array
{
$this->authorize('update', Asset::class);
if ($maintenance = Maintenance::with('asset')->find($id)) {
// The asset this maintenance is attached to is not valid or has been deleted
if (! $maintenance->asset) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('general.asset'), 'id' => $id])));
}
// Can this user manage the existing asset?
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.action_permission_denied', ['item_type' => trans('admin/maintenances/general.maintenance'), 'id' => $id, 'action' => trans('general.edit')])));
}
// If the request changes asset_id, verify the new asset is accessible
if ($request->filled('asset_id') && (int) $request->input('asset_id') !== $maintenance->asset_id) {
$newAsset = Asset::find($request->input('asset_id'));
if (! $newAsset) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('general.asset'), 'id' => $request->input('asset_id')])));
}
if (! Company::isCurrentUserHasAccess($newAsset)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.action_permission_denied', ['item_type' => trans('general.asset'), 'id' => $request->input('asset_id'), 'action' => trans('general.edit')])), 403);
}
$maintenance->fill($request->except('asset_id'));
$maintenance->asset_id = $newAsset->id;
} else {
$maintenance->fill($request->except('asset_id'));
}
if ($maintenance->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/maintenances/message.edit.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $maintenance->getErrors()));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('admin/maintenances/general.maintenance'), 'id' => $id])));
}
/**
* Delete an asset maintenance
*
* @author A. Gianotto <snipe@snipe.net>
*
* @param int $maintenanceId
*
* @version v1.0
*
* @since [v4.0]
*/
public function destroy($maintenanceId): JsonResponse|array
{
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
$maintenance = Maintenance::findOrFail($maintenanceId);
$maintenance->delete();
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/maintenances/message.delete.success')));
}
/**
* View an asset maintenance
*
* @author A. Gianotto <snipe@snipe.net>
*
* @param int $maintenanceId
*
* @version v1.0
*
* @since [v4.0]
*/
public function show($maintenanceId): JsonResponse|array
{
$this->authorize('view', Asset::class);
$maintenance = Maintenance::findOrFail($maintenanceId);
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot view a maintenance for that asset'));
}
return (new MaintenancesTransformer)->transformMaintenance($maintenance);
}
public function complete(Request $request, Maintenance $maintenance): JsonResponse
{
$this->authorize('update', Asset::class);
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.action_permission_denied', ['item_type' => trans('admin/maintenances/general.maintenance'), 'id' => $maintenance->id, 'action' => trans('admin/maintenances/form.mark_complete')])));
}
if ($maintenance->completed_at) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/maintenances/form.already_complete')));
}
$maintenance->completed_at = now();
$maintenance->completed_by = auth()->id();
$maintenance->asset_maintenance_time = (int) $maintenance->created_at->diffInDays(now(), true);
$maintenance->saveQuietly();
$logAction = new Actionlog;
$logAction->item_type = Maintenance::class;
$logAction->item_id = $maintenance->id;
$logAction->target_type = Asset::class;
$logAction->target_id = $maintenance->asset_id;
$logAction->created_by = auth()->id();
$logAction->note = $request->input('note');
$logAction->logaction(ActionType::MaintenanceComplete);
return response()->json(Helper::formatStandardApiResponse('success', (new MaintenancesTransformer)->transformMaintenance($maintenance->fresh()), trans('admin/maintenances/message.complete.success')));
}
public function history(Request $request, Maintenance $maintenance): JsonResponse|array
{
$this->authorize('history', $maintenance);
$historyQuery = $maintenance->getHistory($request);
$total = (clone $historyQuery)->count();
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
$limit = app('api_limit_value');
$history = (clone $historyQuery)->skip($offset)->take($limit)->get();
return response()->json((new ActionlogsTransformer)->transformActionlogs($history, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
}
public function notesIndex(Maintenance $maintenance): JsonResponse
{
$this->authorize('journal', $maintenance);
$notes = Actionlog::with('user:id,username')
->where('item_type', Maintenance::class)
->where('item_id', $maintenance->id)
->where('action_type', 'note added')
->orderBy('created_at', 'desc')
->get(['id', 'created_at', 'note', 'created_by', 'item_id', 'item_type', 'action_type']);
$notesArray = $notes->map(fn ($note) => [
'id' => $note->id,
'created_at' => $note->created_at,
'note' => $note->note,
'created_by' => $note->created_by,
'username' => $note->user?->username,
'item_id' => $note->item_id,
'item_type' => $note->item_type,
'action_type' => $note->action_type,
]);
return response()->json(Helper::formatStandardApiResponse('success', ['notes' => $notesArray, 'maintenance_id' => $maintenance->id]));
}
public function notesStore(Request $request, Maintenance $maintenance): JsonResponse
{
$this->authorize('update', $maintenance);
if (! $request->filled('note')) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('validation.required', ['attribute' => 'note'])), 422);
}
$logaction = new Actionlog;
$logaction->item_type = Maintenance::class;
$logaction->created_by = auth()->id();
$logaction->item_id = $maintenance->id;
$logaction->note = $request->input('note');
if ($logaction->logaction('note added')) {
return response()->json(Helper::formatStandardApiResponse('success', ['note' => $logaction->note, 'item_id' => $maintenance->id], trans('general.note_added')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'Something went wrong'), 500);
}
}