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')); } }