Add asset financial fields, vendor contracts, and maintenance SLA inputs

This commit is contained in:
Arya Dwi Putra
2025-12-02 16:18:30 +07:00
parent 7a5138cca9
commit ac0be8a19e
7 changed files with 165 additions and 0 deletions

View File

@@ -27,7 +27,13 @@ class Asset extends Model
'warranty_id',
'purchase_date',
'warranty_end',
'warranty_reminder_sent_at',
'cost',
'depreciation_method',
'useful_life_months',
'residual_value',
'capex_opex',
'vendor_contract_id',
'qr_token',
'qr_path',
'metadata',
@@ -36,6 +42,8 @@ class Asset extends Model
protected $casts = [
'purchase_date' => 'date',
'warranty_end' => 'date',
'warranty_reminder_sent_at' => 'datetime',
'residual_value' => 'decimal:2',
'metadata' => 'array',
];
@@ -84,6 +92,11 @@ class Asset extends Model
return $this->belongsTo(Warranty::class);
}
public function vendorContract()
{
return $this->belongsTo(VendorContract::class);
}
public function histories()
{
return $this->hasMany(AssetHistory::class);
@@ -169,4 +182,26 @@ class Asset extends Model
return $departmentMatch || $locationMatch;
}
public function bookValue(?\Carbon\Carbon $asOf = null): float
{
$asOf = $asOf ?: now();
$cost = (float) ($this->cost ?? 0);
$residual = (float) ($this->residual_value ?? 0);
$lifeMonths = (int) ($this->useful_life_months ?? 0);
if ($cost <= 0 || $lifeMonths <= 0 || !$this->purchase_date) {
return $cost;
}
$monthsUsed = max(0, $this->purchase_date->diffInMonths($asOf));
if ($this->depreciation_method === 'diminishing') {
$rate = pow($residual / max($cost, 1), 1 / max($lifeMonths, 1));
$value = $cost * pow($rate, $monthsUsed);
return max($value, $residual);
}
$monthlyDep = ($cost - $residual) / $lifeMonths;
$value = $cost - ($monthlyDep * $monthsUsed);
return max($value, $residual);
}
}

View File

@@ -24,6 +24,8 @@ class AssetMaintenance extends Model
'rejected_by',
'rejected_at',
'decision_notes',
'sla_response_hours',
'sla_resolution_hours',
];
protected $casts = [
@@ -31,6 +33,8 @@ class AssetMaintenance extends Model
'cost' => 'decimal:2',
'approved_at' => 'datetime',
'rejected_at' => 'datetime',
'sla_response_hours' => 'integer',
'sla_resolution_hours' => 'integer',
];
public function asset()

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class VendorContract extends Model
{
use HasFactory, HasUuids;
protected $fillable = [
'vendor_name',
'contract_number',
'start_date',
'end_date',
'sla_response_hours',
'sla_resolution_hours',
'notes',
];
protected $casts = [
'start_date' => 'date',
'end_date' => 'date',
];
public function assets()
{
return $this->hasMany(Asset::class);
}
}

View File

@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('assets', function (Blueprint $table) {
$table->string('depreciation_method')->default('straight_line')->after('cost');
$table->unsignedInteger('useful_life_months')->nullable()->after('depreciation_method');
$table->decimal('residual_value', 15, 2)->nullable()->after('useful_life_months');
$table->string('capex_opex')->nullable()->after('residual_value'); // capex|opex
$table->foreignUuid('vendor_contract_id')->nullable()->after('capex_opex')->constrained('vendor_contracts')->nullOnDelete();
$table->timestamp('warranty_reminder_sent_at')->nullable()->after('warranty_end');
});
}
public function down(): void
{
Schema::table('assets', function (Blueprint $table) {
$table->dropForeign(['vendor_contract_id']);
$table->dropColumn([
'depreciation_method',
'useful_life_months',
'residual_value',
'capex_opex',
'vendor_contract_id',
'warranty_reminder_sent_at',
]);
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('vendor_contracts', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('vendor_name');
$table->string('contract_number')->nullable();
$table->date('start_date')->nullable();
$table->date('end_date')->nullable();
$table->unsignedInteger('sla_response_hours')->nullable();
$table->unsignedInteger('sla_resolution_hours')->nullable();
$table->text('notes')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('vendor_contracts');
}
};

View File

@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('asset_maintenances', function (Blueprint $table) {
$table->unsignedInteger('sla_response_hours')->nullable()->after('status');
$table->unsignedInteger('sla_resolution_hours')->nullable()->after('sla_response_hours');
});
}
public function down(): void
{
Schema::table('asset_maintenances', function (Blueprint $table) {
$table->dropColumn(['sla_response_hours', 'sla_resolution_hours']);
});
}
};

View File

@@ -33,6 +33,14 @@
<option value="completed" selected>Completed</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">SLA Response (jam)</label>
<input type="number" name="sla_response_hours" class="form-control" placeholder="cth: 4">
</div>
<div class="col-md-4">
<label class="form-label">SLA Resolution (jam)</label>
<input type="number" name="sla_resolution_hours" class="form-control" placeholder="cth: 24">
</div>
<div class="col-md-12">
<label class="form-label">Catatan</label>
<textarea name="notes" class="form-control" rows="2" placeholder="Catatan tambahan"></textarea>