refactor: move verification logic from VerificationService to the Verification Filament page.

This commit is contained in:
Yoga Pangestu 2026-01-30 14:23:52 +07:00
parent 9005f9cbec
commit f06d8d100c
14 changed files with 398 additions and 464 deletions

View File

@ -8,8 +8,8 @@
use App\Filament\Support\CheerfulNotification;
use App\Models\User;
use App\Models\VerificationRequest;
use App\Models\VerificationReview;
use App\Notifications\BroadcastNotification;
use App\Services\VerificationService;
use BackedEnum;
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
use Filament\Actions\Action;
@ -21,6 +21,7 @@
use Filament\Pages\Page;
use Filament\Support\Enums\Width;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use UnitEnum;
@ -42,15 +43,8 @@ class AdminVerification extends Page implements HasActions, HasForms
protected static ?string $title = 'Verifikasi Rekanan';
protected VerificationService $verificationService;
protected string $view = 'filament.pages.admin-verification';
public function boot(): void
{
$this->verificationService = app(VerificationService::class);
}
public static function getNavigationBadge(): ?string
{
return VerificationRequest::query()->pending()->count();
@ -96,7 +90,7 @@ public function getStatusCounts(): array
*/
public function getCompanyName(VerificationRequest $request): string
{
return $this->verificationService->getCompanyName($request);
return $request->company?->name ?? 'Data tidak ditemukan';
}
/**
@ -116,7 +110,27 @@ public function getDetailUrl(VerificationRequest $request): ?string
*/
public function getVerificationSummary(VerificationRequest $request): array
{
return $this->verificationService->getVerificationSummary($request);
$company = $request->company;
$media = $company?->partnerMedia;
$journalists = $media?->journalists ?? collect();
return [
'company' => [
'name' => $company?->name ?? 'Belum diisi',
'email' => $company?->email ?? '-',
'exists' => $company !== null,
],
'media' => [
'name' => $media?->name ?? 'Belum diisi',
'type' => $media?->type?->getLabel() ?? '-',
'exists' => $media !== null,
],
'journalists' => [
'count' => $journalists->count(),
'names' => $journalists->pluck('name')->take(3)->toArray(),
'exists' => $journalists->count() > 0,
],
];
}
/**
@ -242,7 +256,28 @@ protected function processReview(int $requestId, DecisionAdmin $decision, ?strin
}
try {
$this->verificationService->processReview($verificationRequest, $decision, $note);
DB::beginTransaction();
// Create review record
VerificationReview::create([
'verification_request_id' => $verificationRequest->id,
'reviewer_id' => auth()->id(),
'decision' => $decision,
'note' => $note,
]);
// Update verification request status
$newStatus = match ($decision) {
DecisionAdmin::APPROVED => VerificationStatus::APPROVED,
DecisionAdmin::NEED_REVISION => VerificationStatus::NEED_REVISION,
DecisionAdmin::REJECTED => VerificationStatus::REJECTED,
};
$verificationRequest->update([
'status' => $newStatus,
]);
DB::commit();
$actionLabel = match ($decision) {
DecisionAdmin::APPROVED => 'disetujui',
@ -286,6 +321,7 @@ protected function processReview(int $requestId, DecisionAdmin $decision, ?strin
]));
}
} catch (\Throwable $e) {
DB::rollBack();
Log::error('Verification processing failed', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),

View File

@ -3,6 +3,7 @@
namespace App\Filament\Pages;
use App\Enums\DataChangeStatus;
use App\Enums\VerificationStatus;
use App\Filament\Resources\Manage\DataChanges\DataChangesResource;
use App\Filament\Support\CheerfulNotification;
use App\Models\Company as CompanyModel;
@ -37,6 +38,8 @@ class Company extends Page implements HasForms
public array $originalDocuments = [];
public bool $isVerificationStage = false;
protected static string|UnitEnum|null $navigationGroup = 'Kelola';
protected static string|BackedEnum|null $navigationIcon = Heroicon::BuildingOffice2;
@ -63,6 +66,11 @@ public function mount(): void
return;
}
$this->isVerificationStage = in_array($company->verificationRequest?->status, [
VerificationStatus::PENDING,
VerificationStatus::REJECTED,
]) || $company->verificationRequest()->pending()->exists();
$this->verificationRequest = $company->verificationRequest()
->latest()
->first();
@ -283,7 +291,8 @@ public function saveAction(): Action
->label('Alasan')
->placeholder('Jelaskan alasan perubahan data ini...')
->autocomplete(false)
->autofocus(),
->autofocus()
->required(),
]
: []
)

View File

@ -6,6 +6,7 @@
use App\Enums\MediaClassification;
use App\Enums\MediaType;
use App\Enums\RoleEnum;
use App\Enums\VerificationStatus;
use App\Filament\Resources\Manage\DataChanges\DataChangesResource;
use App\Filament\Support\CheerfulNotification;
use App\Models\Company;
@ -58,6 +59,8 @@ class Media extends Page implements HasForms
public bool $restricted = false;
public bool $isVerificationStage = false;
protected static ?string $slug = 'manage/media';
protected static ?string $title = 'Media';
@ -84,6 +87,11 @@ public function mount(): void
$this->company = $company;
$this->isVerificationStage = in_array($company?->verificationRequest?->status, [
VerificationStatus::PENDING,
VerificationStatus::REJECTED,
]) || $company->verificationRequest()->pending()->exists();
$verificationRequest = $this->company->verificationRequest()
->latest()
->first();
@ -235,10 +243,10 @@ public function saveAction(): Action
? [
Textarea::make('change_reason')
->label('Alasan')
->required()
->placeholder('Jelaskan alasan perubahan data ini...')
->autocomplete(false)
->autofocus(),
->autofocus()
->required(),
]
: []
)

View File

@ -3,12 +3,13 @@
namespace App\Filament\Pages;
use App\Enums\VerificationStatus;
use App\Filament\Pages\Company as PagesCompany;
use App\Filament\Resources\Manage\Journalists\Pages\ManageJournalists;
use App\Filament\Resources\Manage\Partners\PartnerResource;
use App\Models\Company;
use App\Models\User;
use App\Models\VerificationRequest;
use App\Notifications\BroadcastNotification;
use App\Services\VerificationService;
use BackedEnum;
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
use Filament\Actions\Action;
@ -19,6 +20,7 @@
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Support\Icons\Heroicon;
use Illuminate\Support\Collection;
use UnitEnum;
class Verification extends Page implements HasActions, HasForms
@ -41,15 +43,8 @@ class Verification extends Page implements HasActions, HasForms
public ?VerificationRequest $verificationRequest = null;
protected VerificationService $verificationService;
protected string $view = 'filament.pages.verification';
public function boot(): void
{
$this->verificationService = app(VerificationService::class);
}
public function mount(): void
{
$user = auth()->user();
@ -69,14 +64,35 @@ public function mount(): void
*/
public function getVerificationProgress(): array
{
$progress = $this->verificationService->getVerificationProgress(
$this->company,
$this->verificationRequest
);
$isComplete = $this->isDataComplete();
$missingData = $this->getMissingData();
$status = $this->verificationRequest?->status;
$progress = [
'currentStep' => $this->resolveStep(),
'isDataComplete' => $isComplete,
'missingData' => $missingData,
'canSubmit' => $this->canSubmit(),
'verification' => $this->verificationRequest,
'status' => [
'label' => $status?->getLabel() ?? 'Belum Mengajukan',
'color' => $status?->getColor() ?? 'gray',
],
'isApproved' => $status === VerificationStatus::APPROVED,
'isRejected' => $status === VerificationStatus::REJECTED,
'isPending' => $status === VerificationStatus::PENDING,
'needsRevision' => $status === VerificationStatus::NEED_REVISION,
'latestNotes' => $this->getLatestReviewNotes(),
'reviewHistory' => $this->getReviewHistory(),
'company' => $this->company,
'partnerMedia' => $this->company?->partnerMedia,
'journalists' => $this->company?->partnerMedia?->journalists ?? collect(),
'steps' => $this->getSteps($isComplete),
];
// Add additional data for view
$progress['missingDataButtons'] = $this->verificationService->getMissingDataButtons($this->company);
$progress['alert'] = $this->verificationService->getAlertData($this->verificationRequest);
$progress['missingDataButtons'] = $this->getMissingDataButtons();
$progress['alert'] = $this->getAlertData();
$progress['lastSubmittedAt'] = $this->verificationRequest?->updated_at?->translatedFormat('l, d M Y H:i');
return $progress;
@ -120,10 +136,7 @@ public function submitVerificationAction(): Action
$isRevision = $this->verificationRequest && $this->verificationRequest->status === VerificationStatus::NEED_REVISION;
$this->verificationRequest = $this->verificationService->submitVerification(
$this->company,
$this->verificationRequest
);
$this->verificationRequest = $this->submitVerification();
Notification::make()
->title('Berhasil Diajukan! 🚀')
@ -151,6 +164,261 @@ public function submitVerificationAction(): Action
});
}
/**
* Check if all required data is complete for verification
*/
protected function isDataComplete(): bool
{
return $this->company !== null
&& $this->company->partnerMedia !== null
&& $this->company->partnerMedia?->journalists !== null
&& $this->company->partnerMedia->journalists->count() > 0;
}
/**
* Get missing data items for verification
*/
protected function getMissingData(): array
{
$missing = [];
if (! $this->company) {
$missing[] = 'Data Perusahaan';
}
if ($this->company && ! $this->company->partnerMedia) {
$missing[] = 'Data Media';
}
if ($this->company && $this->company->partnerMedia && ($this->company->partnerMedia?->journalists === null || $this->company->partnerMedia?->journalists?->count() === 0)) {
$missing[] = 'Data Jurnalis (minimal 1)';
}
return $missing;
}
/**
* Check if user can submit verification
*/
protected function canSubmit(): bool
{
if (! $this->isDataComplete()) {
return false;
}
// No verification yet - can submit
if (! $this->verificationRequest) {
return true;
}
// Can resubmit only if status is NEED_REVISION
return $this->verificationRequest->status === VerificationStatus::NEED_REVISION;
}
/**
* Determine the current step based on status
*/
protected function resolveStep(): int
{
if (! $this->isDataComplete()) {
return 1; // Data belum lengkap
}
if (! $this->verificationRequest) {
return 1; // Belum submit
}
return match ($this->verificationRequest->status) {
VerificationStatus::PENDING,
VerificationStatus::NEED_REVISION => 2, // In review or needs revision
VerificationStatus::APPROVED,
VerificationStatus::REJECTED => 3, // Final decision
default => 1, // Draft / not submitted yet
};
}
/**
* Get the latest review notes
*/
protected function getLatestReviewNotes(): ?string
{
if (! $this->verificationRequest) {
return null;
}
$latestReview = $this->verificationRequest->reviews()
->where(fn ($q) => $q->needRevision()->orWhere(fn ($q) => $q->rejected()))
->latest()
->first();
return $latestReview?->note;
}
/**
* Get all reviews history
*/
protected function getReviewHistory(): Collection
{
if (! $this->verificationRequest) {
return collect();
}
return $this->verificationRequest->reviews()
->with('reviewer')
->latest()
->get();
}
/**
* Submit unified verification
*/
protected function submitVerification(): VerificationRequest
{
if ($this->verificationRequest && $this->verificationRequest->status === VerificationStatus::NEED_REVISION) {
// Resubmit - update existing request
$this->verificationRequest->update([
'status' => VerificationStatus::PENDING,
]);
return $this->verificationRequest->fresh(['reviews.reviewer']);
}
// New submission
return VerificationRequest::create([
'company_id' => $this->company->id,
'submitted_by' => auth()->id(),
'status' => VerificationStatus::PENDING,
])->load(['reviews.reviewer']);
}
/**
* Get alert data for verification status
*/
protected function getAlertData(): ?array
{
if (! $this->verificationRequest) {
return null;
}
$status = $this->verificationRequest->status;
$latestNotes = $this->getLatestReviewNotes();
if ($status === VerificationStatus::NEED_REVISION && $latestNotes) {
return [
'type' => 'revision',
'icon' => 'exclamation-triangle',
'title' => 'Catatan Revisi dari Admin',
'message' => $latestNotes,
'color' => 'warning',
];
}
if ($status === VerificationStatus::REJECTED && $latestNotes) {
return [
'type' => 'rejected',
'icon' => 'x-circle',
'title' => 'Pengajuan Ditolak',
'message' => $latestNotes,
'color' => 'danger',
];
}
if ($status === VerificationStatus::APPROVED) {
return [
'type' => 'approved',
'icon' => 'check-circle',
'title' => 'Verifikasi Diterima',
'message' => 'Verifikasi Anda telah diterima dan disetujui oleh admin. Sekarang Anda sudah resmi terverifikasi dan bekerja sama dengan kami!',
'color' => 'success',
];
}
if ($status === VerificationStatus::PENDING) {
return [
'type' => 'pending',
'icon' => 'clock',
'title' => 'Menunggu Verifikasi',
'message' => 'Data Anda sedang dalam proses verifikasi oleh admin. Mohon tunggu.',
'color' => 'info',
'animate' => true,
];
}
return null;
}
/**
* Get missing data buttons configuration
*/
protected function getMissingDataButtons(): array
{
$buttons = [];
if (! $this->company) {
$buttons[] = [
'label' => 'Lengkapi Data Perusahaan',
'url' => PagesCompany::getUrl(),
'icon' => 'building-office-2',
'color' => 'warning',
];
}
if ($this->company && ! $this->company->partnerMedia) {
$buttons[] = [
'label' => 'Lengkapi Data Media',
'url' => Media::getUrl(),
'icon' => 'newspaper',
'color' => 'warning',
];
}
if ($this->company && $this->company->partnerMedia && ($this->company->partnerMedia->journalists === null || $this->company->partnerMedia->journalists->count() === 0)) {
$buttons[] = [
'label' => 'Lengkapi Data Jurnalis',
'url' => ManageJournalists::getUrl(),
'icon' => 'user-circle',
'color' => 'warning',
];
}
return $buttons;
}
/**
* Get steps array for stepper
*/
protected function getSteps(bool $isComplete): array
{
$status = $this->verificationRequest?->status;
return [
[
'title' => 'Pengajuan',
'description' => $isComplete
? 'Data sudah lengkap, silakan ajukan verifikasi'
: 'Silakan lengkapi data',
],
[
'title' => 'Verifikasi',
'description' => match ($status) {
VerificationStatus::NEED_REVISION => 'Perlu perbaikan data',
VerificationStatus::PENDING => 'Sedang diverifikasi admin',
default => 'Menunggu pengajuan',
},
],
[
'title' => 'Selesai',
'description' => match ($status) {
VerificationStatus::APPROVED => 'Verifikasi disetujui',
VerificationStatus::REJECTED => 'Verifikasi ditolak',
default => 'Menunggu keputusan',
},
],
];
}
public function getViewData(): array
{
return [

View File

@ -56,7 +56,7 @@ protected function setUp(): void
// Notify User
$record->user?->notify(new BroadcastNotification([
'title' => 'Permohonan Perubahan Ditolak 🛑',
'body' => "Halo! Mohon maaf, permohonan perubahan data Anda ({$record->change_reason}) ditolak oleh admin. Alasan: {$data['rejection_reason']} 😔",
'body' => "Halo! Mohon maaf, permohonan perubahan data Anda untuk {$record->change_reason} ditolak oleh admin. Alasan: {$data['rejection_reason']} 😔",
]));
})
->visible(fn (DataChangeRequest $record): bool => $record->status === DataChangeStatus::PENDING && ! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value))

