Stage 3 — WO photos + ETA + QR + public tracking page
- HasMedia (Spatie) on WorkOrder with `photos` collection
- eta_at + tracking_token columns; token auto-generated on create
- Public /t/{token} page — tenant-scoped via subdomain, white-label themed
- QR code SVG via chillerlan/php-qrcode (inline modal + download)
- Filament: SpatieMediaLibraryFileUpload + ETA picker + tracking section
- EditWorkOrder header action "Link client (QR)" modal
- Fix: Auditable::dontSubmitEmptyLogs() → dontLogEmptyChanges() (removed in activitylog)
- Tests: TrackingPageTest (4 pass) covering token gen + cross-tenant isolation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Central\Company;
|
||||
use App\Models\Central\Plan;
|
||||
use App\Models\Tenant\Client;
|
||||
use App\Models\Tenant\Vehicle;
|
||||
use App\Models\Tenant\WorkOrder;
|
||||
use App\Tenancy\TenantManager;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TrackingPageTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_tracking_page_returns_200_on_valid_token(): void
|
||||
{
|
||||
$wo = $this->makeWorkOrder('alpha');
|
||||
|
||||
$response = $this->get('http://alpha.service.mir.md/t/' . $wo->tracking_token);
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee('#' . $wo->number);
|
||||
}
|
||||
|
||||
public function test_tracking_page_404_on_unknown_token(): void
|
||||
{
|
||||
$this->makeWorkOrder('beta');
|
||||
|
||||
$response = $this->get('http://beta.service.mir.md/t/NotARealTokenZZZZZ');
|
||||
$response->assertStatus(404);
|
||||
}
|
||||
|
||||
public function test_tracking_token_cannot_be_read_from_other_tenant(): void
|
||||
{
|
||||
$woA = $this->makeWorkOrder('gamma');
|
||||
|
||||
// Try to access tenant A's WO token from tenant B's subdomain.
|
||||
$this->makeCompany('delta');
|
||||
|
||||
$response = $this->get('http://delta.service.mir.md/t/' . $woA->tracking_token);
|
||||
$response->assertStatus(404);
|
||||
}
|
||||
|
||||
public function test_token_is_generated_on_create(): void
|
||||
{
|
||||
$wo = $this->makeWorkOrder('epsilon');
|
||||
$this->assertNotEmpty($wo->tracking_token);
|
||||
$this->assertGreaterThanOrEqual(16, strlen($wo->tracking_token));
|
||||
}
|
||||
|
||||
private function makeWorkOrder(string $slug): WorkOrder
|
||||
{
|
||||
$company = $this->makeCompany($slug);
|
||||
app(TenantManager::class)->setCurrent($company);
|
||||
|
||||
$client = Client::create([
|
||||
'name' => 'Test Client', 'phone' => '+37399' . random_int(100000, 999999),
|
||||
'type' => 'individual', 'status' => 'active',
|
||||
]);
|
||||
|
||||
$vehicle = Vehicle::create([
|
||||
'client_id' => $client->id,
|
||||
'make' => 'VW', 'model' => 'Golf', 'plate' => 'XYZ' . random_int(100, 999),
|
||||
]);
|
||||
|
||||
return WorkOrder::create([
|
||||
'number' => WorkOrder::generateNumber($company->id),
|
||||
'client_id' => $client->id,
|
||||
'vehicle_id' => $vehicle->id,
|
||||
'opened_at' => now(),
|
||||
'complaint' => 'Test',
|
||||
'status' => 'in_work',
|
||||
]);
|
||||
}
|
||||
|
||||
private function makeCompany(string $slug): Company
|
||||
{
|
||||
$plan = Plan::firstOrCreate(['slug' => 'test'], [
|
||||
'name' => 'Test', 'price' => 0, 'features' => [],
|
||||
]);
|
||||
|
||||
return Company::create([
|
||||
'plan_id' => $plan->id,
|
||||
'slug' => $slug,
|
||||
'name' => ucfirst($slug) . ' Service',
|
||||
'status' => 'active',
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user