refactor: move verification logic from VerificationService to the Verification Filament page.
This commit is contained in:
parent
9005f9cbec
commit
f06d8d100c
@ -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(),
|
||||
|
||||
@ -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(),
|
||||
]
|
||||
: []
|
||||
)
|
||||
|
||||
@ -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(),
|
||||
]
|
||||
: []
|
||||
)
|
||||
|
||||
@ -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 [
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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])) {
|
||||
|
||||
@ -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.')
|
||||
|
||||
@ -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())),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user