View File

@ -44,13 +44,13 @@ class DataChangesResource extends Resource
protected static ?string $slug = 'manage/data-changes';
public static function getNavigationBadge(): string
public static function getNavigationBadge(): ?string
{
return static::getModel()::pending()
->when(auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value), function (Builder $query): void {
$query->where('user_id', auth()->id());
})
->count();
if (auth()->user()->hasRole(RoleEnum::PERUSAHAAN)) {
return null;
}
return static::getModel()::pending()->count();
}
public static function infolist(Schema $schema): Schema

View File

@ -2,7 +2,6 @@
namespace App\Filament\Resources\Manage\Journalists\Actions;
use App\Enums\VerificationStatus;
use App\Filament\Actions\Cheerful\CreateAction;
use App\Filament\Resources\Manage\DataChanges\DataChangesResource;
use App\Filament\Support\CheerfulNotification;
@ -26,7 +25,7 @@ protected function setUp(): void
->modalSubmitActionLabel('Simpan')
->modalCancelActionLabel('Batal')
->successNotification(null)
->extraModalFooterActions(fn(CreateAction $action): array => [
->extraModalFooterActions(fn (CreateAction $action): array => [
$action->makeModalSubmitAction('createAnother', arguments: ['another' => true])
->label('Simpan dan Tambah Lagi'),
])
@ -100,10 +99,10 @@ protected function setUp(): void
User::superAdmin()
->get()
->each(function ($admin) use ($dataChangeRequest): void {
->each(function ($admin) use ($dataChangeRequest, $user): void {
$admin->notify(new BroadcastNotification([
'title' => 'Ada Penambahan Jurnalis Baru ✨',
'body' => 'Halooo Admin! 👋 ' . $user->name . ' baru saja menambahkan jurnalis baru. Yuk, cek detailnya! 🚀',
'body' => 'Halooo Admin! 👋 '.$user->name.' baru saja menambahkan jurnalis baru. Yuk, cek detailnya! 🚀',
'action' => [
Action::make('view')
->label('Lihat')

View File

@ -23,9 +23,9 @@ protected function setUp(): void
$this->successNotification(null);
$this->modalHeading(fn() => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED ? 'Ajukan Penghapusan Jurnalis' : 'Hapus Jurnalis');
$this->modalHeading(fn () => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED ? 'Ajukan Penghapusan Jurnalis' : 'Hapus Jurnalis');
$this->schema(fn() => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED ? [
$this->schema(fn () => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED ? [
Textarea::make('deletion_reason')
->label('Alasan Penghapusan')
->placeholder('Jelaskan alasan mengapa jurnalis ini dihapus...')
@ -69,7 +69,7 @@ protected function setUp(): void
->each(function ($admin) use ($user, $record, $dataChangeRequest): void {
$admin->notify(new BroadcastNotification([
'title' => 'Ada Pengajuan Penghapusan Jurnalis ✨',
'body' => 'Halooo Admin! 👋 ' . $user->name . ' baru saja mengajukan penghapusan jurnalis ' . $record->name . '. Yuk, cek detailnya! 🚀',
'body' => 'Halooo Admin! 👋 '.$user->name.' baru saja mengajukan penghapusan jurnalis '.$record->name.'. Yuk, cek detailnya! 🚀',
'action' => [
Action::make('view')
->label('Lihat')

View File

@ -4,7 +4,6 @@
use App\Enums\DataChangeStatus;
use App\Enums\RoleEnum;
use App\Enums\VerificationStatus;
use App\Filament\Resources\Manage\DataChanges\DataChangesResource;
use App\Filament\Support\CheerfulNotification;
use App\Models\DataChangeRequest;
@ -143,10 +142,10 @@ protected function setUp(): void
User::superAdmin()
->get()
->each(function ($admin) use ($dataChangeRequest): void {
->each(function ($admin) use ($dataChangeRequest, $user): void {
$admin->notify(new BroadcastNotification([
'title' => 'Ada Pengajuan Perubahan Jurnalis ✨',
'body' => 'Halooo Admin! 👋 ' . $user->name . ' baru saja mengajukan perubahan data jurnalis. Yuk, cek detailnya! 🚀',
'body' => 'Halooo Admin! 👋 '.$user->name.' baru saja mengajukan perubahan data jurnalis. Yuk, cek detailnya! 🚀',
'action' => [
Action::make('view')
->label('Lihat')
@ -195,7 +194,7 @@ private function performSave(Journalist $record, array $data): void
];
$existingMedia = $record->getMedia('journalists');
$existingPaths = $existingMedia->map(fn($m) => $m->getPathRelativeToRoot())->toArray();
$existingPaths = $existingMedia->map(fn ($m) => $m->getPathRelativeToRoot())->toArray();
foreach ($documents as $field) {
if (empty($data[$field])) {

View File

@ -134,7 +134,7 @@ public static function form(Schema $schema): Schema
->rows(3)
->required(),
])
->visible(fn () => auth()->user()->company?->status === VerificationStatus::APPROVED && auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)),
->visible(fn () => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED && auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)),
])
->columns(1);
}
@ -177,14 +177,24 @@ public static function table(Table $table): Table
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
])
->checkIfRecordIsSelectableUsing(
fn () => ! (
in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) ||
auth()->user()->company?->verificationRequest()->pending()->exists()
)
)
->recordActions([
EditJournalistAction::make('edit'),
EditJournalistAction::make('edit')
->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())),
DeleteJournalistAction::make('delete'),
DeleteJournalistAction::make('delete')
->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())),
ForceDeleteAction::make(),
ForceDeleteAction::make()
->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())),
RestoreAction::make(),
RestoreAction::make()
->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())),
])
->toolbarActions([
BulkActionGroup::make([
@ -193,7 +203,7 @@ public static function table(Table $table): Table
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
])->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())),
])
->emptyStateIcon(Heroicon::Identification)
->emptyStateDescription('Setelah Anda membubat data pertama, maka akan muncul disini.')

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Manage\Journalists\Pages;
use App\Enums\RoleEnum;
use App\Enums\VerificationStatus;
use App\Filament\Resources\Manage\Journalists\Actions\CreateJournalistAction;
use App\Filament\Resources\Manage\Journalists\JournalistResource;
use Filament\Resources\Pages\ManageRecords;
@ -20,7 +21,8 @@ protected function getHeaderActions(): array
}
return [
CreateJournalistAction::make(),
CreateJournalistAction::make()
->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())),
];
}
}

View File

@ -1,401 +0,0 @@
<?php
namespace App\Services;
use App\Enums\DecisionAdmin;
use App\Enums\VerificationStatus;
use App\Filament\Pages\Company as PagesCompany;
use App\Filament\Pages\Media;
use App\Filament\Resources\Manage\Journalists\Pages\ManageJournalists;
use App\Models\Company;
use App\Models\VerificationRequest;
use App\Models\VerificationReview;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class VerificationService
{
/**
* Check if all required data is complete for verification
*/
public function isDataComplete(?Company $company): bool
{
return $company !== null
&& $company->partnerMedia !== null
&& $company->partnerMedia?->journalists !== null
&& $company->partnerMedia->journalists->count() > 0;
}
/**
* Get missing data items for verification
*/
public function getMissingData(?Company $company): array
{
$missing = [];
if (! $company) {
$missing[] = 'Data Perusahaan';
}
if ($company && ! $company->partnerMedia) {
$missing[] = 'Data Media';
}
if ($company && $company->partnerMedia && ($company->partnerMedia?->journalists === null || $company->partnerMedia?->journalists?->count() === 0)) {
$missing[] = 'Data Jurnalis (minimal 1)';
}
return $missing;
}
/**
* Check if user can submit verification
*/
public function canSubmit(?Company $company, ?VerificationRequest $verificationRequest): bool
{
if (! $this->isDataComplete($company)) {
return false;
}
// No verification yet - can submit
if (! $verificationRequest) {
return true;
}
// Can resubmit only if status is NEED_REVISION
return $verificationRequest->status === VerificationStatus::NEED_REVISION;
}
/**
* Determine the current step based on status
*/
public function resolveStep(?Company $company, ?VerificationRequest $verificationRequest): int
{
if (! $this->isDataComplete($company)) {
return 1; // Data belum lengkap
}
if (! $verificationRequest) {
return 1; // Belum submit
}
return match ($verificationRequest->status) {
VerificationStatus::PENDING,
VerificationStatus::NEED_REVISION => 2, // In review or needs revision
VerificationStatus::APPROVED,
VerificationStatus::REJECTED => 3, // Final decision
default => 1, // Draft / not submitted yet
};
}
/**
* Get the latest review notes
*/
public function getLatestReviewNotes(?VerificationRequest $verificationRequest): ?string
{
if (! $verificationRequest) {
return null;
}
$latestReview = $verificationRequest->reviews()
->where(fn ($q) => $q->needRevision()->orWhere(fn ($q) => $q->rejected()))
->latest()
->first();
return $latestReview?->note;
}
/**
* Get all reviews history
*/
public function getReviewHistory(?VerificationRequest $verificationRequest): Collection
{
if (! $verificationRequest) {
return collect();
}
return $verificationRequest->reviews()
->with('reviewer')
->latest()
->get();
}
/**
* Get unified verification progress data
*/
public function getVerificationProgress(?Company $company, ?VerificationRequest $verificationRequest): array
{
$currentStep = $this->resolveStep($company, $verificationRequest);
$isComplete = $this->isDataComplete($company);
$missingData = $this->getMissingData($company);
$status = $verificationRequest?->status;
return [
'currentStep' => $currentStep,
'isDataComplete' => $isComplete,
'missingData' => $missingData,
'canSubmit' => $this->canSubmit($company, $verificationRequest),
'verification' => $verificationRequest,
'status' => [
'label' => $status?->getLabel() ?? 'Belum Mengajukan',
'color' => $status?->getColor() ?? 'gray',
],
'isApproved' => $status === VerificationStatus::APPROVED,
'isRejected' => $status === VerificationStatus::REJECTED,
'isPending' => $status === VerificationStatus::PENDING,
'needsRevision' => $status === VerificationStatus::NEED_REVISION,
'latestNotes' => $this->getLatestReviewNotes($verificationRequest),
'reviewHistory' => $this->getReviewHistory($verificationRequest),
'company' => $company,
'partnerMedia' => $company?->partnerMedia,
'journalists' => $company?->partnerMedia?->journalists ?? collect(),
'steps' => $this->getSteps($verificationRequest, $isComplete, $missingData),
];
}
/**
* Get steps array for stepper
*/
protected function getSteps(?VerificationRequest $verificationRequest, bool $isComplete, array $missingData): array
{
$status = $verificationRequest?->status;
return [
[
'title' => 'Pengajuan',
'description' => $isComplete
? 'Data sudah lengkap, silakan ajukan verifikasi'
: 'Silakan lengkapi data',
],
[
'title' => 'Verifikasi',
'description' => match ($status) {
VerificationStatus::NEED_REVISION => 'Perlu perbaikan data',
VerificationStatus::PENDING => 'Sedang diverifikasi admin',
default => 'Menunggu pengajuan',
},
],
[
'title' => 'Selesai',
'description' => match ($status) {
VerificationStatus::APPROVED => 'Verifikasi disetujui',
VerificationStatus::REJECTED => 'Verifikasi ditolak',
default => 'Menunggu keputusan',
},
],
];
}
/**
* Submit unified verification
*/
public function submitVerification(Company $company, ?VerificationRequest $existingRequest): VerificationRequest
{
if ($existingRequest && $existingRequest->status === VerificationStatus::NEED_REVISION) {
// Resubmit - update existing request
$existingRequest->update([
'status' => VerificationStatus::PENDING,
]);
return $existingRequest->fresh(['reviews.reviewer']);
}
// New submission
return VerificationRequest::create([
'company_id' => $company->id,
'submitted_by' => auth()->id(),
'status' => VerificationStatus::PENDING,
])->load(['reviews.reviewer']);
}
/**
* Process review decision - unified for Company, Media, and Journalists
*/
public function processReview(VerificationRequest $verificationRequest, DecisionAdmin $decision, ?string $note): void
{
$verificationRequest->load(['company.partnerMedia.journalists']);
DB::beginTransaction();
try {
// Create review record
VerificationReview::create([
'verification_request_id' => $verificationRequest->id,
'reviewer_id' => auth()->id(),
'decision' => $decision,
'note' => $note,
]);
// Update verification request status
$newStatus = match ($decision) {
DecisionAdmin::APPROVED => VerificationStatus::APPROVED,
DecisionAdmin::NEED_REVISION => VerificationStatus::NEED_REVISION,
DecisionAdmin::REJECTED => VerificationStatus::REJECTED,
};
$verificationRequest->update([
'status' => $newStatus,
]);
// Update Company status
$company = $verificationRequest->company;
if ($company) {
$company->update(['status' => $newStatus]);
// Update Media status
$media = $company->partnerMedia;
if ($media) {
$media->update(['status' => $newStatus]);
// Update all Journalists status
$media->journalists()->update(['status' => $newStatus]);
}
}
DB::commit();
} catch (\Throwable $e) {
Log::error('Verification review processing failed', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'request_id' => $verificationRequest->id,
'decision' => $decision->value,
]);
DB::rollBack();
throw $e;
}
}
/**
* Get company name from verification request
*/
public function getCompanyName(VerificationRequest $request): string
{
return $request->company?->name ?? 'Data tidak ditemukan';
}
/**
* Get verification summary data for admin
*/
public function getVerificationSummary(VerificationRequest $request): array
{
$company = $request->company;
$media = $company?->partnerMedia;
$journalists = $media?->journalists ?? collect();
return [
'company' => [
'name' => $company?->name ?? 'Belum diisi',
'email' => $company?->email ?? '-',
'exists' => $company !== null,
],
'media' => [
'name' => $media?->name ?? 'Belum diisi',
'type' => $media?->type?->getLabel() ?? '-',
'exists' => $media !== null,
],
'journalists' => [
'count' => $journalists->count(),
'names' => $journalists->pluck('name')->take(3)->toArray(),
'exists' => $journalists->count() > 0,
],
];
}
/**
* Get alert data for verification status
*/
public function getAlertData(?VerificationRequest $verificationRequest): ?array
{
if (! $verificationRequest) {
return null;
}
$status = $verificationRequest->status;
$latestNotes = $this->getLatestReviewNotes($verificationRequest);
if ($status === VerificationStatus::NEED_REVISION && $latestNotes) {
return [
'type' => 'revision',
'icon' => 'exclamation-triangle',
'title' => 'Catatan Revisi dari Admin',
'message' => $latestNotes,
'color' => 'warning',
];
}
if ($status === VerificationStatus::REJECTED && $latestNotes) {
return [
'type' => 'rejected',
'icon' => 'x-circle',
'title' => 'Pengajuan Ditolak',
'message' => $latestNotes,
'color' => 'danger',
];
}
if ($status === VerificationStatus::APPROVED) {
return [
'type' => 'approved',
'icon' => 'check-circle',
'title' => 'Verifikasi Diterima',
'message' => 'Verifikasi Anda telah diterima dan disetujui oleh admin. Sekarang Anda sudah resmi terverifikasi dan bekerja sama dengan kami!',
'color' => 'success',
];
}
if ($status === VerificationStatus::PENDING) {
return [
'type' => 'pending',
'icon' => 'clock',
'title' => 'Menunggu Verifikasi',
'message' => 'Data Anda sedang dalam proses verifikasi oleh admin. Mohon tunggu.',
'color' => 'info',
'animate' => true,
];
}
return null;
}
/**
* Get missing data buttons configuration
*/
public function getMissingDataButtons(?Company $company): array
{
$buttons = [];
if (! $company) {
$buttons[] = [
'label' => 'Lengkapi Data Perusahaan',
'url' => PagesCompany::getUrl(),
'icon' => 'building-office-2',
'color' => 'warning',
];
}
if ($company && ! $company->partnerMedia) {
$buttons[] = [
'label' => 'Lengkapi Data Media',
'url' => Media::getUrl(),
'icon' => 'newspaper',
'color' => 'warning',
];
}
if ($company && $company->partnerMedia && ($company->partnerMedia->journalists === null || $company->partnerMedia->journalists->count() === 0)) {
$buttons[] = [
'label' => 'Lengkapi Data Jurnalis',
'url' => ManageJournalists::getUrl(),
'icon' => 'user-circle',
'color' => 'warning',
];
}
return $buttons;
}
}

View File

@ -2,8 +2,10 @@
<form wire:submit.prevent="save">
{{ $this->form }}
<div class="mt-6">
{{ $this->saveAction() }}
</div>
@if (!$this->isVerificationStage)
<div class="mt-6">
{{ $this->saveAction() }}
</div>
@endif
</form>
</x-filament-panels::page>

View File

@ -32,9 +32,11 @@ class="dark:text-gray-400">
<form wire:submit.prevent="save">
{{ $this->form }}
<div class="mt-6">
{{ $this->saveAction() }}
</div>
@if (!$this->isVerificationStage)
<div class="mt-6">
{{ $this->saveAction() }}
</div>
@endif
</form>
@endif
</x-filament-panels::page>