5 Commits

Author SHA1 Message Date
7b26fc57f4 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
2026-03-06 12:29:00 +08:00
35723fb226 Merge pull request 'Add nightly town sync with autocomplete on journey fields' (#12) from feature/town-sync into master
Some checks failed
linter / quality (push) Has been cancelled
security / Dependency Audit (push) Has been cancelled
security / Static Analysis (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled
Reviewed-on: #12
2026-03-06 12:24:19 +08:00
e01caf678b Fix form submission staying in draft status
All checks were successful
linter / quality (pull_request) Successful in 1m34s
security / Dependency Audit (pull_request) Successful in 1m30s
security / Static Analysis (pull_request) Successful in 1m32s
tests / ci (8.4) (pull_request) Successful in 1m25s
tests / ci (8.5) (pull_request) Successful in 1m58s
When no active approval workflow exists, ApprovalService::submit() was
silently returning, leaving the request in Draft while showing a false
success message. Now throws a RuntimeException as a safety net, and the
Livewire component guards before creating any records and shows a clear
error to the user.
2026-03-06 04:23:55 +00:00
89b3ddb793 Merge pull request 'Fix persistent LDAP volumes preventing bootstrap.ldif from reloading' (#11) from fix/ldap-bootstrap into master
Some checks failed
linter / quality (push) Successful in 1m22s
security / Static Analysis (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled
security / Dependency Audit (push) Has been cancelled
Reviewed-on: #11
2026-03-06 12:08:18 +08:00
d59ec55999 Remove persistent LDAP volumes so bootstrap.ldif always applies on startup
All checks were successful
linter / quality (pull_request) Successful in 1m45s
security / Dependency Audit (pull_request) Successful in 1m21s
security / Static Analysis (pull_request) Successful in 2m11s
tests / ci (8.4) (pull_request) Successful in 1m27s
tests / ci (8.5) (pull_request) Successful in 2m21s
The osixia/openldap image only runs bootstrap LDIF when the database is
empty. Named volumes (sail-ldap-data, sail-ldap-config) caused changes
to bootstrap.ldif to be ignored after the first run. Removing these
volumes ensures the test LDAP directory is always seeded fresh from the
bootstrap file on each sail up.
2026-03-06 04:02:18 +00:00
4 changed files with 137 additions and 8 deletions

View File

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

View File

@@ -97,8 +97,6 @@ services:
LDAP_READONLY_USER_USERNAME: '${LDAP_READONLY_USERNAME:-readonly}'
LDAP_READONLY_USER_PASSWORD: '${LDAP_READONLY_PASSWORD:-readonly}'
volumes:
- 'sail-ldap-data:/var/lib/ldap'
- 'sail-ldap-config:/etc/ldap/slapd.d'
- './docker/openldap/bootstrap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/bootstrap.ldif'
networks:
- sail
@@ -136,7 +134,3 @@ volumes:
driver: local
sail-redis:
driver: local
sail-ldap-data:
driver: local
sail-ldap-config:
driver: local

View File

@@ -92,7 +92,6 @@ new #[Layout('components.layouts.app')] class extends Component {
public function saveDraft(): void
{
$this->saveRequest(submit: false);
session()->flash('success', 'Draft saved successfully.');
}
public function submit(): void
@@ -102,6 +101,12 @@ new #[Layout('components.layouts.app')] class extends Component {
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([
'emergencyFullName' => ['required', 'string', 'max:255'],
'emergencyPhone' => ['required', 'string', 'max:50'],
@@ -182,6 +187,8 @@ new #[Layout('components.layouts.app')] class extends Component {
if ($submit) {
app(ApprovalService::class)->submit($travelRequest);
session()->flash('success', 'Travel request submitted for approval.');
} else {
session()->flash('success', 'Draft saved successfully.');
}
$this->redirect(route('travel-requests.show', $travelRequest), navigate: true);
@@ -209,6 +216,10 @@ new #[Layout('components.layouts.app')] class extends Component {
</div>
@endif
@error('workflow')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
<form wire:submit.prevent>
{{-- 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);
}
}