Big changes added events for decisions

This commit is contained in:
Simon Pocrnjič
2025-10-22 23:20:04 +02:00
parent 872b76b012
commit 67ebe4b225
36 changed files with 2240 additions and 189 deletions
@@ -0,0 +1,131 @@
<?php
use App\Models\Action;
use App\Models\ArchiveSetting;
use App\Models\Client;
use App\Models\ClientCase;
use App\Models\Contract;
use App\Models\ContractType;
use App\Models\Decision;
use App\Models\Segment;
use App\Models\User;
use Illuminate\Support\Facades\DB;
it('archives the contract when creating an activity with an archive event decision', function () {
// Auth
$user = User::factory()->create();
$this->actingAs($user);
// Minimal person/client/case
$person = \App\Models\Person\Person::factory()->create([
'first_name' => 'Test',
'last_name' => 'User',
'full_name' => 'Test User',
]);
$client = Client::create(['person_id' => $person->id]);
$case = ClientCase::create([
'client_id' => $client->id,
'person_id' => $person->id,
]);
// Contract to be archived
$ctype = ContractType::factory()->create();
$contract = Contract::create([
'client_case_id' => $case->id,
'reference' => 'R-TEST',
'start_date' => now()->toDateString(),
'type_id' => $ctype->id,
'description' => 'Test',
]);
// ensure exists before triggering
expect($contract->id)->toBeGreaterThan(0);
// Action/Decision
$segment = Segment::factory()->create();
$action = Action::create(['name' => 'Call', 'color_tag' => '#000000', 'segment_id' => $segment->id]);
$decision = Decision::create(['name' => 'Archive', 'color_tag' => '#ff0000', 'auto_mail' => false]);
$action->decisions()->attach($decision->id);
// Create initial and archive segments
$initialSegment = Segment::factory()->create();
$archiveSegment = Segment::factory()->create();
// Ensure case and contract have an initial segment active
DB::table('client_case_segment')->insert([
'client_case_id' => $case->id,
'segment_id' => $initialSegment->id,
'active' => true,
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('contract_segment')->insert([
'contract_id' => $contract->id,
'segment_id' => $initialSegment->id,
'active' => true,
'created_at' => now(),
'updated_at' => now(),
]);
// Archive setting (focus on contracts table, soft archive) with archive segment
$setting = ArchiveSetting::create([
'name' => 'Test archive',
'enabled' => true,
'strategy' => 'immediate',
'soft' => true,
'reactivate' => false,
'segment_id' => $archiveSegment->id,
'entities' => [
[
'table' => 'contracts',
'focus' => true,
],
],
]);
// Event: archive_contract (ensure legacy NOT NULL options is satisfied)
$eventId = DB::table('events')->insertGetId([
'name' => 'Archive Contract',
'key' => 'archive_contract',
'description' => 'Archive selected contract',
'active' => true,
'options' => json_encode(new stdClass),
'config' => json_encode(new stdClass),
'created_at' => now(),
'updated_at' => now(),
]);
// Attach event to decision with required config
DB::table('decision_event')->insert([
'decision_id' => $decision->id,
'event_id' => $eventId,
'active' => true,
'run_order' => 1,
'config' => json_encode(['archive_setting_id' => $setting->id]),
'created_at' => now(),
'updated_at' => now(),
]);
// POST activity (should synchronously trigger decision events in testing env)
$route = route('clientCase.activity.store', $case);
$this->post($route, [
'action_id' => $action->id,
'decision_id' => $decision->id,
'contract_uuid' => $contract->uuid,
'note' => 'Trigger archive',
])->assertSessionHasNoErrors();
// Contract should now be archived (active = 0)
$fresh = $contract->fresh();
expect((int) $fresh->active)->toBe(0);
// Segment should be moved to archive segment
$activePivots = DB::table('contract_segment')->where('contract_id', $contract->id)->where('active', true)->get();
expect($activePivots->count())->toBe(1);
expect($activePivots->first()->segment_id)->toBe($archiveSegment->id);
// Case should have archive segment attached and active
$caseSeg = DB::table('client_case_segment')->where('client_case_id', $case->id)->where('segment_id', $archiveSegment->id)->first();
expect($caseSeg)->not->toBeNull();
expect((int) $caseSeg->active)->toBe(1);
});
@@ -0,0 +1,122 @@
<?php
use App\Models\Action;
use App\Models\Client;
use App\Models\ClientCase;
use App\Models\Contract;
use App\Models\Decision;
use App\Models\FieldJob;
use App\Models\FieldJobSetting;
use App\Models\Segment;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
it('completes field job and moves contract to return segment when end_field_job event fires', function () {
// Auth user
$user = User::factory()->create();
$this->actingAs($user);
// Minimal person/client/case
$person = \App\Models\Person\Person::factory()->create([
'first_name' => 'Job',
'last_name' => 'Runner',
'full_name' => 'Job Runner',
]);
$client = Client::create(['person_id' => $person->id]);
$case = ClientCase::create([
'client_id' => $client->id,
'person_id' => $person->id,
]);
$returnSeg = Segment::factory()->create(['name' => 'Return']);
$fieldSeg = Segment::factory()->create(['name' => 'Field']);
$action = Action::create(['name' => 'Call', 'color_tag' => '#000', 'segment_id' => $returnSeg->id]);
$decision = Decision::create(['name' => 'Finish field job', 'color_tag' => '#00f', 'auto_mail' => false]);
$action->decisions()->attach($decision->id);
// Create a contract under the case and attach field segment as active
$contract = Contract::create([
'reference' => 'REF-2002',
'client_case_id' => $case->id,
'type_id' => \Database\Factories\ContractTypeFactory::new()->create()->id,
'start_date' => now()->toDateString(),
]);
// Ensure both segments exist on the case and contract
DB::table('client_case_segment')->insert([
['client_case_id' => $case->id, 'segment_id' => $fieldSeg->id, 'active' => true, 'created_at' => now(), 'updated_at' => now()],
['client_case_id' => $case->id, 'segment_id' => $returnSeg->id, 'active' => true, 'created_at' => now(), 'updated_at' => now()],
]);
DB::table('contract_segment')->insert([
['contract_id' => $contract->id, 'segment_id' => $fieldSeg->id, 'active' => true, 'created_at' => now(), 'updated_at' => now()],
['contract_id' => $contract->id, 'segment_id' => $returnSeg->id, 'active' => false, 'created_at' => now(), 'updated_at' => now()],
]);
// FieldJobSetting with return segment
$setting = FieldJobSetting::create([
'segment_id' => $fieldSeg->id,
'assign_decision_id' => $decision->id, // not used here
'complete_decision_id' => $decision->id,
'return_segment_id' => $returnSeg->id,
'queue_segment_id' => $fieldSeg->id,
'action_id' => $action->id,
]);
// Active FieldJob for this contract (simulates assignment)
FieldJob::create([
'field_job_setting_id' => $setting->id,
'assigned_user_id' => $user->id,
'contract_id' => $contract->id,
'assigned_at' => now()->subDay(),
]);
// Insert end_field_job event (satisfy legacy options NOT NULL)
$eventId = DB::table('events')->insertGetId([
'name' => 'End Field Job',
'key' => 'end_field_job',
'description' => 'Complete a field job',
'active' => true,
'options' => json_encode(new stdClass),
'config' => json_encode(new stdClass),
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('decision_event')->insert([
'decision_id' => $decision->id,
'event_id' => $eventId,
'active' => true,
'run_order' => 1,
'config' => json_encode([]),
'created_at' => now(),
'updated_at' => now(),
]);
// Spy on logs to assert EndFieldJob ran
Log::spy();
$this->post(route('clientCase.activity.store', $case), [
'action_id' => $action->id,
'decision_id' => $decision->id,
'contract_uuid' => $contract->uuid,
'note' => 'Finish job',
])->assertSessionHasNoErrors();
// The active field job should be marked completed
$completedJob = FieldJob::query()
->where('contract_id', $contract->id)
->latest('id')
->first();
expect($completedJob)->not()->toBeNull();
expect($completedJob->completed_at)->not()->toBeNull();
// The contract should now be in return segment
$activeSegIds = DB::table('contract_segment')
->where('contract_id', $contract->id)
->where('active', true)
->pluck('segment_id');
expect($activeSegIds->contains($returnSeg->id))->toBeTrue();
});
@@ -0,0 +1,91 @@
<?php
use App\Models\Segment;
use App\Models\User;
use Illuminate\Foundation\Testing\WithFaker;
uses(WithFaker::class);
it('creates a decision with add_segment event when config is valid', function () {
$user = User::factory()->create([
'email_verified_at' => now(),
]);
$this->actingAs($user);
$segment = Segment::factory()->create();
$addSegmentEventId = \DB::table('events')->insertGetId([
'name' => 'Add Segment',
'key' => 'add_segment',
'description' => 'Attach a segment to contract',
'active' => 1,
'config' => json_encode([]),
'options' => json_encode([]),
'created_at' => now(),
'updated_at' => now(),
]);
$payload = [
'name' => fake()->unique()->words(2, true),
'color_tag' => null,
'auto_mail' => false,
'email_template_id' => null,
'actions' => [],
'events' => [
[
'id' => $addSegmentEventId,
'active' => true,
'run_order' => 1,
'config' => [
'segment_id' => $segment->id,
'deactivate_previous' => true,
],
],
],
];
$response = $this->post(route('settings.decisions.store'), $payload);
$response->assertSessionHasNoErrors();
$response->assertRedirect(route('settings.workflow'));
});
it('validates segment_id is required for add_segment event on create', function () {
$user = User::factory()->create([
'email_verified_at' => now(),
]);
$this->actingAs($user);
$addSegmentEventId = \DB::table('events')->insertGetId([
'name' => 'Add Segment',
'key' => 'add_segment',
'description' => 'Attach a segment to contract',
'active' => 1,
'config' => json_encode([]),
'options' => json_encode([]),
'created_at' => now(),
'updated_at' => now(),
]);
$payload = [
'name' => fake()->unique()->words(2, true),
'color_tag' => null,
'auto_mail' => false,
'email_template_id' => null,
'events' => [
[
'id' => $addSegmentEventId,
'active' => true,
'run_order' => 1,
'config' => [
// missing segment_id
'deactivate_previous' => true,
],
],
],
];
$response = $this->post(route('settings.decisions.store'), $payload);
$response->assertSessionHasErrors(['events.0.config.segment_id']);
});
@@ -0,0 +1,139 @@
<?php
use App\Models\Decision;
use App\Models\Event;
use App\Models\Segment;
use App\Models\User;
use Illuminate\Foundation\Testing\WithFaker;
uses(WithFaker::class);
it('validates segment_id is required for add_segment event', function () {
$user = User::factory()->create([
'email_verified_at' => now(),
]);
$this->actingAs($user);
$decision = Decision::factory()->create();
// Ensure event exists (legacy schema requires non-null options)
$addSegmentId = \DB::table('events')->insertGetId([
'name' => 'Add Segment',
'key' => 'add_segment',
'description' => 'Attach a segment to contract',
'active' => 1,
'config' => json_encode([]),
'options' => json_encode([]),
'created_at' => now(),
'updated_at' => now(),
]);
$payload = [
'name' => $decision->name,
'color_tag' => $decision->color_tag,
'auto_mail' => false,
'email_template_id' => null,
'events' => [
[
'id' => $addSegmentId,
'active' => true,
'run_order' => 1,
'config' => [
// 'segment_id' => missing on purpose
'deactivate_previous' => true,
],
],
],
];
$response = $this->put(route('settings.decisions.update', ['id' => $decision->id]), $payload);
$response->assertSessionHasErrors(['events.0.config.segment_id']);
});
it('validates archive_setting_id is required for archive_contract event', function () {
$user = User::factory()->create([
'email_verified_at' => now(),
]);
$this->actingAs($user);
$decision = Decision::factory()->create();
$archiveEventId = \DB::table('events')->insertGetId([
'name' => 'Archive Contract',
'key' => 'archive_contract',
'description' => 'Archive a contract by archive setting',
'active' => 1,
'config' => json_encode([]),
'options' => json_encode([]),
'created_at' => now(),
'updated_at' => now(),
]);
$payload = [
'name' => $decision->name,
'color_tag' => $decision->color_tag,
'auto_mail' => false,
'email_template_id' => null,
'events' => [
[
'id' => $archiveEventId,
'active' => true,
'run_order' => 1,
'config' => [
// 'archive_setting_id' => missing on purpose
'reactivate' => false,
],
],
],
];
$response = $this->put(route('settings.decisions.update', ['id' => $decision->id]), $payload);
$response->assertSessionHasErrors(['events.0.config.archive_setting_id']);
});
it('accepts valid add_segment config and updates without validation errors', function () {
$user = User::factory()->create([
'email_verified_at' => now(),
]);
$this->actingAs($user);
$decision = Decision::factory()->create();
$addSegmentId = \DB::table('events')->insertGetId([
'name' => 'Add Segment',
'key' => 'add_segment',
'description' => 'Attach a segment to contract',
'active' => 1,
'config' => json_encode([]),
'options' => json_encode([]),
'created_at' => now(),
'updated_at' => now(),
]);
$segment = Segment::factory()->create();
$payload = [
'name' => $decision->name,
'color_tag' => $decision->color_tag,
'auto_mail' => false,
'email_template_id' => null,
'events' => [
[
'id' => $addSegmentId,
'active' => true,
'run_order' => 1,
'config' => [
'segment_id' => $segment->id,
'deactivate_previous' => true,
],
],
],
];
$response = $this->put(route('settings.decisions.update', ['id' => $decision->id]), $payload);
$response->assertSessionHasNoErrors();
$response->assertRedirect(route('settings.workflow'));
});