Added maintenance type strings

This commit is contained in:
snipe
2026-05-18 11:09:48 +01:00
parent a65ae59810
commit 058da6bfef
25 changed files with 753 additions and 25 deletions
+3
View File
@@ -31,6 +31,9 @@ enum ActionType: string
case DeleteSeats = 'delete seats';
case AddSeats = 'add seats';
// Maintenances
case MaintenanceComplete = 'completed';
// File Uploads
case Uploaded = 'uploaded';
case UploadDeleted = 'upload deleted';
@@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\FilterRequest;
use App\Http\Transformers\MaintenanceTypesTransformer;
use App\Models\MaintenanceType;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class MaintenanceTypesController extends Controller
{
public function index(FilterRequest $request): JsonResponse|array
{
$this->authorize('view', MaintenanceType::class);
$types = MaintenanceType::select(['id', 'name', 'created_at', 'updated_at', 'deleted_at']);
if ($request->input('deleted') == 'true') {
$types->onlyTrashed();
}
if ($request->filled('search')) {
$types->where('name', 'LIKE', '%'.$request->input('search').'%');
}
if ($request->filled('name')) {
$types->where('name', '=', $request->input('name'));
}
$offset = ($request->input('offset') > $types->count()) ? $types->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), ['id', 'name', 'created_at', 'updated_at']) ? $request->input('sort') : 'name';
$total = $types->count();
$types = $types->orderBy($sort, $order)->skip($offset)->take($limit)->get();
return (new MaintenanceTypesTransformer)->transformMaintenanceTypes($types, $total);
}
public function show(MaintenanceType $maintenanceType): JsonResponse|array
{
$this->authorize('view', $maintenanceType);
return (new MaintenanceTypesTransformer)->transformMaintenanceType($maintenanceType);
}
public function store(Request $request): JsonResponse
{
$this->authorize('create', MaintenanceType::class);
$type = new MaintenanceType;
$type->name = $request->input('name');
$type->created_by = auth()->id();
if ($type->save()) {
return response()->json(Helper::formatStandardApiResponse('success', (new MaintenanceTypesTransformer)->transformMaintenanceType($type), trans('admin/maintenance_types/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $type->getErrors()));
}
public function update(Request $request, MaintenanceType $maintenanceType): JsonResponse
{
$this->authorize('update', $maintenanceType);
$maintenanceType->name = $request->input('name');
if ($maintenanceType->save()) {
return response()->json(Helper::formatStandardApiResponse('success', (new MaintenanceTypesTransformer)->transformMaintenanceType($maintenanceType), trans('admin/maintenance_types/message.update.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $maintenanceType->getErrors()));
}
public function destroy(MaintenanceType $maintenanceType): JsonResponse
{
$this->authorize('delete', $maintenanceType);
$maintenanceType->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/maintenance_types/message.delete.success')));
}
}
@@ -2,12 +2,14 @@
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;
@@ -39,7 +41,7 @@ class MaintenancesController extends Controller
$maintenances = Maintenance::select('maintenances.*')
->whereHas('asset')
->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.status', 'adminuser', 'asset.assignedTo');
->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')) {
@@ -66,6 +68,14 @@ class MaintenancesController extends Controller
$maintenances->where('asset_maintenance_type', '=', $request->input('asset_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'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $maintenances->count()) ? $maintenances->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
@@ -256,6 +266,33 @@ class MaintenancesController extends Controller
}
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->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->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);
@@ -0,0 +1,72 @@
<?php
namespace App\Http\Controllers;
use App\Models\MaintenanceType;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class MaintenanceTypesController extends Controller
{
public function index(): View
{
$this->authorize('index', MaintenanceType::class);
return view('maintenance-types.index');
}
public function create(): View
{
$this->authorize('create', MaintenanceType::class);
return view('maintenance-types.edit')->with('item', new MaintenanceType);
}
public function store(Request $request): RedirectResponse
{
$this->authorize('create', MaintenanceType::class);
$type = new MaintenanceType;
$type->name = $request->input('name');
$type->created_by = auth()->id();
if ($type->save()) {
return redirect()->route('maintenance-types.index')
->with('success', trans('admin/maintenance_types/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($type->getErrors());
}
public function edit(MaintenanceType $maintenanceType): View
{
$this->authorize('update', $maintenanceType);
return view('maintenance-types.edit')->with('item', $maintenanceType);
}
public function update(Request $request, MaintenanceType $maintenanceType): RedirectResponse
{
$this->authorize('update', $maintenanceType);
$maintenanceType->name = $request->input('name');
if ($maintenanceType->save()) {
return redirect()->route('maintenance-types.index')
->with('success', trans('admin/maintenance_types/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($maintenanceType->getErrors());
}
public function destroy(MaintenanceType $maintenanceType): RedirectResponse
{
$this->authorize('delete', $maintenanceType);
$maintenanceType->delete();
return redirect()->route('maintenance-types.index')
->with('success', trans('admin/maintenance_types/message.delete.success'));
}
}
@@ -2,10 +2,13 @@
namespace App\Http\Controllers;
use App\Enums\ActionType;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Maintenance;
use App\Models\MaintenanceType;
use Carbon\Carbon;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
@@ -57,6 +60,7 @@ class MaintenancesController extends Controller
return view('maintenances/edit')
->with('maintenanceType', Maintenance::getImprovementOptions())
->with('maintenanceTypes', MaintenanceType::orderBy('name')->get())
->with('asset', $asset)
->with('item', new Maintenance);
}
@@ -92,9 +96,11 @@ class MaintenancesController extends Controller
// Save the asset maintenance data
$maintenance->asset_id = $asset->id;
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$maintenance->maintenance_type_id = $request->input('maintenance_type_id');
$maintenance->name = $request->input('name');
$maintenance->start_date = $request->input('start_date');
$maintenance->completion_date = $request->input('completion_date');
$maintenance->responsible_party_id = $request->input('responsible_party_id') ?: auth()->id();
$maintenance->created_by = auth()->id();
if (($maintenance->completion_date !== null)
@@ -141,6 +147,7 @@ class MaintenancesController extends Controller
->with('selected_assets', $maintenance->asset->pluck('id')->toArray())
->with('asset_ids', request()->input('asset_ids', []))
->with('maintenanceType', Maintenance::getImprovementOptions())
->with('maintenanceTypes', MaintenanceType::orderBy('name')->get())
->with('item', $maintenance);
}
@@ -169,9 +176,11 @@ class MaintenancesController extends Controller
$maintenance->cost = $request->input('cost');
$maintenance->notes = $request->input('notes');
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$maintenance->maintenance_type_id = $request->input('maintenance_type_id');
$maintenance->name = $request->input('name');
$maintenance->start_date = $request->input('start_date');
$maintenance->completion_date = $request->input('completion_date');
$maintenance->responsible_party_id = $request->input('responsible_party_id');
$maintenance->url = $request->input('url');
// Todo - put this in a getter/setter?
@@ -253,6 +262,34 @@ class MaintenancesController extends Controller
)->validate();
}
/**
* Mark a maintenance record as complete, logging who completed it and when.
*/
public function complete(Request $request, Maintenance $maintenance): RedirectResponse
{
$this->authorize('update', $maintenance->asset);
if ($maintenance->completed_at) {
return redirect()->back()
->with('warning', trans('admin/maintenances/form.already_complete'));
}
$maintenance->completed_at = now();
$maintenance->completed_by = auth()->id();
$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->logaction(ActionType::MaintenanceComplete);
return redirect()->back()
->with('success', trans('admin/maintenances/message.complete.success'));
}
/**
* Delete an asset maintenance
*
+1
View File
@@ -30,6 +30,7 @@ class ModalController extends Controller
'kit-consumable',
'kit-accessory',
'location',
'maintenance-type',
'manufacturer',
'model',
'statuslabel',
@@ -0,0 +1,37 @@
<?php
namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\MaintenanceType;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Gate;
class MaintenanceTypesTransformer
{
public function transformMaintenanceTypes(Collection $types, int $total): array
{
$array = [];
foreach ($types as $type) {
$array[] = self::transformMaintenanceType($type);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformMaintenanceType(MaintenanceType $type): array
{
return [
'id' => (int) $type->id,
'name' => e($type->name),
'created_at' => Helper::getFormattedDateObject($type->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($type->updated_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($type->deleted_at, 'datetime'),
'available_actions' => [
'update' => Gate::allows('update', $type),
'delete' => $type->isDeletable(),
'restore' => Gate::allows('delete', $type),
],
];
}
}
@@ -82,6 +82,24 @@ class MaintenancesTransformer
'id' => (int) $assetmaintenance->adminuser->id,
'name' => e($assetmaintenance->adminuser->display_name),
] : null,
'maintenance_type' => ($assetmaintenance->maintenanceType) ? [
'id' => (int) $assetmaintenance->maintenanceType->id,
'name' => e($assetmaintenance->maintenanceType->name),
] : null,
'maintenance_type_name' => $assetmaintenance->maintenanceType ? e($assetmaintenance->maintenanceType->name) : null,
'responsible_party' => ($assetmaintenance->responsibleParty) ? [
'id' => (int) $assetmaintenance->responsibleParty->id,
'name' => e($assetmaintenance->responsibleParty->display_name),
] : null,
'checked_out_to_at_creation' => $assetmaintenance->checked_out_to_id ? [
'id' => (int) $assetmaintenance->checked_out_to_id,
'type' => $assetmaintenance->checked_out_to_type,
] : null,
'completed_at' => Helper::getFormattedDateObject($assetmaintenance->completed_at, 'datetime'),
'completed_by' => ($assetmaintenance->completedByUser) ? [
'id' => (int) $assetmaintenance->completedByUser->id,
'name' => e($assetmaintenance->completedByUser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($assetmaintenance->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($assetmaintenance->updated_at, 'datetime'),
'is_warranty' => (bool) $assetmaintenance->is_warranty,
@@ -91,6 +109,7 @@ class MaintenancesTransformer
$permissions_array['available_actions'] = [
'update' => (Gate::allows('update', Asset::class) && ((($assetmaintenance->asset) && $assetmaintenance->asset->deleted_at == ''))) ? true : false,
'delete' => Gate::allows('delete', Asset::class),
'complete' => Gate::allows('update', Asset::class) && ! $assetmaintenance->completed_at,
];
$array += $permissions_array;
+31 -1
View File
@@ -39,7 +39,7 @@ class Maintenance extends SnipeModel implements ICompanyableChild
protected $rules = [
'asset_id' => 'required|integer',
'supplier_id' => 'nullable|integer',
'asset_maintenance_type' => 'required',
'maintenance_type_id' => 'required|integer|exists:maintenance_types,id',
'name' => 'required|max:100',
'is_warranty' => 'boolean',
'start_date' => 'required|date_format:Y-m-d',
@@ -47,6 +47,8 @@ class Maintenance extends SnipeModel implements ICompanyableChild
'notes' => 'string|nullable',
'cost' => 'numeric|nullable|gte:0|max:99999999999999999.99',
'url' => 'nullable|url|max:255',
'responsible_party_id' => 'nullable|integer|exists:users,id',
'completed_by' => 'nullable|integer|exists:users,id',
];
/**
@@ -59,6 +61,7 @@ class Maintenance extends SnipeModel implements ICompanyableChild
'asset_id',
'supplier_id',
'asset_maintenance_type',
'maintenance_type_id',
'is_warranty',
'start_date',
'completion_date',
@@ -66,6 +69,11 @@ class Maintenance extends SnipeModel implements ICompanyableChild
'notes',
'cost',
'url',
'checked_out_to_id',
'checked_out_to_type',
'responsible_party_id',
'completed_at',
'completed_by',
];
use Searchable;
@@ -204,6 +212,28 @@ class Maintenance extends SnipeModel implements ICompanyableChild
->withTrashed();
}
public function maintenanceType()
{
return $this->belongsTo(MaintenanceType::class, 'maintenance_type_id');
}
public function responsibleParty()
{
return $this->belongsTo(User::class, 'responsible_party_id')
->withTrashed();
}
public function completedByUser()
{
return $this->belongsTo(User::class, 'completed_by')
->withTrashed();
}
public function checkedOutTo()
{
return $this->morphTo('checked_out_to');
}
public function getDisplayNameAttribute()
{
return $this->name;
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace App\Models;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Gate;
use Watson\Validating\ValidatingTrait;
class MaintenanceType extends SnipeModel
{
use HasFactory;
use Presentable;
use SoftDeletes;
use ValidatingTrait;
protected $table = 'maintenance_types';
protected $rules = [
'name' => 'required|max:100|unique:maintenance_types,name,NULL,id,deleted_at,NULL',
];
protected $injectUniqueIdentifier = true;
protected $fillable = ['name'];
public function isDeletable(): bool
{
return Gate::allows('delete', $this)
&& ($this->deleted_at == '');
}
public function maintenances()
{
return $this->hasMany(Maintenance::class, 'maintenance_type_id');
}
public function getDisplayNameAttribute(): string
{
return $this->name;
}
}
+26
View File
@@ -5,9 +5,23 @@ namespace App\Observers;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Maintenance;
use App\Models\MaintenanceType;
class MaintenanceObserver
{
/**
* Capture the asset's current checkout state before the maintenance record is saved.
*/
public function creating(Maintenance $maintenance): void
{
if ($maintenance->asset_id && $asset = Asset::find($maintenance->asset_id)) {
$maintenance->checked_out_to_id = $asset->assigned_to;
$maintenance->checked_out_to_type = $asset->assigned_type;
}
$this->syncLegacyMaintenanceType($maintenance);
}
/**
* Listen to the User created event.
*
@@ -15,6 +29,8 @@ class MaintenanceObserver
*/
public function updating(Maintenance $maintenance)
{
$this->syncLegacyMaintenanceType($maintenance);
$changed = [];
foreach ($maintenance->getRawOriginal() as $key => $value) {
@@ -47,6 +63,16 @@ class MaintenanceObserver
$logAction->logaction('update');
}
private function syncLegacyMaintenanceType(Maintenance $maintenance): void
{
if ($maintenance->maintenance_type_id && ! $maintenance->asset_maintenance_type) {
$type = MaintenanceType::find($maintenance->maintenance_type_id);
if ($type) {
$maintenance->asset_maintenance_type = $type->name;
}
}
}
/**
* Listen to the Component created event when
* a new component is created.
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace App\Policies;
class MaintenanceTypePolicy extends SnipePermissionsPolicy
{
protected function columnName()
{
return 'maintenances';
}
}
@@ -0,0 +1,54 @@
<?php
namespace App\Presenters;
class MaintenanceTypePresenter extends Presenter
{
public static function dataTableLayout(): string
{
$layout = [
[
'field' => 'id',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.id'),
'visible' => false,
], [
'field' => 'name',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('general.name'),
'visible' => true,
], [
'field' => 'created_at',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.created_at'),
'visible' => false,
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'updated_at',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.updated_at'),
'visible' => false,
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'actions',
'searchable' => false,
'sortable' => false,
'switchable' => false,
'title' => trans('table.actions'),
'visible' => true,
'formatter' => 'maintenanceTypesActionsFormatter',
'printIgnore' => true,
],
];
return json_encode($layout);
}
}
+42 -2
View File
@@ -88,7 +88,7 @@ class MaintenancesPresenter extends Presenter
'switchable' => true,
'title' => trans('general.model_no'),
'visible' => true,
],[
], [
'field' => 'assigned_to',
'searchable' => true,
'sortable' => true,
@@ -110,11 +110,51 @@ class MaintenancesPresenter extends Presenter
'sortable' => true,
'title' => trans('general.location'),
'formatter' => 'locationsLinkObjFormatter',
], [
'field' => 'maintenance_type_name',
'searchable' => true,
'sortable' => false,
'switchable' => true,
'title' => trans('admin/maintenances/form.asset_maintenance_type'),
'visible' => true,
], [
'field' => 'asset_maintenance_type',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/maintenances/form.asset_maintenance_type'),
'switchable' => true,
'title' => trans('admin/maintenances/form.asset_maintenance_type').' (legacy)',
'visible' => false,
], [
'field' => 'responsible_party',
'searchable' => true,
'sortable' => false,
'switchable' => true,
'title' => trans('admin/maintenances/form.responsible_party'),
'visible' => false,
'formatter' => 'usersLinkObjFormatter',
], [
'field' => 'checked_out_to_at_creation',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'title' => trans('admin/maintenances/form.checked_out_to_at_creation'),
'visible' => false,
], [
'field' => 'completed_at',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('admin/maintenances/form.completed_at'),
'visible' => false,
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'completed_by',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'title' => trans('admin/maintenances/form.completed_by'),
'visible' => false,
'formatter' => 'usersLinkObjFormatter',
], [
'field' => 'start_date',
'searchable' => true,
+3
View File
@@ -16,6 +16,7 @@ use App\Models\Depreciation;
use App\Models\License;
use App\Models\Location;
use App\Models\Maintenance;
use App\Models\MaintenanceType;
use App\Models\Manufacturer;
use App\Models\PredefinedKit;
use App\Models\Statuslabel;
@@ -35,6 +36,7 @@ use App\Policies\DepreciationPolicy;
use App\Policies\LicensePolicy;
use App\Policies\LocationPolicy;
use App\Policies\MaintenancePolicy;
use App\Policies\MaintenanceTypePolicy;
use App\Policies\ManufacturerPolicy;
use App\Policies\PredefinedKitPolicy;
use App\Policies\StatuslabelPolicy;
@@ -71,6 +73,7 @@ class AuthServiceProvider extends ServiceProvider
License::class => LicensePolicy::class,
Location::class => LocationPolicy::class,
Maintenance::class => MaintenancePolicy::class,
MaintenanceType::class => MaintenanceTypePolicy::class,
PredefinedKit::class => PredefinedKitPolicy::class,
Statuslabel::class => StatuslabelPolicy::class,
Supplier::class => SupplierPolicy::class,
+5 -1
View File
@@ -4,6 +4,7 @@ namespace Database\Factories;
use App\Models\Asset;
use App\Models\Maintenance;
use App\Models\MaintenanceType;
use App\Models\Supplier;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
@@ -24,10 +25,13 @@ class MaintenanceFactory extends Factory
*/
public function definition()
{
$maintenanceType = MaintenanceType::factory()->create();
return [
'asset_id' => Asset::factory()->laptopZenbook(),
'supplier_id' => Supplier::factory(),
'asset_maintenance_type' => $this->faker->randomElement(['maintenance', 'repair', 'upgrade']),
'maintenance_type_id' => $maintenanceType->id,
'asset_maintenance_type' => $maintenanceType->name,
'name' => $this->faker->sentence(3),
'start_date' => $this->faker->date(),
'is_warranty' => $this->faker->boolean(),
@@ -0,0 +1,18 @@
<?php
namespace Database\Factories;
use App\Models\MaintenanceType;
use Illuminate\Database\Eloquent\Factories\Factory;
class MaintenanceTypeFactory extends Factory
{
protected $model = MaintenanceType::class;
public function definition(): array
{
return [
'name' => $this->faker->unique()->words(2, true),
];
}
}
@@ -0,0 +1,89 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Create maintenance_types lookup table
Schema::create('maintenance_types', function (Blueprint $table) {
$table->id();
$table->string('name', 100);
$table->unsignedBigInteger('created_by')->nullable();
$table->softDeletes();
$table->timestamps();
});
// Seed with the 8 built-in types
$now = now();
$types = [
'Maintenance',
'Repair',
'Upgrade',
'PAT Test',
'Calibration',
'Software Support',
'Hardware Support',
'Configuration Change',
];
foreach ($types as $name) {
DB::table('maintenance_types')->insert([
'name' => $name,
'created_at' => $now,
'updated_at' => $now,
]);
}
// Add new tracking columns and the maintenance_type FK to maintenances
Schema::table('maintenances', function (Blueprint $table) {
$table->unsignedBigInteger('maintenance_type_id')->nullable();
$table->unsignedBigInteger('checked_out_to_id')->nullable();
$table->string('checked_out_to_type')->nullable();
$table->unsignedBigInteger('responsible_party_id')->nullable();
$table->timestamp('completed_at')->nullable();
$table->unsignedBigInteger('completed_by')->nullable();
});
// Map existing string values to new type IDs (best-effort, case-insensitive)
$typeMap = [
'maintenance' => 'Maintenance',
'repair' => 'Repair',
'upgrade' => 'Upgrade',
'pat_test' => 'PAT Test',
'calibration' => 'Calibration',
'software_support' => 'Software Support',
'hardware_support' => 'Hardware Support',
'configuration_change' => 'Configuration Change',
];
foreach ($typeMap as $oldValue => $newName) {
$newId = DB::table('maintenance_types')->where('name', $newName)->value('id');
if ($newId) {
DB::table('maintenances')
->where('asset_maintenance_type', $oldValue)
->update(['maintenance_type_id' => $newId]);
}
}
}
public function down(): void
{
Schema::table('maintenances', function (Blueprint $table) {
$table->dropColumn([
'maintenance_type_id',
'checked_out_to_id',
'checked_out_to_type',
'responsible_party_id',
'completed_at',
'completed_by',
]);
});
Schema::dropIfExists('maintenance_types');
}
};
@@ -0,0 +1,7 @@
<?php
return [
'maintenance_types' => 'Maintenance Types',
'create' => 'Create Maintenance Type',
'update' => 'Update Maintenance Type',
];
@@ -0,0 +1,22 @@
<?php
return [
'not_found' => 'Maintenance type not found.',
'create' => [
'error' => 'Maintenance type was not created, please try again.',
'success' => 'Maintenance type created successfully.',
],
'update' => [
'error' => 'Maintenance type was not updated, please try again.',
'success' => 'Maintenance type updated successfully.',
],
'delete' => [
'confirm' => 'Are you sure you wish to delete this maintenance type?',
'error' => 'There was an issue deleting this maintenance type. Please try again.',
'success' => 'The maintenance type was deleted successfully.',
],
'complete' => [
'success' => 'Maintenance marked as complete.',
'error' => 'There was an issue marking this maintenance as complete. Please try again.',
],
];
@@ -18,4 +18,8 @@ return [
'asset_maintenance_incomplete' => 'Not Completed Yet',
'warranty' => 'Warranty',
'not_warranty' => 'Not Warranty',
'complete' => [
'success' => 'Maintenance marked as complete.',
'error' => 'There was an issue marking this maintenance as complete. Please try again.',
],
];
+1
View File
@@ -209,6 +209,7 @@ return [
'logout' => 'Logout',
'lookup_by_tag' => 'Lookup by Asset Tag',
'maintenances' => 'Maintenances',
'maintenance_complete' => 'Maintenance Complete',
'manage_api_keys' => 'Manage API keys',
'manufacturer' => 'Manufacturer',
'manufacturers' => 'Manufacturers',
+37 -6
View File
@@ -21,7 +21,7 @@
@section('content')
<div class="row">
<div class="col-md-9">
<div class="col-md-6 col-md-offset-3">
@if ($item->id)
<form class="form-horizontal" method="post" action="{{ route('maintenances.update', $item->id) }}" autocomplete="off" enctype="multipart/form-data">
{{ method_field('PUT') }}
@@ -106,7 +106,36 @@
@include ('partials.forms.edit.maintenance_type')
<!-- Start Date -->
<!-- Responsible Party -->
<div class="form-group {{ $errors->has('responsible_party_id') ? ' has-error' : '' }}">
<label for="responsible_party_id" class="col-md-3 control-label">
{{ trans('admin/maintenances/form.responsible_party') }}
</label>
<div class="col-md-7">
<select
class="js-data-ajax select2"
data-endpoint="users"
name="responsible_party_id"
id="responsible_party_id"
data-placeholder="{{ trans('general.select_user') }}"
aria-label="responsible_party_id"
style="width: 100%;"
>
@if ($item->responsibleParty)
<option value="{{ $item->responsibleParty->id }}" selected="selected">
{{ $item->responsibleParty->display_name }}
</option>
@elseif (! $item->id)
<option value="{{ auth()->id() }}" selected="selected">
{{ auth()->user()->display_name }}
</option>
@endif
</select>
{!! $errors->first('responsible_party_id', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>
</div>
<!-- Start Date -->
<div class="form-group {{ $errors->has('start_date') ? ' has-error' : '' }}">
<label for="start_date" class="col-md-3 control-label">
{{ trans('admin/maintenances/form.start_date') }}
@@ -157,16 +186,18 @@
<!-- Asset Maintenance Cost -->
<div class="form-group {{ $errors->has('cost') ? ' has-error' : '' }}">
<label for="cost" class="col-md-3 control-label">{{ trans('admin/maintenances/form.cost') }}</label>
<div class="col-md-3 text-right">
<div class="input-group">
<div class="col-md-9">
<div class="input-group col-md-5" style="padding-left: 0px;">
<input class="form-control" type="text" inputmode="decimal" pattern="[\d.,]+" name="cost" aria-label="cost" id="cost" value="{{ old('cost', \App\Helpers\Helper::formatCurrencyOutput($item->cost)) }}" maxlength="25" data-msg-pattern="{{ trans('general.purchase_cost_invalid') }}"/>
<span class="input-group-addon">
@if (($item->asset) && ($item->asset->location) && ($item->asset->location->currency!=''))
@if (($item->asset) && ($item->asset->location) && ($item->asset->location->currency != ''))
{{ $item->asset->location->currency }}
@else
{{ $snipeSettings->default_currency }}
@endif
</span>
<input class="form-control" type="text" inputmode="decimal" pattern="[\d.,]+" name="cost" aria-label="cost" id="cost" value="{{ old('cost', \App\Helpers\Helper::formatCurrencyOutput($item->cost)) }}" maxlength="25" data-msg-pattern="{{ trans('general.purchase_cost_invalid') }}"/>
</div>
<div class="col-md-9" style="padding-left: 0px;">
{!! $errors->first('cost', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
<p class="help-block">{{ trans('general.purchase_cost_format_help', ['format' => $snipeSettings->digit_separator]) }}</p>
</div>
+21 -13
View File
@@ -28,23 +28,31 @@
@section('moar_scripts')
@include ('partials.bootstrap-table', ['exportFile' => 'maintenances-export', 'search' => true])
<script nonce="{{ csrf_token() }}">
function maintenanceActions(value, row) {
function maintenancesActionsFormatter(value, row) {
var actions = '<nobr>';
if ((row) && (row.available_actions.update === true)) {
actions += '<a href="{{ config('app.url') }}/hardware/maintenances/' + row.id + '/edit" class="btn btn-sm btn-warning" data-tooltip="true" title="Update"><i class="fas fa-pencil-alt"></i></a>&nbsp;';
}
actions += '</nobr>'
if ((row) && (row.available_actions.delete === true)) {
actions += '<a href="{{ config('app.url') }}/hardware/maintenances/' + row.id + '" '
+ ' class="btn btn-danger btn-sm delete-asset" data-tooltip="true" '
+ ' data-toggle="modal" '
+ ' data-content="{{ trans('general.sure_to_delete') }} ' + row.name + '?" '
+ ' data-title="{{ trans('general.delete') }}" onClick="return false;">'
+ '<i class="fas fa-trash"></i></a></nobr>';
if ((row.available_actions) && (row.available_actions.update === true)) {
actions += '<a href="{{ config('app.url') }}/hardware/maintenances/' + row.id + '/edit" class="actions btn btn-sm btn-warning hidden-print" data-tooltip="true" title="{{ trans('general.update') }}"><x-icon type="edit" class="fa-fw" /><span class="sr-only">{{ trans('general.update') }}</span></a>&nbsp;';
}
if ((row.available_actions) && (row.available_actions.complete === true)) {
actions += '<form style="display:inline;" method="POST" action="{{ config('app.url') }}/hardware/maintenances/' + row.id + '/complete">';
actions += '{{ csrf_field() }}';
actions += '<button type="submit" class="actions btn btn-sm btn-success hidden-print" data-tooltip="true" title="{{ trans('admin/maintenances/form.mark_complete') }}"><x-icon type="checkmark" class="fa-fw" /><span class="sr-only">{{ trans('admin/maintenances/form.mark_complete') }}</span></button>&nbsp;';
actions += '</form>';
}
if ((row.available_actions) && (row.available_actions.delete === true)) {
actions += '<a href="{{ config('app.url') }}/hardware/maintenances/' + row.id + '" '
+ ' class="actions btn btn-danger btn-sm delete-asset hidden-print" data-tooltip="true" '
+ ' data-toggle="modal" data-icon="fa-trash"'
+ ' data-content="{{ trans('general.sure_to_delete') }}: ' + row.name + '?" '
+ ' data-title="{{ trans('general.delete') }}" onClick="return false;">'
+ '<x-icon type="delete" class="fa-fw" /><span class="sr-only">{{ trans('general.delete') }}</span></a>&nbsp;';
}
actions += '</nobr>';
return actions;
}
</script>
@stop
+45 -1
View File
@@ -111,8 +111,36 @@ use Carbon\Carbon;
<x-icon type="x" class="text-danger"/>
{{ trans('general.no') }}
@endif
</x-data-row>
@if ($maintenance->responsibleParty)
<x-data-row :label="trans('admin/maintenances/form.responsible_party')">
{!! $maintenance->responsibleParty->present()->nameUrl() !!}
</x-data-row>
@endif
@if ($maintenance->checked_out_to_id)
<x-data-row :label="trans('admin/maintenances/form.checked_out_to_at_creation')">
{{ $maintenance->checked_out_to_type ? class_basename($maintenance->checked_out_to_type) : '' }}
#{{ $maintenance->checked_out_to_id }}
<p class="help-block">
{{ trans('admin/maintenances/form.checked_out_to_at_creation') }}
</p>
</x-data-row>
@endif
@if ($maintenance->completed_at)
<x-data-row :label="trans('admin/maintenances/form.completed_at')">
{{ Helper::getFormattedDateObject($maintenance->completed_at, 'datetime', false) }}
</x-data-row>
@if ($maintenance->completedByUser)
<x-data-row :label="trans('admin/maintenances/form.completed_by')">
{!! $maintenance->completedByUser->present()->nameUrl() !!}
</x-data-row>
@endif
@endif
</x-page-data>
<!-- ./ definition list content -->
<div class="clearfix"></div>
@@ -178,6 +206,22 @@ use Carbon\Carbon;
<x-slot:buttons>
<x-button.edit :item="$maintenance" :route="route('maintenances.edit', $maintenance->id)" />
@if (! $maintenance->completed_at)
@can('update', $maintenance->asset)
<form method="POST" action="{{ route('maintenances.complete', $maintenance->id) }}" style="display:inline;">
@csrf
<button type="submit" class="btn btn-success btn-sm" data-tooltip="true" title="{{ trans('admin/maintenances/form.mark_complete') }}">
<x-icon type="checkmark" class="fa-fw"/>
<span class="sr-only">{{ trans('admin/maintenances/form.mark_complete') }}</span>
</button>
</form>
@endcan
@else
<span class="btn btn-sm btn-default disabled" data-tooltip="true" title="{{ trans('admin/maintenances/form.already_complete') }}: {{ Helper::getFormattedDateObject($maintenance->completed_at, 'datetime', false) }}">
<x-icon type="checkmark" class="fa-fw"/>
<span class="sr-only">{{ trans('admin/maintenances/form.already_complete') }}</span>
</span>
@endif
<x-button.delete :item="$maintenance" />
</x-slot:buttons>