changes 0230092025
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use App\Models\ClientCase;
|
||||
use App\Models\Contract;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('returns client cases when searching by contract reference', function () {
|
||||
// Arrange: create a user and authenticate
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
// Create a client case with a contract that has a known reference
|
||||
/** @var ClientCase $clientCase */
|
||||
$clientCase = ClientCase::factory()->create();
|
||||
|
||||
/** @var Contract $contract */
|
||||
$contract = Contract::factory()->create([
|
||||
'client_case_id' => $clientCase->id,
|
||||
'reference' => 'REF-TEST-12345',
|
||||
]);
|
||||
|
||||
// Act: hit the global search route with the contract reference
|
||||
$response = $this->get(route('search', [
|
||||
'query' => 'REF-TEST-12345',
|
||||
'limit' => 8,
|
||||
]));
|
||||
|
||||
// Assert: response contains the affiliated case in "client_cases"
|
||||
$response->assertOk();
|
||||
$data = $response->json();
|
||||
|
||||
expect($data)
|
||||
->toHaveKey('client_cases')
|
||||
->and(collect($data['client_cases'])->pluck('case_uuid')->all())
|
||||
->toContain($clientCase->uuid);
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Contract;
|
||||
use App\Models\Import;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('imports account.initial_amount when mapped', function () {
|
||||
\Illuminate\Support\Facades\Artisan::call('db:seed', ['--force' => true]);
|
||||
$user = User::factory()->create();
|
||||
Auth::login($user);
|
||||
|
||||
$client = Client::factory()->create();
|
||||
Storage::fake('local');
|
||||
// CSV with straightforward decimal to avoid delimiter/locale complications
|
||||
$csv = "contract.reference,account.reference,account.initial_amount\nREF-IA-1,ACC-IA-1,1234.56\n";
|
||||
Storage::disk('local')->put('imports/acc_init.csv', $csv);
|
||||
|
||||
$import = Import::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'user_id' => $user->id,
|
||||
'client_id' => $client->id,
|
||||
'source_type' => 'csv',
|
||||
'file_name' => 'acc_init.csv',
|
||||
'original_name' => 'acc_init.csv',
|
||||
'disk' => 'local',
|
||||
'path' => 'imports/acc_init.csv',
|
||||
'status' => 'queued',
|
||||
'meta' => [
|
||||
'has_header' => true,
|
||||
'columns' => ['contract.reference','account.reference','account.initial_amount'],
|
||||
],
|
||||
'import_template_id' => null,
|
||||
]);
|
||||
|
||||
// Mappings
|
||||
DB::table('import_mappings')->insert([
|
||||
'import_id' => $import->id,
|
||||
'source_column' => 'contract.reference',
|
||||
'target_field' => 'contract.reference',
|
||||
'transform' => 'trim|upper|ref',
|
||||
'apply_mode' => 'both',
|
||||
'options' => null,
|
||||
'position' => 0,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
DB::table('import_mappings')->insert([
|
||||
'import_id' => $import->id,
|
||||
'source_column' => 'contract.reference',
|
||||
'target_field' => 'account.contract_reference',
|
||||
'transform' => 'trim|upper|ref',
|
||||
'apply_mode' => 'both',
|
||||
'options' => null,
|
||||
'position' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
DB::table('import_mappings')->insert([
|
||||
'import_id' => $import->id,
|
||||
'source_column' => 'account.reference',
|
||||
'target_field' => 'account.reference',
|
||||
'transform' => 'trim|upper|ref',
|
||||
'apply_mode' => 'both',
|
||||
'options' => null,
|
||||
'position' => 2,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
DB::table('import_mappings')->insert([
|
||||
'import_id' => $import->id,
|
||||
'source_column' => 'account.initial_amount',
|
||||
'target_field' => 'account.initial_amount',
|
||||
'transform' => 'trim',
|
||||
'apply_mode' => 'both',
|
||||
'options' => null,
|
||||
'position' => 3,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$service = app(\App\Services\ImportProcessor::class);
|
||||
$service->process($import, $user);
|
||||
|
||||
$acc = \App\Models\Account::query()->first();
|
||||
expect($acc)->not->toBeNull();
|
||||
// normalized decimal should be 1234.56
|
||||
expect((string) $acc->initial_amount)->toBe('1234.56');
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Import;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('deduplicates client cases by client_ref when importing', function () {
|
||||
// Ensure base seeds for reference tables
|
||||
\Illuminate\Support\Facades\Artisan::call('db:seed', ['--force' => true]);
|
||||
$user = User::factory()->create();
|
||||
Auth::login($user);
|
||||
|
||||
// Prepare client and a small CSV with two rows sharing same client_ref but different contract.reference
|
||||
$client = Client::factory()->create();
|
||||
Storage::fake('local');
|
||||
$csv = "client_ref,contract.reference,contract.description\nCASE-001,REF-1,First\nCASE-001,REF-2,Second\n";
|
||||
Storage::disk('local')->put('imports/test.csv', $csv);
|
||||
|
||||
$import = Import::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'user_id' => $user->id,
|
||||
'client_id' => $client->id,
|
||||
'source_type' => 'csv',
|
||||
'file_name' => 'test.csv',
|
||||
'original_name' => 'test.csv',
|
||||
'disk' => 'local',
|
||||
'path' => 'imports/test.csv',
|
||||
'status' => 'queued',
|
||||
'meta' => ['has_header' => true, 'columns' => ['client_ref', 'contract.reference', 'contract.description']],
|
||||
'import_template_id' => null,
|
||||
]);
|
||||
|
||||
// Mapping: map client_ref to client_case.client_ref and contract fields
|
||||
DB::table('import_mappings')->insert([
|
||||
'import_id' => $import->id,
|
||||
'source_column' => 'client_ref',
|
||||
'target_field' => 'client_case.client_ref',
|
||||
'transform' => 'trim|upper',
|
||||
'apply_mode' => 'both',
|
||||
'options' => null,
|
||||
'position' => 0,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
DB::table('import_mappings')->insert([
|
||||
'import_id' => $import->id,
|
||||
'source_column' => 'contract.reference',
|
||||
'target_field' => 'contract.reference',
|
||||
'transform' => 'trim|upper|ref',
|
||||
'apply_mode' => 'both',
|
||||
'options' => null,
|
||||
'position' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
DB::table('import_mappings')->insert([
|
||||
'import_id' => $import->id,
|
||||
'source_column' => 'contract.description',
|
||||
'target_field' => 'contract.description',
|
||||
'transform' => 'trim',
|
||||
'apply_mode' => 'both',
|
||||
'options' => null,
|
||||
'position' => 2,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
// Process
|
||||
$service = app(\App\Services\ImportProcessor::class);
|
||||
$service->process($import, $user);
|
||||
|
||||
// There should be exactly one client case for CASE-001 and two contracts under it
|
||||
$ccs = \App\Models\ClientCase::where('client_id', $client->id)->where('client_ref', 'CASE-001')->get();
|
||||
// Ensure at least one exists
|
||||
expect($ccs->count())->toBeGreaterThanOrEqual(1);
|
||||
// Dedup to 1
|
||||
expect($ccs->count())->toBe(1);
|
||||
$contracts = \App\Models\Contract::where('client_case_id', $ccs->first()->id)->get();
|
||||
expect($contracts->count())->toBe(2);
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Contract;
|
||||
use App\Models\Import;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('reuses a single person per client_ref (client_case) across multiple rows', function () {
|
||||
\Illuminate\Support\Facades\Artisan::call('db:seed', ['--force' => true]);
|
||||
$user = User::factory()->create();
|
||||
Auth::login($user);
|
||||
|
||||
$client = Client::factory()->create();
|
||||
Storage::fake('local');
|
||||
$csv = "client_ref,contract.reference,person.first_name\nCASE-PR-1,REF-100,Ana\nCASE-PR-1,REF-200,Borut\n";
|
||||
Storage::disk('local')->put('imports/ppl.csv', $csv);
|
||||
|
||||
$import = Import::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'user_id' => $user->id,
|
||||
'client_id' => $client->id,
|
||||
'source_type' => 'csv',
|
||||
'file_name' => 'ppl.csv',
|
||||
'original_name' => 'ppl.csv',
|
||||
'disk' => 'local',
|
||||
'path' => 'imports/ppl.csv',
|
||||
'status' => 'queued',
|
||||
'meta' => ['has_header' => true, 'columns' => ['client_ref','contract.reference','person.first_name']],
|
||||
'import_template_id' => null,
|
||||
]);
|
||||
|
||||
// Map client_ref, contract.reference, and a person field
|
||||
DB::table('import_mappings')->insert([
|
||||
'import_id' => $import->id,
|
||||
'source_column' => 'client_ref',
|
||||
'target_field' => 'client_case.client_ref',
|
||||
'transform' => 'trim|upper',
|
||||
'apply_mode' => 'both',
|
||||
'options' => null,
|
||||
'position' => 0,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
DB::table('import_mappings')->insert([
|
||||
'import_id' => $import->id,
|
||||
'source_column' => 'contract.reference',
|
||||
'target_field' => 'contract.reference',
|
||||
'transform' => 'trim|upper|ref',
|
||||
'apply_mode' => 'both',
|
||||
'options' => null,
|
||||
'position' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
DB::table('import_mappings')->insert([
|
||||
'import_id' => $import->id,
|
||||
'source_column' => 'person.first_name',
|
||||
'target_field' => 'person.first_name',
|
||||
'transform' => 'trim',
|
||||
'apply_mode' => 'both',
|
||||
'options' => null,
|
||||
'position' => 2,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$service = app(\App\Services\ImportProcessor::class);
|
||||
$service->process($import, $user);
|
||||
|
||||
$case = \App\Models\ClientCase::where('client_id', $client->id)->where('client_ref', 'CASE-PR-1')->first();
|
||||
expect($case)->not->toBeNull();
|
||||
expect($case->person_id)->not->toBeNull();
|
||||
|
||||
// Both contracts should be under the same case
|
||||
$countContracts = Contract::where('client_case_id', $case->id)->count();
|
||||
expect($countContracts)->toBe(2);
|
||||
|
||||
// The person assigned to the case should be the only person linked for this case usage
|
||||
$personId = $case->person_id;
|
||||
// There should not be any other person created solely due to these rows; we assert that attaching contacts is done to the same person
|
||||
// To be conservative, assert that person exists and is used; deeper global uniqueness is not enforced here.
|
||||
expect($personId)->toBeInt();
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use App\Services\CsvImportService;
|
||||
|
||||
it('detects semicolon-delimited headers after leading blanks', function () {
|
||||
$tmpPath = storage_path('app/test_csv_detection_semicolon.csv');
|
||||
$content = "\n\n\nCol A;Col B;Col C\n1;2;3\n";
|
||||
// Prepend UTF-8 BOM to simulate common exports
|
||||
$content = "\xEF\xBB\xBF".$content;
|
||||
file_put_contents($tmpPath, $content);
|
||||
|
||||
$svc = new CsvImportService;
|
||||
[$delim, $cols] = $svc->detectColumnsFromCsv($tmpPath, true);
|
||||
|
||||
expect($delim)->toBe(';');
|
||||
expect($cols)->toBe(['Col A', 'Col B', 'Col C']);
|
||||
|
||||
@unlink($tmpPath);
|
||||
});
|
||||
|
||||
it('returns positional indices when hasHeader is false', function () {
|
||||
$tmpPath = storage_path('app/test_csv_detection_positional.csv');
|
||||
$content = "\n\nA;B;C\n";
|
||||
file_put_contents($tmpPath, $content);
|
||||
|
||||
$svc = new CsvImportService;
|
||||
[$delim, $cols] = $svc->detectColumnsFromCsv($tmpPath, false);
|
||||
|
||||
expect($delim)->toBe(';');
|
||||
expect($cols)->toBe(['0', '1', '2']);
|
||||
|
||||
@unlink($tmpPath);
|
||||
});
|
||||
Reference in New Issue
Block a user