0620635abb
Audit pass (33 new tests in tests/Feature/Audit/): - CrmFunnelE2ETest: full Lead→Deal→Appointment→WO→Payment journey, covering 5 previously-untested models. Verifies WO.balanceDue updates correctly after payments, including refunds (negative amount → balance increases). - WorkOrderTotalsTest: works+parts+subcontract+discount sum correctly, cancelled subcontract excluded, deleting lines triggers recalc, status=done consumes part reservations into issues, cancelled releases reservations. - ShopJourneyE2ETest: register→cart→checkout→email confirmation→tracking page reachable→admin fulfills→stock drops→warehouse event recorded. Also guest checkout still works without account. - CsvImportExportTest: round-trip, dedup-by-phone, **caught real bug** — Vehicle export wrote $row->brand (no such property) and import set 'brand' => row['brand'] in Vehicle::create (column is `make`). Fix applied to both paths. - TenantBackupServiceTest: zip contains valid manifest with counts + data/*.json per model + works embedded with WorkOrder. - WorkOrderPdfServiceTest: generated PDF starts with %PDF, includes WO data, non-trivial size, handles empty WO. - PayrollCalculatorTest: base + works_pct + parts_pct + bonus - fine - advance, scoped to user + period. - NotificationFallbackTest: Telegram wins when chat_id present, falls back to email when not, returns false when neither, tenant disable flag stops both. - AiProvidersCrossCheckTest: OpenAI request shape, Gemini URL with model, no-key friendly message, tenant model override propagates into HTTP body. - SettingsPersistenceTest: 25-key settings JSON round-trips, partial update via array_replace_recursive preserves other keys. - CompanyProvisionerTest: suspend / reactivate / archive behavior. Bug fixed: CsvImportExport used `brand` on Vehicle which has column `make`. The export silently emitted empty values, the import silently dropped the brand. Now both paths use `make`. Full suite: 173 passed (468 assertions). 0 failed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
126 lines
4.4 KiB
PHP
126 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Audit;
|
|
|
|
use App\Models\Central\Company;
|
|
use App\Models\Central\Plan;
|
|
use App\Models\Tenant\Client;
|
|
use App\Models\Tenant\Vehicle;
|
|
use App\Services\CsvImportExport;
|
|
use App\Tenancy\TenantManager;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
class CsvImportExportTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_import_clients_creates_records_with_correct_fields(): void
|
|
{
|
|
$this->bootTenant();
|
|
|
|
$csv = $this->makeCsv(CsvImportExport::CLIENT_COLUMNS, [
|
|
['Ion Pop', '+37369000001', '', 'ion@example.com', '', 'individual', 'active', 'site', '', '0', '0', ''],
|
|
['SRL Auto', '+37322777888', '', '', 'SRL Auto', 'company', 'active', '', '', '5', '0', ''],
|
|
]);
|
|
|
|
$r = app(CsvImportExport::class)->importClients($csv);
|
|
@unlink($csv);
|
|
|
|
$this->assertEquals(2, $r['imported']);
|
|
$this->assertEquals(0, $r['skipped']);
|
|
$this->assertEquals(2, Client::count());
|
|
$this->assertEquals('Ion Pop', Client::where('phone', '+37369000001')->first()->name);
|
|
}
|
|
|
|
public function test_import_clients_skips_duplicate_phone(): void
|
|
{
|
|
$this->bootTenant();
|
|
Client::create([
|
|
'name' => 'Existing', 'phone' => '+37369000002',
|
|
'type' => 'individual', 'status' => 'active',
|
|
]);
|
|
|
|
$csv = $this->makeCsv(CsvImportExport::CLIENT_COLUMNS, [
|
|
['New Name', '+37369000002', '', '', '', 'individual', 'active', '', '', '0', '0', ''],
|
|
]);
|
|
$r = app(CsvImportExport::class)->importClients($csv);
|
|
@unlink($csv);
|
|
|
|
$this->assertEquals(0, $r['imported']);
|
|
$this->assertEquals(1, $r['skipped']);
|
|
$this->assertEquals('Existing', Client::first()->name);
|
|
}
|
|
|
|
public function test_import_vehicles_uses_make_column_correctly(): void
|
|
{
|
|
// This guards a real bug: the service used to write `brand`, but the
|
|
// vehicles table column is `make`. The import must persist `make`.
|
|
$ctx = $this->bootTenant();
|
|
Client::create([
|
|
'name' => 'Owner', 'phone' => '+37399100200',
|
|
'type' => 'individual', 'status' => 'active',
|
|
]);
|
|
|
|
$csv = $this->makeCsv(CsvImportExport::VEHICLE_COLUMNS, [
|
|
// plate, vin, brand(=make), model, year, engine, gearbox, fuel, mileage, color, notes, client_phone
|
|
['ABC-001', 'VIN1234567890ABCD', 'BMW', 'X5', '2019', '3.0i', 'AT', 'Benzină', '85000', 'negru', '', '+37399100200'],
|
|
]);
|
|
|
|
$r = app(CsvImportExport::class)->importVehicles($csv);
|
|
@unlink($csv);
|
|
|
|
$this->assertEquals(1, $r['imported'], 'one vehicle should be imported');
|
|
|
|
$v = Vehicle::first();
|
|
$this->assertNotNull($v, 'vehicle row exists');
|
|
$this->assertEquals('BMW', $v->make, 'make column populated from CSV brand header');
|
|
$this->assertEquals('X5', $v->model);
|
|
$this->assertEquals(2019, $v->year);
|
|
}
|
|
|
|
public function test_export_vehicles_emits_make_value(): void
|
|
{
|
|
$this->bootTenant();
|
|
$client = Client::create([
|
|
'name' => 'X', 'phone' => '+37377111222',
|
|
'type' => 'individual', 'status' => 'active',
|
|
]);
|
|
Vehicle::create([
|
|
'client_id' => $client->id,
|
|
'plate' => 'CSV-001', 'make' => 'BMW', 'model' => 'M3',
|
|
]);
|
|
|
|
ob_start();
|
|
$resp = app(CsvImportExport::class)->exportVehicles();
|
|
$resp->sendContent();
|
|
$csv = ob_get_clean();
|
|
|
|
$this->assertStringContainsString('BMW', $csv, 'exported CSV must include the make value');
|
|
$this->assertStringContainsString('CSV-001', $csv);
|
|
$this->assertStringContainsString('M3', $csv);
|
|
}
|
|
|
|
private function bootTenant(): array
|
|
{
|
|
$plan = Plan::firstOrCreate(['slug' => 'test'], ['name' => 'T', 'price' => 0, 'features' => []]);
|
|
$company = Company::create([
|
|
'plan_id' => $plan->id, 'slug' => 'csv-' . uniqid(),
|
|
'name' => 'CSV Co', 'status' => 'active',
|
|
]);
|
|
app(TenantManager::class)->setCurrent($company);
|
|
return compact('company');
|
|
}
|
|
|
|
private function makeCsv(array $headers, array $rows): string
|
|
{
|
|
$path = tempnam(sys_get_temp_dir(), 'csv') . '.csv';
|
|
$fh = fopen($path, 'w');
|
|
fputs($fh, "\xEF\xBB\xBF"); // BOM
|
|
fputcsv($fh, $headers);
|
|
foreach ($rows as $row) fputcsv($fh, $row);
|
|
fclose($fh);
|
|
return $path;
|
|
}
|
|
}
|