|
@@ -0,0 +1,279 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace Tests\Unit;
|
|
|
|
|
+
|
|
|
|
|
+use App\Http\Requests\UnitRequest;
|
|
|
|
|
+use App\Http\Resources\UnitResource;
|
|
|
|
|
+use App\Models\StudentContract;
|
|
|
|
|
+use App\Models\Unit;
|
|
|
|
|
+use App\Services\StudentContractService;
|
|
|
|
|
+use Illuminate\Http\Request;
|
|
|
|
|
+use Illuminate\Support\Facades\Validator as ValidatorFacade;
|
|
|
|
|
+use Tests\TestCase;
|
|
|
|
|
+
|
|
|
|
|
+class StudentContractProtocolTest extends TestCase
|
|
|
|
|
+{
|
|
|
|
|
+ public function test_it_generates_first_protocol_as_000001_when_no_contracts_exist(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $service = new class extends StudentContractService {
|
|
|
|
|
+ public function getMaxProtocolNumber(): int
|
|
|
|
|
+ {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertSame('000001', $service->generateNextProtocol());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_it_generates_sequential_protocol_based_on_max_existing_number(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $service = new class extends StudentContractService {
|
|
|
|
|
+ public int $max = 0;
|
|
|
|
|
+
|
|
|
|
|
+ public function getMaxProtocolNumber(): int
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->max;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ $service->max = 1;
|
|
|
|
|
+ $this->assertSame('000002', $service->generateNextProtocol());
|
|
|
|
|
+
|
|
|
|
|
+ $service->max = 9;
|
|
|
|
|
+ $this->assertSame('000010', $service->generateNextProtocol());
|
|
|
|
|
+
|
|
|
|
|
+ $service->max = 99;
|
|
|
|
|
+ $this->assertSame('000100', $service->generateNextProtocol());
|
|
|
|
|
+
|
|
|
|
|
+ $service->max = 999;
|
|
|
|
|
+ $this->assertSame('001000', $service->generateNextProtocol());
|
|
|
|
|
+
|
|
|
|
|
+ $service->max = 999999;
|
|
|
|
|
+ $this->assertSame('1000000', $service->generateNextProtocol());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_it_respects_global_sequence_across_multiple_units(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Simulating contracts across multiple units:
|
|
|
|
|
+ // Unit 1 created contract 000001, Unit 2 created contract 000002, Unit 1 created contract 000003
|
|
|
|
|
+ $existingContracts = collect([
|
|
|
|
|
+ (object) ['unit_id' => 1, 'protocol' => '000001'],
|
|
|
|
|
+ (object) ['unit_id' => 2, 'protocol' => '000002'],
|
|
|
|
|
+ (object) ['unit_id' => 1, 'protocol' => '000003'],
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $service = new class($existingContracts) extends StudentContractService {
|
|
|
|
|
+ public function __construct(private $contracts) {}
|
|
|
|
|
+
|
|
|
|
|
+ public function getMaxProtocolNumber(): int
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->contracts
|
|
|
|
|
+ ->filter(fn ($c) => ctype_digit((string) $c->protocol))
|
|
|
|
|
+ ->map(fn ($c) => (int) $c->protocol)
|
|
|
|
|
+ ->max() ?? 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Next protocol for ANY unit should be 000004
|
|
|
|
|
+ $this->assertSame('000004', $service->generateNextProtocol());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_it_ignores_non_numeric_manual_protocols_when_calculating_next_sequence(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $existingContracts = collect([
|
|
|
|
|
+ (object) ['unit_id' => 1, 'protocol' => '000005'],
|
|
|
|
|
+ (object) ['unit_id' => 2, 'protocol' => 'CUSTOM-ABC-123'],
|
|
|
|
|
+ (object) ['unit_id' => 1, 'protocol' => 'CONTRATO-2026'],
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $service = new class($existingContracts) extends StudentContractService {
|
|
|
|
|
+ public function __construct(private $contracts) {}
|
|
|
|
|
+
|
|
|
|
|
+ public function getMaxProtocolNumber(): int
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->contracts
|
|
|
|
|
+ ->filter(fn ($c) => ctype_digit((string) $c->protocol))
|
|
|
|
|
+ ->map(fn ($c) => (int) $c->protocol)
|
|
|
|
|
+ ->max() ?? 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Highest numeric is 5, so next sequence is 000006
|
|
|
|
|
+ $this->assertSame('000006', $service->generateNextProtocol());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_it_recalculates_protocol_in_automatic_mode_when_creating_contract(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $unit = new Unit;
|
|
|
|
|
+ $unit->id = 10;
|
|
|
|
|
+ $unit->automatic_protocol = true;
|
|
|
|
|
+
|
|
|
|
|
+ $service = new class($unit) extends StudentContractService {
|
|
|
|
|
+ public array $capturedData = [];
|
|
|
|
|
+
|
|
|
|
|
+ public function __construct(private Unit $mockUnit) {}
|
|
|
|
|
+
|
|
|
|
|
+ protected function findUnit(int $unitId): ?Unit
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->mockUnit;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function generateNextProtocol(): string
|
|
|
|
|
+ {
|
|
|
|
|
+ return '000007';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function create(array $data): StudentContract
|
|
|
|
|
+ {
|
|
|
|
|
+ if (! empty($data['due_day'])) {
|
|
|
|
|
+ $data['recurring_day'] = (int) $data['due_day'];
|
|
|
|
|
+ }
|
|
|
|
|
+ unset($data['due_day']);
|
|
|
|
|
+
|
|
|
|
|
+ $unitId = $data['unit_id'] ?? null;
|
|
|
|
|
+ $isAutomatic = true;
|
|
|
|
|
+
|
|
|
|
|
+ if ($unitId) {
|
|
|
|
|
+ $unit = $this->findUnit($unitId);
|
|
|
|
|
+ if ($unit && $unit->automatic_protocol === false) {
|
|
|
|
|
+ $isAutomatic = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($isAutomatic) {
|
|
|
|
|
+ $data['protocol'] = $this->generateNextProtocol();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $this->capturedData = $data;
|
|
|
|
|
+
|
|
|
|
|
+ $contract = new StudentContract;
|
|
|
|
|
+ $contract->setRawAttributes($data);
|
|
|
|
|
+
|
|
|
|
|
+ return $contract;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Client sent old/stale protocol '000001'
|
|
|
|
|
+ $contract = $service->create([
|
|
|
|
|
+ 'unit_id' => 10,
|
|
|
|
|
+ 'protocol' => '000001',
|
|
|
|
|
+ 'status' => 'active',
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ // Service recalculates to '000007'
|
|
|
|
|
+ $this->assertSame('000007', $contract->protocol);
|
|
|
|
|
+ $this->assertSame('000007', $service->capturedData['protocol']);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_it_preserves_custom_protocol_when_unit_automatic_protocol_is_disabled(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $unit = new Unit;
|
|
|
|
|
+ $unit->id = 20;
|
|
|
|
|
+ $unit->automatic_protocol = false;
|
|
|
|
|
+
|
|
|
|
|
+ $service = new class($unit) extends StudentContractService {
|
|
|
|
|
+ public array $capturedData = [];
|
|
|
|
|
+
|
|
|
|
|
+ public function __construct(private Unit $mockUnit) {}
|
|
|
|
|
+
|
|
|
|
|
+ protected function findUnit(int $unitId): ?Unit
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->mockUnit;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function generateNextProtocol(): string
|
|
|
|
|
+ {
|
|
|
|
|
+ return '000099';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function create(array $data): StudentContract
|
|
|
|
|
+ {
|
|
|
|
|
+ if (! empty($data['due_day'])) {
|
|
|
|
|
+ $data['recurring_day'] = (int) $data['due_day'];
|
|
|
|
|
+ }
|
|
|
|
|
+ unset($data['due_day']);
|
|
|
|
|
+
|
|
|
|
|
+ $unitId = $data['unit_id'] ?? null;
|
|
|
|
|
+ $isAutomatic = true;
|
|
|
|
|
+
|
|
|
|
|
+ if ($unitId) {
|
|
|
|
|
+ $unit = $this->findUnit($unitId);
|
|
|
|
|
+ if ($unit && $unit->automatic_protocol === false) {
|
|
|
|
|
+ $isAutomatic = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($isAutomatic) {
|
|
|
|
|
+ $data['protocol'] = $this->generateNextProtocol();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $this->capturedData = $data;
|
|
|
|
|
+
|
|
|
|
|
+ $contract = new StudentContract;
|
|
|
|
|
+ $contract->setRawAttributes($data);
|
|
|
|
|
+
|
|
|
|
|
+ return $contract;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Franchisee with manual mode enters custom protocol
|
|
|
|
|
+ $contract = $service->create([
|
|
|
|
|
+ 'unit_id' => 20,
|
|
|
|
|
+ 'protocol' => 'MANUAL-2026-001',
|
|
|
|
|
+ 'status' => 'active',
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertSame('MANUAL-2026-001', $contract->protocol);
|
|
|
|
|
+ $this->assertSame('MANUAL-2026-001', $service->capturedData['protocol']);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_unit_model_casts_automatic_protocol_to_boolean(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $unit = new Unit;
|
|
|
|
|
+ $unit->automatic_protocol = 1;
|
|
|
|
|
+ $this->assertTrue($unit->automatic_protocol);
|
|
|
|
|
+
|
|
|
|
|
+ $unit->automatic_protocol = 0;
|
|
|
|
|
+ $this->assertFalse($unit->automatic_protocol);
|
|
|
|
|
+
|
|
|
|
|
+ $unit->automatic_protocol = 'true';
|
|
|
|
|
+ $this->assertTrue($unit->automatic_protocol);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_unit_resource_includes_automatic_protocol(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $unit = new Unit;
|
|
|
|
|
+ $unit->id = 1;
|
|
|
|
|
+ $unit->name = 'Unidade Teste';
|
|
|
|
|
+ $unit->social_reason = 'Unidade Teste LTDA';
|
|
|
|
|
+ $unit->automatic_protocol = false;
|
|
|
|
|
+ $unit->created_at = now();
|
|
|
|
|
+ $unit->updated_at = now();
|
|
|
|
|
+
|
|
|
|
|
+ $resource = new UnitResource($unit);
|
|
|
|
|
+ $array = $resource->toArray(Request::create('/'));
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertArrayHasKey('automatic_protocol', $array);
|
|
|
|
|
+ $this->assertFalse($array['automatic_protocol']);
|
|
|
|
|
+
|
|
|
|
|
+ $unit->automatic_protocol = true;
|
|
|
|
|
+ $resourceTrue = new UnitResource($unit);
|
|
|
|
|
+ $arrayTrue = $resourceTrue->toArray(Request::create('/'));
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertTrue($arrayTrue['automatic_protocol']);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_unit_request_validates_automatic_protocol_as_boolean(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $rules = ['automatic_protocol' => (new UnitRequest)->rules()['automatic_protocol']];
|
|
|
|
|
+
|
|
|
|
|
+ $validValidator = ValidatorFacade::make(['automatic_protocol' => true], $rules);
|
|
|
|
|
+ $this->assertFalse($validValidator->errors()->has('automatic_protocol'));
|
|
|
|
|
+
|
|
|
|
|
+ $validFalseValidator = ValidatorFacade::make(['automatic_protocol' => false], $rules);
|
|
|
|
|
+ $this->assertFalse($validFalseValidator->errors()->has('automatic_protocol'));
|
|
|
|
|
+
|
|
|
|
|
+ $invalidValidator = ValidatorFacade::make(['automatic_protocol' => 'not-a-boolean'], $rules);
|
|
|
|
|
+ $this->assertTrue($invalidValidator->errors()->has('automatic_protocol'));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|