Compare commits
5 Commits
5244698384
...
v1.9.31
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48c5c5bb33 | ||
| 1e2a1dae62 | |||
|
|
2a8042ba50 | ||
|
|
bc32648918 | ||
|
|
9027917ea2 |
@@ -69,3 +69,8 @@ when@test:
|
|||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
public: true
|
public: true
|
||||||
|
|
||||||
|
App\Service\SkeletonStructureService:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
public: true
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '1.9.30'
|
app.version: '1.9.31'
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ const formulaBuilderCustomFields = computed(() => {
|
|||||||
|
|
||||||
const extractFormulaFields = (formula: string | null | undefined): string[] => {
|
const extractFormulaFields = (formula: string | null | undefined): string[] => {
|
||||||
if (!formula) return []
|
if (!formula) return []
|
||||||
const matches = [...formula.matchAll(/\{(\w+)\}/g)]
|
const matches = [...formula.matchAll(/\{([^}]+)\}/gu)]
|
||||||
return [...new Set(matches.map(m => m[1]).filter((n): n is string => n !== undefined))]
|
return [...new Set(matches.map(m => m[1]).filter((n): n is string => n !== undefined))]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ const preview = computed(() => {
|
|||||||
fieldMap.set(f.name, previewExamples[f.type] ?? 'VALEUR')
|
fieldMap.set(f.name, previewExamples[f.type] ?? 'VALEUR')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return props.modelValue.replace(/\{(\w+)\}/g, (_, name) => fieldMap.get(name) ?? '???')
|
return props.modelValue.replace(/\{([^}]+)\}/gu, (_, name) => fieldMap.get(name) ?? '???')
|
||||||
})
|
})
|
||||||
|
|
||||||
const insertField = (fieldName: string) => {
|
const insertField = (fieldName: string) => {
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ const handleSubmit = async (payload: Parameters<typeof updateModelType>[1]) => {
|
|||||||
await updateModelType(id, enrichedPayload)
|
await updateModelType(id, enrichedPayload)
|
||||||
await syncExecute(id, { confirmDeletions: false, confirmTypeChanges: false })
|
await syncExecute(id, { confirmDeletions: false, confirmTypeChanges: false })
|
||||||
await loadComponentTypes({ force: true })
|
await loadComponentTypes({ force: true })
|
||||||
|
await loadCategory()
|
||||||
showSuccess('Catégorie de composant mise à jour avec succès.')
|
showSuccess('Catégorie de composant mise à jour avec succès.')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -183,6 +184,7 @@ const handleSyncConfirm = async () => {
|
|||||||
confirmTypeChanges: !!hasModifications,
|
confirmTypeChanges: !!hasModifications,
|
||||||
})
|
})
|
||||||
await loadComponentTypes({ force: true })
|
await loadComponentTypes({ force: true })
|
||||||
|
await loadCategory()
|
||||||
showSuccess('Catégorie de composant mise à jour avec succès.')
|
showSuccess('Catégorie de composant mise à jour avec succès.')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(normalizeError(error))
|
showError(normalizeError(error))
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ const handleSubmit = async (payload: Parameters<typeof updateModelType>[1]) => {
|
|||||||
await updateModelType(id, enrichedPayload)
|
await updateModelType(id, enrichedPayload)
|
||||||
await syncExecute(id, { confirmDeletions: false, confirmTypeChanges: false })
|
await syncExecute(id, { confirmDeletions: false, confirmTypeChanges: false })
|
||||||
await loadPieceTypes({ force: true })
|
await loadPieceTypes({ force: true })
|
||||||
|
await loadCategory()
|
||||||
showSuccess('Catégorie de pièce mise à jour avec succès.')
|
showSuccess('Catégorie de pièce mise à jour avec succès.')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -181,6 +182,7 @@ const handleSyncConfirm = async () => {
|
|||||||
confirmTypeChanges: !!hasModifications,
|
confirmTypeChanges: !!hasModifications,
|
||||||
})
|
})
|
||||||
await loadPieceTypes({ force: true })
|
await loadPieceTypes({ force: true })
|
||||||
|
await loadCategory()
|
||||||
showSuccess('Catégorie de pièce mise à jour avec succès.')
|
showSuccess('Catégorie de pièce mise à jour avec succès.')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(normalizeError(error))
|
showError(normalizeError(error))
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class ReferenceAutoGenerator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return preg_replace_callback('/\{(\w+)\}/', static function (array $matches) use ($valueMap): string {
|
return preg_replace_callback('/\{([^}]+)\}/u', static function (array $matches) use ($valueMap): string {
|
||||||
return $valueMap[$matches[1]] ?? '';
|
return $valueMap[$matches[1]] ?? '';
|
||||||
}, $modelType->getReferenceFormula());
|
}, $modelType->getReferenceFormula());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,6 +226,13 @@ class SkeletonStructureService
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($existingField) {
|
if ($existingField) {
|
||||||
|
// Propagate rename to the parent ModelType's reference formula and required-fields list
|
||||||
|
// so existing `{oldName}` placeholders keep resolving after the field is renamed.
|
||||||
|
$oldName = $existingField->getName();
|
||||||
|
if ($oldName !== $normalized['name']) {
|
||||||
|
$this->propagateCustomFieldRename($modelType, $oldName, $normalized['name']);
|
||||||
|
}
|
||||||
|
|
||||||
// Update existing field
|
// Update existing field
|
||||||
$existingField->setName($normalized['name']);
|
$existingField->setName($normalized['name']);
|
||||||
$existingField->setType($normalized['type']);
|
$existingField->setType($normalized['type']);
|
||||||
@@ -264,6 +271,38 @@ class SkeletonStructureService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function propagateCustomFieldRename(ModelType $modelType, string $oldName, string $newName): void
|
||||||
|
{
|
||||||
|
$formula = $modelType->getReferenceFormula();
|
||||||
|
if (null !== $formula && '' !== $formula) {
|
||||||
|
$newFormula = preg_replace(
|
||||||
|
'/\{'.preg_quote($oldName, '/').'\}/',
|
||||||
|
'{'.$newName.'}',
|
||||||
|
$formula
|
||||||
|
);
|
||||||
|
if (null !== $newFormula && $newFormula !== $formula) {
|
||||||
|
$modelType->setReferenceFormula($newFormula);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$required = $modelType->getRequiredFieldsForReference();
|
||||||
|
if ($required) {
|
||||||
|
$changed = false;
|
||||||
|
$newRequired = [];
|
||||||
|
foreach ($required as $fieldName) {
|
||||||
|
if ($fieldName === $oldName) {
|
||||||
|
$newRequired[] = $newName;
|
||||||
|
$changed = true;
|
||||||
|
} else {
|
||||||
|
$newRequired[] = $fieldName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($changed) {
|
||||||
|
$modelType->setRequiredFieldsForReference($newRequired);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize frontend custom field data to a common shape.
|
* Normalize frontend custom field data to a common shape.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -145,6 +145,69 @@ class ReferenceAutoGeneratorTest extends AbstractApiTestCase
|
|||||||
self::assertSame('U507', $result);
|
self::assertSame('U507', $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGenerateWithAccentedFieldName(): void
|
||||||
|
{
|
||||||
|
$mt = $this->createModelType('Palier', 'PAL-ACCENT', ModelCategory::PIECE);
|
||||||
|
$mt->setReferenceFormula('PA-{Diamètre}-33');
|
||||||
|
$mt->setRequiredFieldsForReference(['Diamètre']);
|
||||||
|
|
||||||
|
$em = $this->getEntityManager();
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$cf = $this->createCustomField('Diamètre', 'number', typePiece: $mt);
|
||||||
|
$piece = $this->createPiece('Palier 70', null, $mt);
|
||||||
|
$this->createCustomFieldValue($cf, '70', piece: $piece);
|
||||||
|
|
||||||
|
$em->refresh($piece);
|
||||||
|
|
||||||
|
$generator = self::getContainer()->get('App\Service\ReferenceAutoGenerator');
|
||||||
|
$result = $generator->generate($piece);
|
||||||
|
|
||||||
|
self::assertSame('PA-70-33', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGenerateWithNumberTypeField(): void
|
||||||
|
{
|
||||||
|
$mt = $this->createModelType('NumberField', 'NUM-001', ModelCategory::PIECE);
|
||||||
|
$mt->setReferenceFormula('R-{taille}');
|
||||||
|
$mt->setRequiredFieldsForReference(['taille']);
|
||||||
|
|
||||||
|
$em = $this->getEntityManager();
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$cf = $this->createCustomField('taille', 'number', typePiece: $mt);
|
||||||
|
$piece = $this->createPiece('Piece Number', null, $mt);
|
||||||
|
$this->createCustomFieldValue($cf, '42', piece: $piece);
|
||||||
|
|
||||||
|
$em->refresh($piece);
|
||||||
|
|
||||||
|
$generator = self::getContainer()->get('App\Service\ReferenceAutoGenerator');
|
||||||
|
$result = $generator->generate($piece);
|
||||||
|
|
||||||
|
self::assertSame('R-42', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGenerateWithDecimalNumberField(): void
|
||||||
|
{
|
||||||
|
$mt = $this->createModelType('NumberDec', 'NUM-002', ModelCategory::PIECE);
|
||||||
|
$mt->setReferenceFormula('R-{taille}');
|
||||||
|
$mt->setRequiredFieldsForReference(['taille']);
|
||||||
|
|
||||||
|
$em = $this->getEntityManager();
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$cf = $this->createCustomField('taille', 'number', typePiece: $mt);
|
||||||
|
$piece = $this->createPiece('Piece Dec', null, $mt);
|
||||||
|
$this->createCustomFieldValue($cf, '12.5', piece: $piece);
|
||||||
|
|
||||||
|
$em->refresh($piece);
|
||||||
|
|
||||||
|
$generator = self::getContainer()->get('App\Service\ReferenceAutoGenerator');
|
||||||
|
$result = $generator->generate($piece);
|
||||||
|
|
||||||
|
self::assertSame('R-12.5', $result);
|
||||||
|
}
|
||||||
|
|
||||||
public function testGenerateWithSpaceInFormula(): void
|
public function testGenerateWithSpaceInFormula(): void
|
||||||
{
|
{
|
||||||
$mt = $this->createModelType('Palier2', 'PAL-002', ModelCategory::PIECE);
|
$mt = $this->createModelType('Palier2', 'PAL-002', ModelCategory::PIECE);
|
||||||
|
|||||||
76
tests/Service/SkeletonStructureServiceTest.php
Normal file
76
tests/Service/SkeletonStructureServiceTest.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Service;
|
||||||
|
|
||||||
|
use App\Enum\ModelCategory;
|
||||||
|
use App\Service\SkeletonStructureService;
|
||||||
|
use App\Tests\AbstractApiTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class SkeletonStructureServiceTest extends AbstractApiTestCase
|
||||||
|
{
|
||||||
|
public function testRenameCustomFieldPropagatesToReferenceFormulaAndRequiredFields(): void
|
||||||
|
{
|
||||||
|
$mt = $this->createModelType('Roulement', 'ROUL-RENAME', ModelCategory::PIECE);
|
||||||
|
$mt->setReferenceFormula('{material}-{size}');
|
||||||
|
$mt->setRequiredFieldsForReference(['material', 'size']);
|
||||||
|
|
||||||
|
$em = $this->getEntityManager();
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$cfMaterial = $this->createCustomField('material', 'text', typePiece: $mt, orderIndex: 0);
|
||||||
|
$cfSize = $this->createCustomField('size', 'text', typePiece: $mt, orderIndex: 1);
|
||||||
|
|
||||||
|
/** @var SkeletonStructureService $service */
|
||||||
|
$service = static::getContainer()->get(SkeletonStructureService::class);
|
||||||
|
|
||||||
|
// Same fields, but `material` is renamed to `materiau` (matched by customFieldId)
|
||||||
|
$service->updateSkeletonRequirements($mt, [
|
||||||
|
'customFields' => [
|
||||||
|
['customFieldId' => $cfMaterial->getId(), 'name' => 'materiau', 'type' => 'text', 'orderIndex' => 0],
|
||||||
|
['customFieldId' => $cfSize->getId(), 'name' => 'size', 'type' => 'text', 'orderIndex' => 1],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$em->flush();
|
||||||
|
$em->refresh($mt);
|
||||||
|
$em->refresh($cfMaterial);
|
||||||
|
|
||||||
|
self::assertSame('materiau', $cfMaterial->getName());
|
||||||
|
self::assertSame('{materiau}-{size}', $mt->getReferenceFormula());
|
||||||
|
self::assertSame(['materiau', 'size'], $mt->getRequiredFieldsForReference());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRenameLeavesFormulaUnchangedWhenFieldNotInFormula(): void
|
||||||
|
{
|
||||||
|
$mt = $this->createModelType('Roulement2', 'ROUL-RENAME2', ModelCategory::PIECE);
|
||||||
|
$mt->setReferenceFormula('{material}');
|
||||||
|
$mt->setRequiredFieldsForReference(['material']);
|
||||||
|
|
||||||
|
$em = $this->getEntityManager();
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$cfMaterial = $this->createCustomField('material', 'text', typePiece: $mt, orderIndex: 0);
|
||||||
|
$cfUnused = $this->createCustomField('unused', 'text', typePiece: $mt, orderIndex: 1);
|
||||||
|
|
||||||
|
/** @var SkeletonStructureService $service */
|
||||||
|
$service = static::getContainer()->get(SkeletonStructureService::class);
|
||||||
|
|
||||||
|
$service->updateSkeletonRequirements($mt, [
|
||||||
|
'customFields' => [
|
||||||
|
['customFieldId' => $cfMaterial->getId(), 'name' => 'material', 'type' => 'text', 'orderIndex' => 0],
|
||||||
|
['customFieldId' => $cfUnused->getId(), 'name' => 'renamed', 'type' => 'text', 'orderIndex' => 1],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$em->flush();
|
||||||
|
$em->refresh($mt);
|
||||||
|
|
||||||
|
self::assertSame('{material}', $mt->getReferenceFormula());
|
||||||
|
self::assertSame(['material'], $mt->getRequiredFieldsForReference());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user