Merge pull request 'Fix form submission staying in draft status' (#13) from fix/submit-draft-status into master
All checks were successful
linter / quality (push) Successful in 1m22s
security / Dependency Audit (push) Successful in 1m27s
security / Static Analysis (push) Successful in 1m24s
tests / ci (8.4) (push) Successful in 1m52s
tests / ci (8.5) (push) Successful in 2m10s

Reviewed-on: #13
This commit was merged in pull request #13.
This commit is contained in:
2026-03-06 12:29:00 +08:00
3 changed files with 137 additions and 2 deletions

View File

@@ -17,7 +17,7 @@ class ApprovalService
$workflow = $travelRequest->workflow; $workflow = $travelRequest->workflow;
if (! $workflow) { if (! $workflow) {
return; throw new \RuntimeException('No active approval workflow is configured.');
} }
$steps = $workflow->steps()->orderBy('order')->get(); $steps = $workflow->steps()->orderBy('order')->get();

View File

@@ -92,7 +92,6 @@ new #[Layout('components.layouts.app')] class extends Component {
public function saveDraft(): void public function saveDraft(): void
{ {
$this->saveRequest(submit: false); $this->saveRequest(submit: false);
session()->flash('success', 'Draft saved successfully.');
} }
public function submit(): void public function submit(): void
@@ -102,6 +101,12 @@ new #[Layout('components.layouts.app')] class extends Component {
private function saveRequest(bool $submit): void private function saveRequest(bool $submit): void
{ {
if ($submit && ! ApprovalWorkflow::where('is_active', true)->exists()) {
$this->addError('workflow', 'No active approval workflow is configured. Please contact an administrator.');
return;
}
$this->validate([ $this->validate([
'emergencyFullName' => ['required', 'string', 'max:255'], 'emergencyFullName' => ['required', 'string', 'max:255'],
'emergencyPhone' => ['required', 'string', 'max:50'], 'emergencyPhone' => ['required', 'string', 'max:50'],
@@ -182,6 +187,8 @@ new #[Layout('components.layouts.app')] class extends Component {
if ($submit) { if ($submit) {
app(ApprovalService::class)->submit($travelRequest); app(ApprovalService::class)->submit($travelRequest);
session()->flash('success', 'Travel request submitted for approval.'); session()->flash('success', 'Travel request submitted for approval.');
} else {
session()->flash('success', 'Draft saved successfully.');
} }
$this->redirect(route('travel-requests.show', $travelRequest), navigate: true); $this->redirect(route('travel-requests.show', $travelRequest), navigate: true);
@@ -209,6 +216,10 @@ new #[Layout('components.layouts.app')] class extends Component {
</div> </div>
@endif @endif
@error('workflow')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
<form wire:submit.prevent> <form wire:submit.prevent>
{{-- Applicant Details --}} {{-- Applicant Details --}}

View File

@@ -0,0 +1,124 @@
<?php
namespace Tests\Feature;
use App\Enums\ApprovalStatus;
use App\Enums\JourneyMethod;
use App\Enums\TravelStatus;
use App\Models\ApprovalStep;
use App\Models\ApprovalWorkflow;
use App\Models\TravelRequest;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
class TravelRequestSubmissionTest extends TestCase
{
use RefreshDatabase;
private User $staff;
/** @var array<string, mixed> */
private array $validFormData;
protected function setUp(): void
{
parent::setUp();
Role::firstOrCreate(['name' => 'staff']);
Role::firstOrCreate(['name' => 'travel_approver']);
Role::firstOrCreate(['name' => 'administrator']);
$this->staff = User::factory()->create();
$this->staff->assignRole('staff');
$this->validFormData = [
'emergencyFullName' => 'Jane Doe',
'emergencyPhone' => '0400000000',
'emergencyRelationship' => 'Spouse',
'reasonSummary' => 'Attending a medical conference in Sydney',
'journeys' => [
[
'origin' => 'Perth',
'destination' => 'Sydney',
'date' => '2026-04-01',
'time' => '09:00',
'method' => JourneyMethod::Air->value,
],
],
'costCodes' => [],
];
}
private function makeWorkflow(): ApprovalWorkflow
{
$workflow = ApprovalWorkflow::factory()->create(['is_active' => true]);
ApprovalStep::factory()->create([
'workflow_id' => $workflow->id,
'order' => 1,
'name' => 'Travel Approver Review',
'role' => 'travel_approver',
]);
return $workflow;
}
public function test_submitting_form_with_active_workflow_sets_status_to_pending(): void
{
$this->makeWorkflow();
Livewire::actingAs($this->staff)
->test('travel-request.create')
->set($this->validFormData)
->call('submit');
$request = TravelRequest::first();
$this->assertNotNull($request);
$this->assertSame(TravelStatus::Pending, $request->status);
$this->assertNotNull($request->submitted_at);
}
public function test_submitting_form_with_active_workflow_creates_pending_approval(): void
{
$this->makeWorkflow();
Livewire::actingAs($this->staff)
->test('travel-request.create')
->set($this->validFormData)
->call('submit');
$request = TravelRequest::first();
$this->assertCount(1, $request->approvals);
$this->assertSame(ApprovalStatus::Pending->value, $request->approvals->first()->status->value);
}
public function test_submitting_form_with_no_active_workflow_shows_error(): void
{
Livewire::actingAs($this->staff)
->test('travel-request.create')
->set($this->validFormData)
->call('submit')
->assertHasErrors(['workflow']);
$this->assertDatabaseCount('travel_requests', 0);
}
public function test_saving_draft_does_not_submit_for_approval(): void
{
$this->makeWorkflow();
Livewire::actingAs($this->staff)
->test('travel-request.create')
->set($this->validFormData)
->call('saveDraft');
$request = TravelRequest::first();
$this->assertNotNull($request);
$this->assertSame(TravelStatus::Draft, $request->status);
$this->assertNull($request->submitted_at);
$this->assertCount(0, $request->approvals);
}
}