Add approval workflow for movement, disposal, and maintenance
This commit is contained in:
27
app/Http/Controllers/ApprovalController.php
Normal file
27
app/Http/Controllers/ApprovalController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AssetApprovalRequest;
|
||||
use App\Services\Workflow\ApprovalService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ApprovalController extends Controller
|
||||
{
|
||||
public function __construct(private readonly ApprovalService $approvals)
|
||||
{
|
||||
}
|
||||
|
||||
public function approve(Request $request, AssetApprovalRequest $approval): RedirectResponse
|
||||
{
|
||||
$this->approvals->approve($approval, $request->input('notes'));
|
||||
return back()->with('success', 'Permintaan disetujui.');
|
||||
}
|
||||
|
||||
public function reject(Request $request, AssetApprovalRequest $approval): RedirectResponse
|
||||
{
|
||||
$this->approvals->reject($approval, $request->input('notes'));
|
||||
return back()->with('success', 'Permintaan ditolak.');
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,17 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetMaintenance;
|
||||
use App\Services\Asset\AssetService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AssetMaintenanceController extends Controller
|
||||
{
|
||||
public function __construct(private readonly AssetService $assets)
|
||||
{
|
||||
}
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
$maintenances = AssetMaintenance::with('asset')->latest()->paginate(config('system.ui.table_page_size', 20));
|
||||
@@ -26,11 +31,11 @@ class AssetMaintenanceController extends Controller
|
||||
'description' => ['required', 'string', 'max:255'],
|
||||
'vendor' => ['nullable', 'string', 'max:255'],
|
||||
'cost' => ['nullable', 'numeric', 'min:0'],
|
||||
'status' => ['required', 'string'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
AssetMaintenance::create($data);
|
||||
$asset = Asset::findOrFail($data['asset_id']);
|
||||
$this->assets->maintenance($asset, $data);
|
||||
|
||||
return back()->with('success', 'Data perawatan berhasil dicatat.');
|
||||
}
|
||||
|
||||
59
app/Models/AssetApprovalRequest.php
Normal file
59
app/Models/AssetApprovalRequest.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AssetApprovalRequest extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $fillable = [
|
||||
'approvable_id',
|
||||
'approvable_type',
|
||||
'type',
|
||||
'status',
|
||||
'current_step',
|
||||
'required_steps',
|
||||
'requested_by',
|
||||
'approved_by',
|
||||
'approved_at',
|
||||
'rejected_by',
|
||||
'rejected_at',
|
||||
'decision_notes',
|
||||
'metadata',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'metadata' => 'array',
|
||||
'approved_at' => 'datetime',
|
||||
'rejected_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function approvable()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function requester()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'requested_by');
|
||||
}
|
||||
|
||||
public function approver()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'approved_by');
|
||||
}
|
||||
|
||||
public function rejector()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'rejected_by');
|
||||
}
|
||||
|
||||
public function isPending(): bool
|
||||
{
|
||||
return $this->status === 'pending';
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,20 @@ class AssetDisposal extends Model
|
||||
'reversed_at',
|
||||
'reversed_by',
|
||||
'reversed_notes',
|
||||
'status',
|
||||
'requested_by',
|
||||
'approved_by',
|
||||
'approved_at',
|
||||
'rejected_by',
|
||||
'rejected_at',
|
||||
'decision_notes',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'disposed_at' => 'datetime',
|
||||
'reversed_at' => 'datetime',
|
||||
'approved_at' => 'datetime',
|
||||
'rejected_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function asset()
|
||||
@@ -48,4 +57,9 @@ class AssetDisposal extends Model
|
||||
{
|
||||
return $this->belongsTo(Department::class, 'previous_department_id');
|
||||
}
|
||||
|
||||
public function approval()
|
||||
{
|
||||
return $this->morphOne(AssetApprovalRequest::class, 'approvable');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,28 @@ class AssetMaintenance extends Model
|
||||
'cost',
|
||||
'status',
|
||||
'notes',
|
||||
'requested_by',
|
||||
'approved_by',
|
||||
'approved_at',
|
||||
'rejected_by',
|
||||
'rejected_at',
|
||||
'decision_notes',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'performed_at' => 'datetime',
|
||||
'cost' => 'decimal:2',
|
||||
'approved_at' => 'datetime',
|
||||
'rejected_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function asset()
|
||||
{
|
||||
return $this->belongsTo(Asset::class);
|
||||
}
|
||||
|
||||
public function approval()
|
||||
{
|
||||
return $this->morphOne(AssetApprovalRequest::class, 'approvable');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,19 @@ class AssetMovement extends Model
|
||||
'notes',
|
||||
'moved_by',
|
||||
'performed_at',
|
||||
'status',
|
||||
'requested_by',
|
||||
'approved_by',
|
||||
'approved_at',
|
||||
'rejected_by',
|
||||
'rejected_at',
|
||||
'decision_notes',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'performed_at' => 'datetime',
|
||||
'approved_at' => 'datetime',
|
||||
'rejected_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function asset()
|
||||
@@ -61,4 +70,9 @@ class AssetMovement extends Model
|
||||
{
|
||||
return $this->belongsTo(AssetUser::class, 'to_asset_user_id');
|
||||
}
|
||||
|
||||
public function approval()
|
||||
{
|
||||
return $this->morphOne(AssetApprovalRequest::class, 'approvable');
|
||||
}
|
||||
}
|
||||
|
||||
39
app/Notifications/ApprovalDecisionNotification.php
Normal file
39
app/Notifications/ApprovalDecisionNotification.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\AssetApprovalRequest;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class ApprovalDecisionNotification extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(
|
||||
private readonly AssetApprovalRequest $approval,
|
||||
private readonly bool $approved,
|
||||
private readonly ?string $notes = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
$status = $this->approved ? 'disetujui' : 'ditolak';
|
||||
$approvable = $this->approval->approvable;
|
||||
$assetCode = method_exists($approvable, 'asset') ? optional($approvable->asset)->code : null;
|
||||
|
||||
return (new MailMessage)
|
||||
->subject("Permintaan {$this->approval->type} {$status}")
|
||||
->line("Permintaan {$this->approval->type} Anda telah {$status}.")
|
||||
->lineIf($assetCode, "Asset: {$assetCode}")
|
||||
->lineIf($this->notes, "Catatan: {$this->notes}")
|
||||
->action('Buka Dashboard', url('/dashboard/index'));
|
||||
}
|
||||
}
|
||||
37
app/Notifications/ApprovalRequestedNotification.php
Normal file
37
app/Notifications/ApprovalRequestedNotification.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\AssetApprovalRequest;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class ApprovalRequestedNotification extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(private readonly AssetApprovalRequest $approval)
|
||||
{
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
$approvable = $this->approval->approvable;
|
||||
$title = ucfirst($this->approval->type) . ' membutuhkan persetujuan';
|
||||
$assetCode = method_exists($approvable, 'asset') ? optional($approvable->asset)->code : null;
|
||||
|
||||
return (new MailMessage)
|
||||
->subject($title)
|
||||
->line($title)
|
||||
->lineIf($assetCode, "Asset: {$assetCode}")
|
||||
->line('Diminta oleh: '.optional($this->approval->requester)->name)
|
||||
->line('Klik tombol di bawah untuk membuka dashboard dan meninjau.')
|
||||
->action('Buka Dashboard', url('/dashboard/index'));
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ use App\Models\AssetMovement;
|
||||
use App\Models\AssetDisposal;
|
||||
use App\Models\AssetAudit;
|
||||
use App\Models\AssetChangelog;
|
||||
use App\Models\AssetMaintenance;
|
||||
use App\Services\Logging\ActivityLogger;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -17,7 +19,10 @@ use SimpleSoftwareIO\QrCode\Facades\QrCode;
|
||||
|
||||
class AssetService
|
||||
{
|
||||
public function __construct(private readonly SystemSettingService $settings)
|
||||
public function __construct(
|
||||
private readonly SystemSettingService $settings,
|
||||
private readonly ActivityLogger $logger
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -123,7 +128,9 @@ class AssetService
|
||||
*/
|
||||
public function move(Asset $asset, array $data): AssetMovement
|
||||
{
|
||||
return DB::transaction(function () use ($asset, $data) {
|
||||
$requireApproval = config('system.workflow.require_approval.movement', false);
|
||||
|
||||
return DB::transaction(function () use ($asset, $data, $requireApproval) {
|
||||
$movement = AssetMovement::create([
|
||||
'asset_id' => $asset->id,
|
||||
'from_location_id' => $asset->asset_location_id,
|
||||
@@ -135,13 +142,23 @@ class AssetService
|
||||
'notes' => $data['notes'] ?? null,
|
||||
'moved_by' => auth()->id(),
|
||||
'performed_at' => $data['performed_at'] ?? now(),
|
||||
'status' => $requireApproval ? 'pending' : 'approved',
|
||||
'requested_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
$asset->update([
|
||||
'asset_location_id' => $data['to_location_id'] ?? $asset->asset_location_id,
|
||||
'department_id' => $data['to_department_id'] ?? $asset->department_id,
|
||||
'asset_user_id' => $data['to_asset_user_id'] ?? $asset->asset_user_id,
|
||||
]);
|
||||
if (!$requireApproval) {
|
||||
$asset->update([
|
||||
'asset_location_id' => $data['to_location_id'] ?? $asset->asset_location_id,
|
||||
'department_id' => $data['to_department_id'] ?? $asset->department_id,
|
||||
'asset_user_id' => $data['to_asset_user_id'] ?? $asset->asset_user_id,
|
||||
]);
|
||||
$movement->update([
|
||||
'approved_by' => auth()->id(),
|
||||
'approved_at' => now(),
|
||||
]);
|
||||
} else {
|
||||
app(\App\Services\Workflow\ApprovalService::class)->create('movement', $movement);
|
||||
}
|
||||
|
||||
$this->logHistory($asset, 'movement', 'Perpindahan aset', $movement->toArray());
|
||||
|
||||
@@ -154,23 +171,34 @@ class AssetService
|
||||
*/
|
||||
public function dispose(Asset $asset, array $data): AssetDisposal
|
||||
{
|
||||
return DB::transaction(function () use ($asset, $data) {
|
||||
$requireApproval = config('system.workflow.require_approval.disposal', false);
|
||||
|
||||
return DB::transaction(function () use ($asset, $data, $requireApproval) {
|
||||
$disposal = AssetDisposal::create([
|
||||
'asset_id' => $asset->id,
|
||||
'reason' => $data['reason'] ?? null,
|
||||
'notes' => $data['notes'] ?? null,
|
||||
'disposed_by' => auth()->id(),
|
||||
'disposed_at' => $data['disposed_at'] ?? now(),
|
||||
'disposed_by' => $requireApproval ? null : auth()->id(),
|
||||
'disposed_at' => $requireApproval ? null : ($data['disposed_at'] ?? now()),
|
||||
'previous_status_id' => $asset->asset_status_id,
|
||||
'previous_location_id' => $asset->asset_location_id,
|
||||
'previous_department_id' => $asset->department_id,
|
||||
'status' => $requireApproval ? 'pending' : 'approved',
|
||||
'requested_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
// Ubah status aset ke DISPOSED bila tersedia
|
||||
$disposedStatus = AssetStatus::where('code', 'DISPOSED')->first();
|
||||
$asset->update([
|
||||
'asset_status_id' => $disposedStatus?->id ?? $asset->asset_status_id,
|
||||
]);
|
||||
if (!$requireApproval) {
|
||||
$disposedStatus = AssetStatus::where('code', 'DISPOSED')->first();
|
||||
$asset->update([
|
||||
'asset_status_id' => $disposedStatus?->id ?? $asset->asset_status_id,
|
||||
]);
|
||||
$disposal->update([
|
||||
'approved_by' => auth()->id(),
|
||||
'approved_at' => now(),
|
||||
]);
|
||||
} else {
|
||||
app(\App\Services\Workflow\ApprovalService::class)->create('disposal', $disposal);
|
||||
}
|
||||
|
||||
$this->logHistory($asset, 'disposal', 'Aset didispose', $disposal->toArray());
|
||||
|
||||
@@ -227,4 +255,35 @@ class AssetService
|
||||
return $audit;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Catat maintenance aset; jika butuh persetujuan, status pending.
|
||||
*/
|
||||
public function maintenance(Asset $asset, array $data): AssetMaintenance
|
||||
{
|
||||
$requireApproval = config('system.workflow.require_approval.maintenance', false);
|
||||
|
||||
return DB::transaction(function () use ($asset, $data, $requireApproval) {
|
||||
$maintenance = AssetMaintenance::create([
|
||||
'asset_id' => $asset->id,
|
||||
'performed_at' => $data['performed_at'] ?? now(),
|
||||
'description' => $data['description'] ?? null,
|
||||
'vendor' => $data['vendor'] ?? null,
|
||||
'cost' => $data['cost'] ?? null,
|
||||
'status' => $requireApproval ? 'pending' : 'approved',
|
||||
'notes' => $data['notes'] ?? null,
|
||||
'requested_by' => auth()->id(),
|
||||
'approved_by' => $requireApproval ? null : auth()->id(),
|
||||
'approved_at' => $requireApproval ? null : now(),
|
||||
]);
|
||||
|
||||
if ($requireApproval) {
|
||||
app(\App\Services\Workflow\ApprovalService::class)->create('maintenance', $maintenance);
|
||||
}
|
||||
|
||||
$this->logHistory($asset, 'maintenance', 'Perawatan aset', $maintenance->toArray());
|
||||
|
||||
return $maintenance;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
153
app/Services/Workflow/ApprovalService.php
Normal file
153
app/Services/Workflow/ApprovalService.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Workflow;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetApprovalRequest;
|
||||
use App\Models\AssetDisposal;
|
||||
use App\Models\AssetMaintenance;
|
||||
use App\Models\AssetMovement;
|
||||
use App\Services\Logging\ActivityLogger;
|
||||
use App\Models\Role;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Notifications\ApprovalRequestedNotification;
|
||||
use App\Notifications\ApprovalDecisionNotification;
|
||||
|
||||
class ApprovalService
|
||||
{
|
||||
public function __construct(private readonly ActivityLogger $logger)
|
||||
{
|
||||
}
|
||||
|
||||
public function create(string $type, $model): AssetApprovalRequest
|
||||
{
|
||||
$approval = AssetApprovalRequest::create([
|
||||
'approvable_id' => $model->getKey(),
|
||||
'approvable_type' => get_class($model),
|
||||
'type' => $type,
|
||||
'status' => 'pending',
|
||||
'requested_by' => auth()->id(),
|
||||
'current_step' => 1,
|
||||
'required_steps' => config("system.workflow.required_steps.{$type}", 1),
|
||||
]);
|
||||
$this->notifyApprovers($approval);
|
||||
return $approval;
|
||||
}
|
||||
|
||||
public function approve(AssetApprovalRequest $approval, ?string $notes = null): void
|
||||
{
|
||||
DB::transaction(function () use ($approval, $notes) {
|
||||
if ($approval->status !== 'pending') {
|
||||
return;
|
||||
}
|
||||
$approval->update([
|
||||
'status' => 'approved',
|
||||
'approved_by' => auth()->id(),
|
||||
'approved_at' => now(),
|
||||
'decision_notes' => $notes,
|
||||
'current_step' => $approval->required_steps,
|
||||
]);
|
||||
|
||||
$this->applyApprovedAction($approval);
|
||||
|
||||
$this->logger->audit([
|
||||
'action' => 'approval_approved',
|
||||
'model' => class_basename($approval->approvable_type),
|
||||
'model_id' => $approval->approvable_id,
|
||||
'changes' => ['approval_id' => $approval->id, 'notes' => $notes],
|
||||
]);
|
||||
|
||||
$this->notifyDecision($approval, true, $notes);
|
||||
});
|
||||
}
|
||||
|
||||
public function reject(AssetApprovalRequest $approval, ?string $notes = null): void
|
||||
{
|
||||
if ($approval->status !== 'pending') {
|
||||
return;
|
||||
}
|
||||
|
||||
$approval->update([
|
||||
'status' => 'rejected',
|
||||
'rejected_by' => auth()->id(),
|
||||
'rejected_at' => now(),
|
||||
'decision_notes' => $notes,
|
||||
]);
|
||||
|
||||
$this->logger->audit([
|
||||
'action' => 'approval_rejected',
|
||||
'model' => class_basename($approval->approvable_type),
|
||||
'model_id' => $approval->approvable_id,
|
||||
'changes' => ['approval_id' => $approval->id, 'notes' => $notes],
|
||||
]);
|
||||
|
||||
$this->notifyDecision($approval, false, $notes);
|
||||
}
|
||||
|
||||
private function applyApprovedAction(AssetApprovalRequest $approval): void
|
||||
{
|
||||
$approvable = $approval->approvable;
|
||||
|
||||
if ($approvable instanceof AssetMovement) {
|
||||
$asset = $approvable->asset;
|
||||
$asset->update([
|
||||
'asset_location_id' => $approvable->to_location_id ?? $asset->asset_location_id,
|
||||
'department_id' => $approvable->to_department_id ?? $asset->department_id,
|
||||
'asset_user_id' => $approvable->to_asset_user_id ?? $asset->asset_user_id,
|
||||
]);
|
||||
$approvable->update([
|
||||
'status' => 'approved',
|
||||
'approved_by' => auth()->id(),
|
||||
'approved_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($approvable instanceof AssetDisposal) {
|
||||
$disposedStatus = optional($approvable->asset)->status?->code === 'DISPOSED'
|
||||
? $approvable->asset->asset_status_id
|
||||
: \App\Models\AssetStatus::where('code', 'DISPOSED')->value('id');
|
||||
|
||||
$approvable->asset->update([
|
||||
'asset_status_id' => $disposedStatus ?? $approvable->asset->asset_status_id,
|
||||
]);
|
||||
$approvable->update([
|
||||
'status' => 'approved',
|
||||
'approved_by' => auth()->id(),
|
||||
'approved_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($approvable instanceof AssetMaintenance) {
|
||||
$approvable->update([
|
||||
'status' => 'approved',
|
||||
'approved_by' => auth()->id(),
|
||||
'approved_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function notifyApprovers(AssetApprovalRequest $approval): void
|
||||
{
|
||||
$approvers = Role::where('name', 'super-admin')->first()?->users ?? collect();
|
||||
if ($approvers->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Notification::send($approvers, new ApprovalRequestedNotification($approval));
|
||||
$this->logger->audit([
|
||||
'action' => 'notify_approval_request',
|
||||
'changes' => ['approval_id' => $approval->id],
|
||||
]);
|
||||
}
|
||||
|
||||
private function notifyDecision(AssetApprovalRequest $approval, bool $approved, ?string $notes = null): void
|
||||
{
|
||||
if ($approval->requested_by) {
|
||||
$requester = \App\Models\User::find($approval->requested_by);
|
||||
if ($requester) {
|
||||
$requester->notify(new ApprovalDecisionNotification($approval, $approved, $notes));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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('asset_approval_requests', function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
$table->uuidMorphs('approvable');
|
||||
$table->string('type'); // movement, disposal, maintenance
|
||||
$table->string('status')->default('pending'); // pending, approved, rejected
|
||||
$table->unsignedTinyInteger('current_step')->default(1);
|
||||
$table->unsignedTinyInteger('required_steps')->default(1);
|
||||
$table->uuid('requested_by')->nullable();
|
||||
$table->uuid('approved_by')->nullable();
|
||||
$table->timestamp('approved_at')->nullable();
|
||||
$table->uuid('rejected_by')->nullable();
|
||||
$table->timestamp('rejected_at')->nullable();
|
||||
$table->text('decision_notes')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::table('asset_movements', function (Blueprint $table) {
|
||||
$table->string('status')->default('approved')->after('performed_at');
|
||||
$table->uuid('requested_by')->nullable()->after('status');
|
||||
$table->uuid('approved_by')->nullable()->after('requested_by');
|
||||
$table->timestamp('approved_at')->nullable()->after('approved_by');
|
||||
$table->uuid('rejected_by')->nullable()->after('approved_at');
|
||||
$table->timestamp('rejected_at')->nullable()->after('rejected_by');
|
||||
$table->text('decision_notes')->nullable()->after('rejected_at');
|
||||
});
|
||||
|
||||
Schema::table('asset_disposals', function (Blueprint $table) {
|
||||
$table->string('status')->default('approved')->after('reversed_notes');
|
||||
$table->uuid('requested_by')->nullable()->after('status');
|
||||
$table->uuid('approved_by')->nullable()->after('requested_by');
|
||||
$table->timestamp('approved_at')->nullable()->after('approved_by');
|
||||
$table->uuid('rejected_by')->nullable()->after('approved_at');
|
||||
$table->timestamp('rejected_at')->nullable()->after('rejected_by');
|
||||
$table->text('decision_notes')->nullable()->after('rejected_at');
|
||||
});
|
||||
|
||||
Schema::table('asset_maintenances', function (Blueprint $table) {
|
||||
$table->string('status')->default('approved')->after('notes');
|
||||
$table->uuid('requested_by')->nullable()->after('status');
|
||||
$table->uuid('approved_by')->nullable()->after('requested_by');
|
||||
$table->timestamp('approved_at')->nullable()->after('approved_by');
|
||||
$table->uuid('rejected_by')->nullable()->after('approved_at');
|
||||
$table->timestamp('rejected_at')->nullable()->after('rejected_by');
|
||||
$table->text('decision_notes')->nullable()->after('rejected_at');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('asset_maintenances', function (Blueprint $table) {
|
||||
$table->dropColumn(['status','requested_by','approved_by','approved_at','rejected_by','rejected_at','decision_notes']);
|
||||
});
|
||||
Schema::table('asset_disposals', function (Blueprint $table) {
|
||||
$table->dropColumn(['status','requested_by','approved_by','approved_at','rejected_by','rejected_at','decision_notes']);
|
||||
});
|
||||
Schema::table('asset_movements', function (Blueprint $table) {
|
||||
$table->dropColumn(['status','requested_by','approved_by','approved_at','rejected_by','rejected_at','decision_notes']);
|
||||
});
|
||||
Schema::dropIfExists('asset_approval_requests');
|
||||
}
|
||||
};
|
||||
@@ -101,6 +101,9 @@ Route::middleware(['auth','maintenance.readonly','session.timeout','audit.reques
|
||||
|
||||
Route::resource('asset-maintenances', AssetMaintenanceController::class)->only(['index','store','update','destroy'])
|
||||
->middleware('permission:maintenance.manage');
|
||||
|
||||
Route::post('approvals/{approval}/approve', [\App\Http\Controllers\ApprovalController::class, 'approve'])->name('approvals.approve')->middleware('permission:assets.manage');
|
||||
Route::post('approvals/{approval}/reject', [\App\Http\Controllers\ApprovalController::class, 'reject'])->name('approvals.reject')->middleware('permission:assets.manage');
|
||||
});
|
||||
|
||||
Route::get('login', [AuthController::class, 'showLogin'])->name('login');
|
||||
|
||||
Reference in New Issue
Block a user