mirror of
https://github.com/snipe/snipe-it.git
synced 2026-08-18 11:15:42 +00:00
374 lines
12 KiB
PHP
374 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Helpers\Helper;
|
|
use App\Models\Builders\MaintenanceQueryBuilder;
|
|
use App\Models\Traits\CompanyableChildTrait;
|
|
use App\Models\Traits\HasUploads;
|
|
use App\Models\Traits\Loggable;
|
|
use App\Models\Traits\Searchable;
|
|
use App\Presenters\MaintenancesPresenter;
|
|
use App\Presenters\Presentable;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Watson\Validating\ValidatingTrait;
|
|
|
|
/**
|
|
* Model for Asset Maintenances.
|
|
*
|
|
* @version v1.0
|
|
*
|
|
* @property-read \App\Models\Asset|null $asset
|
|
* @property-read \App\Models\Actionlog|null $assetlog
|
|
* @property-read \App\Models\Supplier|null $supplier
|
|
* @property-read \App\Models\MaintenanceType|null $maintenanceType
|
|
* @property-read \App\Models\User|null $adminuser
|
|
* @property-read \App\Models\User|null $responsibleParty
|
|
* @property-read \App\Models\User|null $completedByUser
|
|
* @property-read \Illuminate\Database\Eloquent\Model|null $item
|
|
*/
|
|
class Maintenance extends SnipeModel implements ICompanyableChild
|
|
{
|
|
use CompanyableChildTrait;
|
|
use HasFactory;
|
|
use HasUploads;
|
|
use Loggable, Presentable;
|
|
use SoftDeletes;
|
|
use ValidatingTrait;
|
|
|
|
protected $presenter = MaintenancesPresenter::class;
|
|
|
|
protected $with = ['asset', 'asset.company'];
|
|
|
|
protected $table = 'maintenances';
|
|
|
|
protected $rules = [
|
|
// item_id is the polymorphic FK. Legacy asset_id keeps working
|
|
// via the accessor / mutator pair below. Both mutators set
|
|
// item_id so the validator's item_id rule catches either.
|
|
'item_id' => 'required|integer',
|
|
'item_type' => 'required|string',
|
|
'supplier_id' => 'nullable|integer',
|
|
'maintenance_type_id' => 'required|integer|exists:maintenance_types,id',
|
|
'name' => 'required|max:100',
|
|
'is_warranty' => 'boolean',
|
|
'start_date' => 'required|date',
|
|
'expected_completion_date' => 'date|nullable|after_or_equal:start_date',
|
|
'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',
|
|
'completed_at' => 'nullable|date|after_or_equal:start_date|before_or_equal:now',
|
|
];
|
|
|
|
protected $casts = [
|
|
'start_date' => 'datetime',
|
|
'expected_completion_date' => 'datetime',
|
|
'completed_at' => 'datetime',
|
|
];
|
|
|
|
/**
|
|
* The attributes that are mass assignable.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $fillable = [
|
|
'name',
|
|
// Polymorphic item FK. Legacy asset_id is kept fillable so
|
|
// Maintenance::create(['asset_id' => ...]) still works via the
|
|
// setAssetIdAttribute mutator that forwards to item_id +
|
|
// item_type=Asset. New callers should pass item_id + item_type
|
|
// directly.
|
|
'item_id',
|
|
'item_type',
|
|
'asset_id',
|
|
'supplier_id',
|
|
'asset_maintenance_type',
|
|
'maintenance_type_id',
|
|
'is_warranty',
|
|
'start_date',
|
|
'expected_completion_date',
|
|
// Legacy alias for expected_completion_date. Kept fillable so an
|
|
// API v1 caller doing Maintenance::create(['completion_date' =>
|
|
// ...]) or ->update(...) still routes through the mutator that
|
|
// forwards to the renamed column.
|
|
'completion_date',
|
|
'asset_maintenance_time',
|
|
'notes',
|
|
'cost',
|
|
'url',
|
|
'checked_out_to_id',
|
|
'checked_out_to_type',
|
|
'responsible_party_id',
|
|
'completed_at',
|
|
'completed_by',
|
|
];
|
|
|
|
use Searchable;
|
|
|
|
/**
|
|
* The attributes that should be included when searching the model.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $searchableAttributes =
|
|
[
|
|
'name',
|
|
'notes',
|
|
'cost',
|
|
'start_date',
|
|
'expected_completion_date',
|
|
];
|
|
|
|
/**
|
|
* The relations and their attributes that should be included when searching the model.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $searchableRelations = [
|
|
'asset' => ['name', 'asset_tag', 'serial'],
|
|
'asset.model' => ['name', 'model_number'],
|
|
'asset.supplier' => ['name'],
|
|
'asset.status' => ['name'],
|
|
'asset.company' => ['name'],
|
|
'asset.location' => ['name'],
|
|
'asset.defaultLoc' => ['name'],
|
|
'supplier' => ['name'],
|
|
'adminuser' => ['first_name', 'last_name', 'display_name'],
|
|
'maintenanceType' => ['name'],
|
|
];
|
|
|
|
public function getCompanyableParents()
|
|
{
|
|
return ['asset'];
|
|
}
|
|
|
|
/**
|
|
* getImprovementOptions
|
|
*
|
|
* @return array
|
|
*
|
|
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
|
*
|
|
* @version v1.0
|
|
*/
|
|
public static function getImprovementOptions()
|
|
{
|
|
return [
|
|
trans('admin/maintenances/general.maintenance') => trans('admin/maintenances/general.maintenance'),
|
|
trans('admin/maintenances/general.repair') => trans('admin/maintenances/general.repair'),
|
|
trans('admin/maintenances/general.upgrade') => trans('admin/maintenances/general.upgrade'),
|
|
trans('admin/maintenances/general.pat_test') => trans('admin/maintenances/general.pat_test'),
|
|
trans('admin/maintenances/general.calibration') => trans('admin/maintenances/general.calibration'),
|
|
trans('admin/maintenances/general.software_support') => trans('admin/maintenances/general.software_support'),
|
|
trans('admin/maintenances/general.hardware_support') => trans('admin/maintenances/general.hardware_support'),
|
|
trans('admin/maintenances/general.configuration_change') => trans('admin/maintenances/general.configuration_change'),
|
|
];
|
|
}
|
|
|
|
public function isDeletable()
|
|
{
|
|
return Gate::allows('delete', $this);
|
|
}
|
|
|
|
public function setIsWarrantyAttribute($value)
|
|
{
|
|
if ($value == '') {
|
|
$value = 0;
|
|
}
|
|
$this->attributes['is_warranty'] = $value;
|
|
}
|
|
|
|
public function setCostAttribute($value)
|
|
{
|
|
$value = Helper::ParseCurrency($value);
|
|
if ($value == 0) {
|
|
$value = null;
|
|
}
|
|
$this->attributes['cost'] = $value;
|
|
}
|
|
|
|
public function setNotesAttribute($value)
|
|
{
|
|
if ($value == '') {
|
|
$value = null;
|
|
}
|
|
$this->attributes['notes'] = $value;
|
|
}
|
|
|
|
public function setExpectedCompletionDateAttribute($value)
|
|
{
|
|
if ($value == '' || $value == '0000-00-00' || $value == '0000-00-00 00:00:00') {
|
|
$value = null;
|
|
}
|
|
$this->attributes['expected_completion_date'] = $value;
|
|
}
|
|
|
|
/**
|
|
* Legacy alias for the old `completion_date` field name (renamed to
|
|
* `expected_completion_date` to match the "Expected Completion" UI
|
|
* label). Kept so API v1 consumers that still write to the old key
|
|
* — either as a mass-assigned Eloquent create/update or as raw HTTP
|
|
* form field — keep working. The API transformer emits both keys
|
|
* on read (see MaintenancesTransformer) for the same reason.
|
|
*/
|
|
public function setCompletionDateAttribute($value)
|
|
{
|
|
$this->setExpectedCompletionDateAttribute($value);
|
|
}
|
|
|
|
public function getCompletionDateAttribute()
|
|
{
|
|
return $this->expected_completion_date;
|
|
}
|
|
|
|
/**
|
|
* asset
|
|
* Get asset for this improvement
|
|
*
|
|
* @return mixed
|
|
*
|
|
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
|
*
|
|
* @version v1.0
|
|
*/
|
|
/**
|
|
* Polymorphic parent - an Asset (historically), and potentially
|
|
* an Accessory or other checkoutable model going forward. Every
|
|
* new caller should use ->item; ->asset() below stays as a
|
|
* backward-compat alias that returns the parent only when it IS
|
|
* an Asset (returns null for other item_types).
|
|
*/
|
|
public function item()
|
|
{
|
|
return $this->morphTo()->withTrashed();
|
|
}
|
|
|
|
/**
|
|
* Legacy alias for ->item kept so existing blades, transformers,
|
|
* and controllers using $maintenance->asset or ->with('asset')
|
|
* keep working during the transition to polymorphic items.
|
|
* No item_type WHERE guard here because it would attach to the
|
|
* assets-table subquery during eager loading (Eloquent scopes the
|
|
* belongsTo child query at the parent side) and reference a
|
|
* non-existent maintenances.item_type column from the assets
|
|
* table context. Every existing maintenance has item_type = Asset
|
|
* per the rename migration's backfill, so the plain belongsTo
|
|
* still returns the right rows today. Once accessories actually
|
|
* carry maintenances, callers should switch to ->item.
|
|
*/
|
|
public function asset()
|
|
{
|
|
return $this->belongsTo(Asset::class, 'item_id')
|
|
->withTrashed();
|
|
}
|
|
|
|
/**
|
|
* Legacy accessor for the old asset_id column. Returns item_id
|
|
* only when the polymorphic parent is an Asset so it doesn't
|
|
* misrepresent a maintenance attached to an accessory as an
|
|
* asset-scoped one.
|
|
*/
|
|
public function getAssetIdAttribute()
|
|
{
|
|
return $this->item_type === Asset::class ? $this->attributes['item_id'] ?? null : null;
|
|
}
|
|
|
|
/**
|
|
* Legacy mutator - Maintenance::create(['asset_id' => X]) still
|
|
* works. Sets item_id + pins item_type to Asset since callers
|
|
* using asset_id are, by definition, targeting an Asset.
|
|
*/
|
|
public function setAssetIdAttribute($value): void
|
|
{
|
|
$this->attributes['item_id'] = $value;
|
|
$this->attributes['item_type'] = Asset::class;
|
|
}
|
|
|
|
/**
|
|
* Get the maintenance logs
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
*
|
|
* @since [v8.2.2]
|
|
*
|
|
* @return Relation
|
|
*/
|
|
public function assetlog()
|
|
{
|
|
return $this->hasMany(Actionlog::class, 'item_id')
|
|
->where('item_type', '=', self::class)
|
|
->orderBy('created_at', 'desc')
|
|
->withTrashed();
|
|
}
|
|
|
|
public function supplier()
|
|
{
|
|
return $this->belongsTo(Supplier::class, 'supplier_id')
|
|
->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');
|
|
}
|
|
|
|
/**
|
|
* Fields published to the calendar_events index table via the
|
|
* HasCalendarEvents trait. A single range event per maintenance
|
|
* anchored on start_date -> expected_completion_date. The
|
|
* completed_at column is intentionally NOT its own event today -
|
|
* "completed" is a state, not a scheduled item, and the read
|
|
* path can flip a completed marker via the source model. Add
|
|
* more entries here if a maintenance ever needs multiple event
|
|
* markers on the calendar.
|
|
*/
|
|
public function calendarEventDefinitions(): array
|
|
{
|
|
return [
|
|
[
|
|
'field' => 'start_date',
|
|
'event_type' => 'maintenance.start',
|
|
'end_field' => 'expected_completion_date',
|
|
'all_day' => true,
|
|
],
|
|
];
|
|
}
|
|
|
|
public function journal()
|
|
{
|
|
return $this->assetlog()->where('action_type', '=', 'note added');
|
|
}
|
|
|
|
public function getDisplayNameAttribute()
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
public function newEloquentBuilder($query): MaintenanceQueryBuilder
|
|
{
|
|
return new MaintenanceQueryBuilder($query);
|
|
}
|
|
}
|