createGestionnaireClient(); $site = $this->createSite('Site A'); $modelType = $this->createModelType('Motor', 'MOT', ModelCategory::COMPONENT); $contextField = $this->createCustomField( name: 'Voltage', type: 'number', typeComposant: $modelType, machineContextOnly: true, ); $normalField = $this->createCustomField( name: 'Serial', type: 'text', typeComposant: $modelType, ); $machine = $this->createMachine('Machine A', $site); $composant = $this->createComposant('Motor 1', 'MOT-001', $modelType); $link = $this->createMachineComponentLink($machine, $composant); $this->createCustomFieldValue( customField: $contextField, value: '220', composant: $composant, machineComponentLink: $link, ); $response = $client->request('GET', '/api/machines/'.$machine->getId().'/structure'); $this->assertResponseIsSuccessful(); $data = $response->toArray(); $componentLink = $data['componentLinks'][0]; $this->assertArrayHasKey('contextCustomFields', $componentLink); $this->assertCount(1, $componentLink['contextCustomFields']); $this->assertSame('Voltage', $componentLink['contextCustomFields'][0]['name']); $this->assertTrue($componentLink['contextCustomFields'][0]['machineContextOnly']); $this->assertArrayHasKey('contextCustomFieldValues', $componentLink); $this->assertCount(1, $componentLink['contextCustomFieldValues']); $this->assertSame('220', $componentLink['contextCustomFieldValues'][0]['value']); $normalFields = array_filter( $componentLink['composant']['customFields'], fn (array $f) => 'Serial' === $f['name'], ); $this->assertCount(1, $normalFields); } public function testStructureReturnsContextFieldsOnPieceLink(): void { $client = $this->createGestionnaireClient(); $site = $this->createSite('Site B'); $modelType = $this->createModelType('Bearing', 'BRG', ModelCategory::PIECE); $contextField = $this->createCustomField( name: 'Wear Level', type: 'select', typePiece: $modelType, machineContextOnly: true, ); $contextField->setOptions(['Good', 'Fair', 'Replace']); $this->getEntityManager()->flush(); $machine = $this->createMachine('Machine B', $site); $piece = $this->createPiece('Bearing 1', 'BRG-001', $modelType); $link = $this->createMachinePieceLink($machine, $piece); $this->createCustomFieldValue( customField: $contextField, value: 'Fair', piece: $piece, machinePieceLink: $link, ); $response = $client->request('GET', '/api/machines/'.$machine->getId().'/structure'); $data = $response->toArray(); $pieceLink = $data['pieceLinks'][0]; $this->assertCount(1, $pieceLink['contextCustomFields']); $this->assertSame('Wear Level', $pieceLink['contextCustomFields'][0]['name']); $this->assertCount(1, $pieceLink['contextCustomFieldValues']); $this->assertSame('Fair', $pieceLink['contextCustomFieldValues'][0]['value']); } public function testUpsertContextFieldValueViaComponentLink(): void { $client = $this->createGestionnaireClient(); $site = $this->createSite('Site C'); $modelType = $this->createModelType('Pump', 'PMP', ModelCategory::COMPONENT); $contextField = $this->createCustomField( name: 'Flow Rate', type: 'number', typeComposant: $modelType, machineContextOnly: true, ); $machine = $this->createMachine('Machine C', $site); $composant = $this->createComposant('Pump 1', 'PMP-001', $modelType); $link = $this->createMachineComponentLink($machine, $composant); $response = $client->request('POST', '/api/custom-fields/values/upsert', [ 'json' => [ 'customFieldId' => $contextField->getId(), 'machineComponentLinkId' => $link->getId(), 'value' => '380', ], ]); $this->assertResponseIsSuccessful(); $data = $response->toArray(); $this->assertSame('380', $data['value']); } public function testSameComposantDifferentValuesPerMachine(): void { $client = $this->createGestionnaireClient(); $site = $this->createSite('Site D'); $modelType = $this->createModelType('Valve', 'VLV', ModelCategory::COMPONENT); $contextField = $this->createCustomField( name: 'Pressure', type: 'number', typeComposant: $modelType, machineContextOnly: true, ); $machineA = $this->createMachine('Machine A', $site); $machineB = $this->createMachine('Machine B', $site); $composant = $this->createComposant('Valve 1', 'VLV-001', $modelType); $linkA = $this->createMachineComponentLink($machineA, $composant); $linkB = $this->createMachineComponentLink($machineB, $composant); $this->createCustomFieldValue( customField: $contextField, value: '100', composant: $composant, machineComponentLink: $linkA, ); $this->createCustomFieldValue( customField: $contextField, value: '200', composant: $composant, machineComponentLink: $linkB, ); $dataA = $client->request('GET', '/api/machines/'.$machineA->getId().'/structure')->toArray(); $this->assertSame('100', $dataA['componentLinks'][0]['contextCustomFieldValues'][0]['value']); $dataB = $client->request('GET', '/api/machines/'.$machineB->getId().'/structure')->toArray(); $this->assertSame('200', $dataB['componentLinks'][0]['contextCustomFieldValues'][0]['value']); } public function testMachineContextOnlyFieldSerialization(): void { $client = $this->createGestionnaireClient(); $site = $this->createSite('Site E'); $modelType = $this->createModelType('Sensor', 'SNS', ModelCategory::COMPONENT); $contextField = $this->createCustomField( name: 'Calibration Date', type: 'date', typeComposant: $modelType, machineContextOnly: true, ); $response = $client->request('GET', '/api/custom_fields/'.$contextField->getId()); $this->assertResponseIsSuccessful(); $data = $response->toArray(); $this->assertTrue($data['machineContextOnly']); } public function testCloneMachineCopiesContextFieldValues(): void { $client = $this->createGestionnaireClient(); $site = $this->createSite('Site F'); $modelType = $this->createModelType('Motor Clone', 'MOTC', ModelCategory::COMPONENT); $contextField = $this->createCustomField( name: 'RPM Setting', type: 'number', typeComposant: $modelType, machineContextOnly: true, ); $source = $this->createMachine('Source Machine', $site); $composant = $this->createComposant('Motor C', 'MOTC-001', $modelType); $link = $this->createMachineComponentLink($source, $composant); $this->createCustomFieldValue( customField: $contextField, value: '3000', composant: $composant, machineComponentLink: $link, ); $response = $client->request('POST', '/api/machines/'.$source->getId().'/clone', [ 'json' => [ 'name' => 'Cloned Machine', 'siteId' => $site->getId(), ], ]); $this->assertResponseStatusCodeSame(201); $data = $response->toArray(); $clonedLink = $data['componentLinks'][0] ?? null; $this->assertNotNull($clonedLink); $this->assertCount(1, $clonedLink['contextCustomFieldValues']); $this->assertSame('3000', $clonedLink['contextCustomFieldValues'][0]['value']); } public function testCloneMachineCopiesPieceContextFieldValues(): void { $client = $this->createGestionnaireClient(); $site = $this->createSite('Site G'); $modelType = $this->createModelType('Bearing Clone', 'BRGC', ModelCategory::PIECE); $contextField = $this->createCustomField( name: 'Wear Level', type: 'text', typePiece: $modelType, machineContextOnly: true, ); $source = $this->createMachine('Source Piece Machine', $site); $piece = $this->createPiece('Bearing C', 'BRGC-001', $modelType); $link = $this->createMachinePieceLink($source, $piece); $this->createCustomFieldValue( customField: $contextField, value: 'Fair', piece: $piece, machinePieceLink: $link, ); $response = $client->request('POST', '/api/machines/'.$source->getId().'/clone', [ 'json' => [ 'name' => 'Cloned Piece Machine', 'siteId' => $site->getId(), ], ]); $this->assertResponseStatusCodeSame(201); $data = $response->toArray(); $clonedLink = $data['pieceLinks'][0] ?? null; $this->assertNotNull($clonedLink, 'Clone should expose at least one pieceLink'); $this->assertCount(1, $clonedLink['contextCustomFieldValues']); $this->assertSame('Fair', $clonedLink['contextCustomFieldValues'][0]['value']); } public function testCloneMachineLeavesSourceContextFieldValuesIntact(): void { $client = $this->createGestionnaireClient(); $site = $this->createSite('Site H'); $modelType = $this->createModelType('Motor Source', 'MOTS', ModelCategory::COMPONENT); $contextField = $this->createCustomField( name: 'RPM', type: 'number', typeComposant: $modelType, machineContextOnly: true, ); $source = $this->createMachine('Original Machine', $site); $composant = $this->createComposant('Motor S', 'MOTS-001', $modelType); $link = $this->createMachineComponentLink($source, $composant); $this->createCustomFieldValue( customField: $contextField, value: '1500', composant: $composant, machineComponentLink: $link, ); $client->request('POST', '/api/machines/'.$source->getId().'/clone', [ 'json' => [ 'name' => 'Clone Machine', 'siteId' => $site->getId(), ], ]); $this->assertResponseStatusCodeSame(201); // Source must still expose its original context field value $sourceData = $client->request('GET', '/api/machines/'.$source->getId().'/structure')->toArray(); $sourceLink = $sourceData['componentLinks'][0] ?? null; $this->assertNotNull($sourceLink, 'Source machine should still expose its component link'); $this->assertCount(1, $sourceLink['contextCustomFieldValues']); $this->assertSame('1500', $sourceLink['contextCustomFieldValues'][0]['value']); } }