Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 115 additions & 11 deletions app/Filament/Dashboard/Resources/ReportResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,7 @@ public static function form(Schema $schema): Schema
} else {
return [
Textarea::make('reason')
->required()
->helperText(new HtmlString('<span style="color:red">Make sure you manually made all the changes requested before approving. This will only change the status of the report.</span>')),
->required(),
];
}
}
Expand Down Expand Up @@ -269,6 +268,9 @@ public static function form(Schema $schema): Schema
$get('status') == ReportStatus::APPROVED->value ||
$operation != 'edit';
})
->modalHeading(fn (Report $record) => self::getApprovalModalContent($record)['heading'])
->modalDescription(fn (Report $record) => self::getApprovalModalContent($record)['description'])
->modalSubmitActionLabel(fn (Report $record) => self::getApprovalModalContent($record)['submit_label'])
->action(function (array $data, Report $record, $set, $livewire, $get): void {
// Add this validation check before processing the approval
if ($record['report_category'] === ReportCategory::SUBMISSION->value) {
Expand Down Expand Up @@ -1171,6 +1173,113 @@ public static function getEloquentQuery(): Builder
return parent::getEloquentQuery();
}

public static function isFinalApprovalReview(Report $record): bool
{
return $record['status'] == ReportStatus::PENDING_APPROVAL->value
|| $record['status'] == ReportStatus::PENDING_REJECTION->value;
}

/**
* @return array{heading: string, description: string, submit_label: string}
*/
public static function getApprovalModalContent(Report $record): array
{
$isFinalReview = self::isFinalApprovalReview($record);

if ($isFinalReview) {
return match ($record['report_category']) {
ReportCategory::SUBMISSION->value => [
'heading' => 'Approve new molecule',
'description' => 'Approving this report will create and submit the new molecule entry to COCONUT.',
'submit_label' => 'Approve and create molecule',
],
ReportCategory::UPDATE->value => [
'heading' => 'Approve changes',
'description' => 'Approving this report will apply the selected changes to the molecule in the database.',
'submit_label' => 'Approve and apply changes',
],
ReportCategory::REVOKE->value => $record['report_type'] === 'molecule'
? [
'heading' => 'Revoke molecule',
'description' => 'Approving this report will deactivate the molecule and mark it as REVOKED.',
'submit_label' => 'Approve and revoke molecule',
]
: [
'heading' => 'Approve report',
'description' => 'Approving this report will only change the status of the report. Make sure you have manually applied all requested changes before approving.',
'submit_label' => 'Approve report',
],
default => [
'heading' => 'Approve report',
'description' => 'Approving this report will mark it as fully approved.',
'submit_label' => 'Approve',
],
};
}

return match ($record['report_category']) {
ReportCategory::SUBMISSION->value => [
'heading' => 'Approve for second review',
'description' => 'Approving this report will forward the new-molecule submission to a second curator. No molecule will be created yet.',
'submit_label' => 'Approve for second review',
],
ReportCategory::UPDATE->value => [
'heading' => 'Approve for second review',
'description' => 'Approving this report will forward the selected changes to a second curator. No changes will be applied yet.',
'submit_label' => 'Approve for second review',
],
ReportCategory::REVOKE->value => [
'heading' => 'Approve for second review',
'description' => $record['report_type'] === 'molecule'
? 'Approving this report will forward the revocation request to a second curator. The molecule will not be revoked yet.'
: 'Approving this report will forward it to a second curator for final approval.',
'submit_label' => 'Approve for second review',
],
default => [
'heading' => 'Approve for second review',
'description' => 'Approving this report will forward it to a second curator for final approval.',
'submit_label' => 'Approve for second review',
],
};
}

/**
* @return array{title: string, body: string}
*/
public static function getApprovalNotificationContent(Report $record, string $resultingStatus): array
{
if ($resultingStatus === ReportStatus::PENDING_APPROVAL->value) {
return [
'title' => 'Report approved for first review',
'body' => 'The report has been approved in first review and is awaiting a second curator.',
];
}

return match ($record['report_category']) {
ReportCategory::SUBMISSION->value => [
'title' => 'New molecule approved',
'body' => 'The new molecule has been approved and an entry has been created.',
],
ReportCategory::UPDATE->value => [
'title' => 'Changes approved',
'body' => 'The selected changes have been applied to the molecule.',
],
ReportCategory::REVOKE->value => $record['report_type'] === 'molecule'
? [
'title' => 'Molecule revoked',
'body' => 'The molecule has been revoked and deactivated.',
]
: [
'title' => 'Report approved',
'body' => 'The report has been approved. Ensure any manual changes were applied.',
],
default => [
'title' => 'Report fully approved',
'body' => 'The report has been fully approved.',
],
};
}

public static function prepareApprovedChanges(Report $record, $livewire)
{
$approved_changes = [];
Expand Down Expand Up @@ -1425,16 +1534,11 @@ public static function approveReport(array $data, Report $record, $livewire): vo
ReportStatusChanged::dispatch($record);

// Show appropriate notification based on action
if ($status == ReportStatus::PENDING_APPROVAL->value) {
Notification::make()
->title('Report approved for first review')
->body('The report has been approved in first review and is now pending final approval.')
->success()
->send();
} elseif ($status == ReportStatus::APPROVED->value) {
if ($status == ReportStatus::PENDING_APPROVAL->value || $status == ReportStatus::APPROVED->value) {
$notification = self::getApprovalNotificationContent($record, $status);
Notification::make()
->title('Report fully approved')
->body('The report has been fully approved.')
->title($notification['title'])
->body($notification['body'])
->success()
->send();
}
Expand Down
179 changes: 179 additions & 0 deletions tests/Unit/ReportApprovalMessageTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<?php

namespace Tests\Unit;

use App\Enums\ReportCategory;
use App\Enums\ReportStatus;
use App\Filament\Dashboard\Resources\ReportResource;
use App\Models\Report;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;

class ReportApprovalMessageTest extends TestCase
{
private function makeReport(string $category, string $status, string $reportType = 'molecule'): Report
{
return new Report([
'report_category' => $category,
'status' => $status,
'report_type' => $reportType,
]);
}

#[DataProvider('firstReviewModalProvider')]
public function test_first_review_modal_content(
string $category,
string $reportType,
string $expectedDescriptionFragment,
): void {
$record = $this->makeReport($category, ReportStatus::SUBMITTED->value, $reportType);
$content = ReportResource::getApprovalModalContent($record);

$this->assertSame('Approve for second review', $content['heading']);
$this->assertSame('Approve for second review', $content['submit_label']);
$this->assertStringContainsString($expectedDescriptionFragment, $content['description']);
}

public static function firstReviewModalProvider(): array
{
return [
'submission' => [
ReportCategory::SUBMISSION->value,
'molecule',
'No molecule will be created yet',
],
'update' => [
ReportCategory::UPDATE->value,
'molecule',
'No changes will be applied yet',
],
'revoke molecule' => [
ReportCategory::REVOKE->value,
'molecule',
'The molecule will not be revoked yet',
],
'revoke citation' => [
ReportCategory::REVOKE->value,
'citation',
'forward it to a second curator',
],
];
}

#[DataProvider('finalReviewModalProvider')]
public function test_final_review_modal_content(
string $category,
string $reportType,
string $expectedHeading,
string $expectedDescriptionFragment,
string $expectedSubmitLabel,
): void {
$record = $this->makeReport($category, ReportStatus::PENDING_APPROVAL->value, $reportType);
$content = ReportResource::getApprovalModalContent($record);

$this->assertSame($expectedHeading, $content['heading']);
$this->assertSame($expectedSubmitLabel, $content['submit_label']);
$this->assertStringContainsString($expectedDescriptionFragment, $content['description']);
}

public static function finalReviewModalProvider(): array
{
return [
'submission' => [
ReportCategory::SUBMISSION->value,
'molecule',
'Approve new molecule',
'create and submit the new molecule entry',
'Approve and create molecule',
],
'update' => [
ReportCategory::UPDATE->value,
'molecule',
'Approve changes',
'apply the selected changes to the molecule',
'Approve and apply changes',
],
'revoke molecule' => [
ReportCategory::REVOKE->value,
'molecule',
'Revoke molecule',
'deactivate the molecule and mark it as REVOKED',
'Approve and revoke molecule',
],
'revoke citation' => [
ReportCategory::REVOKE->value,
'citation',
'Approve report',
'only change the status of the report',
'Approve report',
],
];
}

public function test_is_final_approval_review(): void
{
$submitted = $this->makeReport(ReportCategory::UPDATE->value, ReportStatus::SUBMITTED->value);
$pendingApproval = $this->makeReport(ReportCategory::UPDATE->value, ReportStatus::PENDING_APPROVAL->value);
$pendingRejection = $this->makeReport(ReportCategory::UPDATE->value, ReportStatus::PENDING_REJECTION->value);

$this->assertFalse(ReportResource::isFinalApprovalReview($submitted));
$this->assertTrue(ReportResource::isFinalApprovalReview($pendingApproval));
$this->assertTrue(ReportResource::isFinalApprovalReview($pendingRejection));
}

#[DataProvider('notificationProvider')]
public function test_approval_notification_content(
string $category,
string $reportType,
string $resultingStatus,
string $expectedTitle,
string $expectedBodyFragment,
): void {
$record = $this->makeReport($category, ReportStatus::PENDING_APPROVAL->value, $reportType);
$content = ReportResource::getApprovalNotificationContent($record, $resultingStatus);

$this->assertSame($expectedTitle, $content['title']);
$this->assertStringContainsString($expectedBodyFragment, $content['body']);
}

public static function notificationProvider(): array
{
return [
'first review' => [
ReportCategory::UPDATE->value,
'molecule',
ReportStatus::PENDING_APPROVAL->value,
'Report approved for first review',
'awaiting a second curator',
],
'final submission' => [
ReportCategory::SUBMISSION->value,
'molecule',
ReportStatus::APPROVED->value,
'New molecule approved',
'entry has been created',
],
'final update' => [
ReportCategory::UPDATE->value,
'molecule',
ReportStatus::APPROVED->value,
'Changes approved',
'applied to the molecule',
],
'final revoke molecule' => [
ReportCategory::REVOKE->value,
'molecule',
ReportStatus::APPROVED->value,
'Molecule revoked',
'revoked and deactivated',
],
'final revoke citation' => [
ReportCategory::REVOKE->value,
'citation',
ReportStatus::APPROVED->value,
'Report approved',
'manual changes were applied',
],
];
}
}
Loading