Dev branch

This commit is contained in:
Simon Pocrnjič
2025-11-02 12:31:01 +01:00
parent 5f879c9436
commit 63e0958b66
241 changed files with 17686 additions and 7327 deletions
+202
View File
@@ -0,0 +1,202 @@
<?php
use App\Models\Action;
use App\Models\Activity;
use App\Models\Client;
use App\Models\ClientCase;
use App\Models\Decision;
use App\Models\Permission;
use App\Models\Person\Person;
use App\Models\Person\PersonGroup;
use App\Models\Person\PersonType;
use App\Models\Role;
use App\Models\Segment;
use App\Models\User;
function makeUserWithPermissions(array $perms = ['reports-view', 'reports-export']): \App\Models\User
{
$user = User::factory()->create();
$role = Role::create(['name' => 'Tester', 'slug' => 'tester']);
foreach ($perms as $slug) {
$p = Permission::firstOrCreate(['slug' => $slug], ['name' => strtoupper(str_replace('-', ' ', $slug))]);
$role->permissions()->syncWithoutDetaching($p->id);
}
$role->users()->syncWithoutDetaching($user->id);
return $user;
}
it('shows reports index with permission', function () {
$user = makeUserWithPermissions(['reports-view']);
$this->actingAs($user);
$resp = $this->get(route('reports.index'));
$resp->assertOk();
});
it('counts actions/decisions correctly', function () {
$user = makeUserWithPermissions(['reports-view']);
$this->actingAs($user);
$action = Action::factory()->create(['name' => 'Klic']);
$decision = Decision::create(['name' => 'Uspešno']);
$group = new PersonGroup;
$group->name = 'grp';
$group->save();
$type = PersonType::create(['name' => 'tip']);
$person = Person::create(['first_name' => 'Ana', 'last_name' => 'Test', 'full_name' => 'Ana Test', 'group_id' => $group->id, 'type_id' => $type->id]);
$client = Client::create(['person_id' => $person->id]);
$case = ClientCase::create(['client_id' => $client->id, 'person_id' => $person->id, 'client_ref' => 'X']);
Activity::create(['action_id' => $action->id, 'decision_id' => $decision->id, 'client_case_id' => $case->id]);
Activity::create(['action_id' => $action->id, 'decision_id' => $decision->id, 'client_case_id' => $case->id]);
Activity::create(['action_id' => $action->id, 'decision_id' => $decision->id, 'client_case_id' => $case->id]);
$resp = $this->getJson(route('reports.data', 'actions-decisions-counts'));
$resp->assertOk();
$json = $resp->json();
expect($json['data'][0]['action_name'])->toBe('Klic');
expect($json['data'][0]['decision_name'])->toBe('Uspešno');
expect($json['data'][0]['activities_count'])->toBe(3);
});
it('lists active contracts filtered by client', function () {
$user = makeUserWithPermissions(['reports-view']);
$this->actingAs($user);
// minimal person/client scaffolding
$group = new PersonGroup; $group->name = 'grp'; $group->save();
$type = PersonType::create(['name' => 'tip']);
$p1 = Person::create(['first_name' => 'Ana', 'last_name' => 'Test', 'full_name' => 'Ana Test', 'group_id' => $group->id, 'type_id' => $type->id]);
$p2 = Person::create(['first_name' => 'Bojan', 'last_name' => 'Drugi', 'full_name' => 'Bojan Drugi', 'group_id' => $group->id, 'type_id' => $type->id]);
$clientA = App\Models\Client::create(['person_id' => $p1->id]);
$clientB = App\Models\Client::create(['person_id' => $p2->id]);
$caseA = ClientCase::create(['client_id' => $clientA->id, 'person_id' => $p1->id, 'client_ref' => 'A']);
$caseB = ClientCase::create(['client_id' => $clientB->id, 'person_id' => $p2->id, 'client_ref' => 'B']);
$ctype = new App\Models\ContractType;
$ctype->name = 'Standard';
$ctype->save();
// Active contract for client A
App\Models\Contract::create([
'reference' => 'A-001',
'client_case_id' => $caseA->id,
'type_id' => $ctype->id,
'start_date' => now()->subDays(10)->toDateString(),
'end_date' => null,
]);
// Inactive for client A (ended yesterday)
App\Models\Contract::create([
'reference' => 'A-OLD',
'client_case_id' => $caseA->id,
'type_id' => $ctype->id,
'start_date' => now()->subMonths(2)->toDateString(),
'end_date' => now()->subDay()->toDateString(),
]);
// Active but for different client B (should be excluded by client filter)
App\Models\Contract::create([
'reference' => 'B-001',
'client_case_id' => $caseB->id,
'type_id' => $ctype->id,
'start_date' => now()->subDays(3)->toDateString(),
'end_date' => null,
]);
$resp = $this->getJson(route('reports.data', ['slug' => 'active-contracts', 'client_uuid' => $clientA->uuid, 'as_of' => now()->toDateString()]));
$resp->assertOk();
$json = $resp->json();
// Should list exactly the active one for client A
expect($json['data'])->toBeArray();
// Filter only references from result
$refs = array_map(fn ($r) => $r['contract_reference'] ?? null, $json['data']);
expect($refs)->toContain('A-001');
expect($refs)->not->toContain('A-OLD');
expect($refs)->not->toContain('B-001');
});
it('counts activities per segment', function () {
$user = makeUserWithPermissions(['reports-view']);
$this->actingAs($user);
$segment = Segment::create(['name' => 'Prioriteta A', 'description' => 'desc', 'active' => true]);
$action = Action::factory()->create(['segment_id' => $segment->id]);
$decision = Decision::create(['name' => 'OK']);
$group = new PersonGroup;
$group->name = 'grp';
$group->save();
$type = PersonType::create(['name' => 'tip']);
$person = Person::create(['first_name' => 'Ana', 'last_name' => 'Test', 'full_name' => 'Ana Test', 'group_id' => $group->id, 'type_id' => $type->id]);
$client = Client::create(['person_id' => $person->id]);
$case = ClientCase::create(['client_id' => $client->id, 'person_id' => $person->id, 'client_ref' => 'X']);
Activity::create(['action_id' => $action->id, 'decision_id' => $decision->id, 'client_case_id' => $case->id]);
Activity::create(['action_id' => $action->id, 'decision_id' => $decision->id, 'client_case_id' => $case->id]);
$resp = $this->getJson(route('reports.data', 'segment-activity-counts'));
$resp->assertOk();
$json = $resp->json();
expect($json['data'][0]['segment_name'])->toBe('Prioriteta A');
expect($json['data'][0]['activities_count'])->toBe(2);
});
it('groups activities per day', function () {
$user = makeUserWithPermissions(['reports-view']);
$this->actingAs($user);
$action = Action::factory()->create();
$decision = Decision::create(['name' => 'OK']);
$group = new PersonGroup;
$group->name = 'grp';
$group->save();
$type = PersonType::create(['name' => 'tip']);
$person = Person::create(['first_name' => 'Ana', 'last_name' => 'Test', 'full_name' => 'Ana Test', 'group_id' => $group->id, 'type_id' => $type->id]);
$client = Client::create(['person_id' => $person->id]);
$case = ClientCase::create(['client_id' => $client->id, 'person_id' => $person->id, 'client_ref' => 'X']);
// Create two today and one yesterday (explicit timestamps via insert to avoid auto-timestamp override)
Activity::query()->insert([
['action_id' => $action->id, 'decision_id' => $decision->id, 'client_case_id' => $case->id, 'created_at' => now(), 'updated_at' => now()],
['action_id' => $action->id, 'decision_id' => $decision->id, 'client_case_id' => $case->id, 'created_at' => now(), 'updated_at' => now()],
['action_id' => $action->id, 'decision_id' => $decision->id, 'client_case_id' => $case->id, 'created_at' => now()->subDay(), 'updated_at' => now()->subDay()],
]);
$resp = $this->getJson(route('reports.data', ['slug' => 'activities-per-period', 'period' => 'day']));
$resp->assertOk();
$json = collect($resp->json('data'));
// Find today row
$todayRow = $json->first(function ($r) {
return str_starts_with((string) $r['period'], date('Y-m-d'));
});
expect($todayRow)->not()->toBeNull();
expect($todayRow['activities_count'])->toBe(2);
});
it('counts activities per decision only', function () {
$user = makeUserWithPermissions(['reports-view']);
$this->actingAs($user);
$decision = Decision::create(['name' => 'OK']);
$group = new PersonGroup;
$group->name = 'grp';
$group->save();
$type = PersonType::create(['name' => 'tip']);
$person = Person::create(['first_name' => 'Ana', 'last_name' => 'Test', 'full_name' => 'Ana Test', 'group_id' => $group->id, 'type_id' => $type->id]);
$client = Client::create(['person_id' => $person->id]);
$case = ClientCase::create(['client_id' => $client->id, 'person_id' => $person->id, 'client_ref' => 'X']);
$action = Action::factory()->create();
Activity::create(['action_id' => $action->id, 'decision_id' => $decision->id, 'client_case_id' => $case->id]);
Activity::create(['action_id' => $action->id, 'decision_id' => $decision->id, 'client_case_id' => $case->id]);
Activity::create(['action_id' => $action->id, 'decision_id' => $decision->id, 'client_case_id' => $case->id]);
$resp = $this->getJson(route('reports.data', 'decisions-counts'));
$resp->assertOk();
$json = $resp->json();
expect($json['data'][0]['decision_name'])->toBe('OK');
expect($json['data'][0]['activities_count'])->toBe(3);
});
@@ -0,0 +1,50 @@
<?php
use App\Models\Permission;
use App\Models\Role;
use App\Models\User;
function grantReportPerms(User $user)
{
$role = Role::create(['name' => 'Reporter', 'slug' => 'reporter']);
$view = Permission::firstOrCreate(['slug' => 'reports-view'], ['name' => 'Reports View']);
$exp = Permission::firstOrCreate(['slug' => 'reports-export'], ['name' => 'Reports Export']);
$role->permissions()->syncWithoutDetaching([$view->id, $exp->id]);
$role->users()->syncWithoutDetaching($user->id);
}
it('exports CSV with headings', function (): void {
$user = User::factory()->create();
grantReportPerms($user);
$this->actingAs($user);
$response = $this->get(route('reports.export', ['slug' => 'field-jobs-completed', 'format' => 'csv']));
$response->assertOk();
$ctype = $response->headers->get('Content-Type');
expect($ctype)->toStartWith('text/csv');
$content = $response->getContent();
expect($content)->toStartWith('#,Pogodba,Terenski,Zaklju');
});
it('exports PDF', function (): void {
$user = User::factory()->create();
grantReportPerms($user);
$this->actingAs($user);
$response = $this->get(route('reports.export', ['slug' => 'field-jobs-completed', 'format' => 'pdf']));
$response->assertOk();
$response->assertHeader('Content-Type');
$ctype = $response->headers->get('Content-Type');
expect($ctype)->toContain('application/pdf');
});
it('exports XLSX', function (): void {
$user = User::factory()->create();
grantReportPerms($user);
$this->actingAs($user);
$response = $this->get(route('reports.export', ['slug' => 'field-jobs-completed', 'format' => 'xlsx']));
$response->assertOk();
$ctype = $response->headers->get('Content-Type');
expect($ctype)->toContain('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
});
@@ -0,0 +1,22 @@
<?php
use App\Models\Permission;
use App\Models\Role;
use App\Models\User;
it('shows reports index', function (): void {
$user = User::factory()->create();
// grant reports-view permission
$role = Role::create(['name' => 'Reporter', 'slug' => 'reporter']);
$perm = Permission::firstOrCreate(['slug' => 'reports-view'], ['name' => 'Reports View']);
$role->permissions()->syncWithoutDetaching($perm->id);
$role->users()->syncWithoutDetaching($user->id);
$this->actingAs($user);
$response = $this->get(route('reports.index'));
$response->assertSuccessful();
$response->assertInertia(fn ($page) => $page
->component('Reports/Index')
->has('reports')
);
});
+37
View File
@@ -0,0 +1,37 @@
<?php
use App\Models\Permission;
use App\Models\Role;
use App\Models\User;
it('shows a single report page', function (): void {
$user = User::factory()->create();
$role = Role::create(['name' => 'Reporter', 'slug' => 'reporter']);
$perm = Permission::firstOrCreate(['slug' => 'reports-view'], ['name' => 'Reports View']);
$role->permissions()->syncWithoutDetaching($perm->id);
$role->users()->syncWithoutDetaching($user->id);
$this->actingAs($user);
$response = $this->get(route('reports.show', 'field-jobs-completed'));
$response->assertSuccessful();
$response->assertInertia(fn ($page) => $page
->component('Reports/Show')
->where('slug', 'field-jobs-completed')
->has('inputs')
->has('columns')
);
});
it('returns data json for report', function (): void {
$user = User::factory()->create();
$role = Role::create(['name' => 'Reporter', 'slug' => 'reporter']);
$perm = Permission::firstOrCreate(['slug' => 'reports-view'], ['name' => 'Reports View']);
$role->permissions()->syncWithoutDetaching($perm->id);
$role->users()->syncWithoutDetaching($user->id);
$this->actingAs($user);
$response = $this->get(route('reports.data', 'field-jobs-completed'));
$response->assertSuccessful();
$json = $response->json();
expect($json)->toHaveKeys(['data', 'total', 'current_page', 'last_page']);
});