Compare commits

...

6 Commits

Author SHA1 Message Date
gitea-actions
b304cf6684 chore : bump version to v1.9.21
All checks were successful
Auto Tag Develop / tag (push) Successful in 8s
Build & Push Docker Image / build (push) Successful in 36s
2026-04-06 15:12:40 +00:00
0fe7f3131e fix(model-type) : retirer l'éditeur de structure produit inutilisé
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Le PieceModelStructureEditor affiché pour les catégories PRODUCT ne
fonctionnait plus et n'est plus utilisé.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:12:29 +02:00
a6bbcaf6d1 fix(custom-fields) : masquer les champs machineContextOnly hors vue machine
Ajoute context: 'standalone' aux appels useCustomFieldInputs dans les
vues composant, pièce et produit (création et édition) pour filtrer
les champs perso réservés au contexte machine.

Exclut également ces champs de la formule de référence automatique
dans le ReferenceFormulaBuilder des catégories.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:12:29 +02:00
9f2e1da6ec fix(composant) : rendre les slots de structure optionnels à la création
Les emplacements pièces, produits et sous-composants du squelette ne
bloquent plus la soumission du formulaire de création de composant.
Les slots vides restent visibles en consultation avec l'indicateur rouge
« manquant » et peuvent être remplis ultérieurement en édition.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:12:29 +02:00
gitea-actions
7962576eec chore : bump version to v1.9.20
All checks were successful
Auto Tag Develop / tag (push) Successful in 8s
Build & Push Docker Image / build (push) Successful in 37s
2026-04-06 14:54:20 +00:00
7d98c1598c fix(deps) : update composer.lock with symfony/mime
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:54:09 +02:00
9 changed files with 209 additions and 39 deletions

175
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "2db01f705a09cf38007a2baa3b078e49",
"content-hash": "f94dc3c05e9ba6be99c510aad3d17182",
"packages": [
{
"name": "api-platform/doctrine-common",
@@ -5341,6 +5341,92 @@
],
"time": "2026-03-04T16:39:24+00:00"
},
{
"name": "symfony/mime",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
"reference": "ddff21f14c7ce04b98101b399a9463dce8b0ce66"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/ddff21f14c7ce04b98101b399a9463dce8b0ce66",
"reference": "ddff21f14c7ce04b98101b399a9463dce8b0ce66",
"shasum": ""
},
"require": {
"php": ">=8.4",
"symfony/polyfill-intl-idn": "^1.10",
"symfony/polyfill-mbstring": "^1.0"
},
"conflict": {
"egulias/email-validator": "~3.0.0",
"phpdocumentor/reflection-docblock": "<5.2|>=7",
"phpdocumentor/type-resolver": "<1.5.1"
},
"require-dev": {
"egulias/email-validator": "^2.1.10|^3.1|^4",
"league/html-to-markdown": "^5.0",
"phpdocumentor/reflection-docblock": "^5.2|^6.0",
"symfony/dependency-injection": "^7.4|^8.0",
"symfony/process": "^7.4|^8.0",
"symfony/property-access": "^7.4|^8.0",
"symfony/property-info": "^7.4|^8.0",
"symfony/serializer": "^7.4|^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Mime\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Allows manipulating MIME messages",
"homepage": "https://symfony.com",
"keywords": [
"mime",
"mime-type"
],
"support": {
"source": "https://github.com/symfony/mime/tree/v8.0.8"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/options-resolver",
"version": "v8.0.0",
@@ -5567,6 +5653,93 @@
],
"time": "2025-06-27T09:58:17+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3",
"reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3",
"shasum": ""
},
"require": {
"php": ">=7.2",
"symfony/polyfill-intl-normalizer": "^1.10"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Idn\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Laurent Bassin",
"email": "laurent@bassin.info"
},
{
"name": "Trevor Rowbotham",
"email": "trevor.rowbotham@pm.me"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"idn",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-10T14:38:51+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.33.0",

View File

@@ -1,2 +1,2 @@
parameters:
app.version: '1.9.19'
app.version: '1.9.21'

View File

@@ -95,16 +95,6 @@
<PieceModelStructureEditor v-model="pieceStructure" />
</div>
<div
v-else
class="space-y-3 rounded-lg border border-base-300 p-4"
>
<p class="text-sm text-base-content/70">
Aperçu :
<span class="font-medium text-base-content">{{ productStructurePreview }}</span>
</p>
<PieceModelStructureEditor v-model="productStructure" />
</div>
</template>
</section>
@@ -194,15 +184,16 @@ const form = reactive<ModelTypePayload & { referenceFormula?: string | null }>({
})
const formulaBuilderCustomFields = computed(() => {
let fields: any[] = []
if (form.category === 'PIECE') {
const fields = pieceStructure.value?.customFields
return Array.isArray(fields) ? fields : []
const raw = pieceStructure.value?.customFields
fields = Array.isArray(raw) ? raw : []
}
if (form.category === 'COMPONENT') {
const fields = componentStructure.value?.customFields
return Array.isArray(fields) ? fields : []
else if (form.category === 'COMPONENT') {
const raw = componentStructure.value?.customFields
fields = Array.isArray(raw) ? raw : []
}
return []
return fields.filter((f: any) => !f.machineContextOnly)
})
const extractFormulaFields = (formula: string | null | undefined): string[] => {

View File

@@ -34,7 +34,6 @@ import {
import {
hasAssignments,
initializeStructureAssignments,
isAssignmentNodeComplete,
serializeStructureAssignments,
} from '~/shared/utils/structureAssignmentHelpers'
import type { ComponentModelStructure } from '~/shared/types/inventory'
@@ -152,24 +151,14 @@ export function useComponentCreate() {
values: computed(() => []),
entityType: 'composant',
entityId: createdComponentId,
context: 'standalone',
})
const structureHasRequirements = computed(() =>
hasAssignments(structureAssignments.value),
)
const structureSelectionsComplete = computed(() => {
if (!structureHasRequirements.value) {
return true
}
if (structureDataLoading.value) {
return false
}
if (!structureAssignments.value) {
return false
}
return isAssignmentNodeComplete(structureAssignments.value, true)
})
const structureSelectionsComplete = computed(() => true)
const canSubmit = computed(() => Boolean(
canEdit.value
@@ -307,11 +296,6 @@ export function useComponentCreate() {
payload.productId = rootProductSelection.selectedProductId.trim()
}
if (structureHasRequirements.value && !structureSelectionsComplete.value) {
toast.showError('Complétez la sélection des pièces, produits et sous-composants.')
return
}
const serializedStructure = structureHasRequirements.value
? serializeStructureAssignments(structureAssignments.value)
: null
@@ -414,6 +398,7 @@ export function useComponentCreate() {
structureSelectionsComplete,
canEdit,
canSubmit,
requiredCustomFieldsFilled,
// Functions
typeOptionLabel,

View File

@@ -209,6 +209,7 @@ export function useComponentEdit(componentId: string) {
values: computed(() => component.value?.customFieldValues ?? []),
entityType: 'composant',
entityId: computed(() => component.value?.id ?? null),
context: 'standalone',
onValueCreated: (newValue) => {
if (component.value && Array.isArray(component.value.customFieldValues)) {
component.value.customFieldValues.push(newValue)
@@ -556,6 +557,7 @@ export function useComponentEdit(componentId: string) {
originalConstructeurLinks,
constructeurIdsFromForm,
customFieldInputs,
requiredCustomFieldsFilled,
historyFieldLabels,
// Computed

View File

@@ -99,6 +99,7 @@ export function usePieceEdit(pieceId: string) {
values: computed(() => piece.value?.customFieldValues ?? []),
entityType: 'piece',
entityId: computed(() => piece.value?.id ?? null),
context: 'standalone',
onValueCreated: (newValue) => {
if (piece.value && Array.isArray(piece.value.customFieldValues)) {
piece.value.customFieldValues.push(newValue)
@@ -435,6 +436,7 @@ export function usePieceEdit(pieceId: string) {
constructeurIdsFromForm,
productSelections,
customFieldInputs,
requiredCustomFieldsFilled,
canEdit,
// Computed

View File

@@ -218,6 +218,9 @@
</p>
</header>
<CustomFieldInputGrid :fields="customFieldInputs" :disabled="!canEdit || submitting" />
<p v-if="hasRequiredCustomFields && !requiredCustomFieldsFilled" class="text-xs text-warning">
Certains champs personnalisés sont obligatoires. Veuillez les renseigner avant de valider.
</p>
</div>
<EmptyState
v-else
@@ -237,6 +240,9 @@
Créer la pièce
</button>
</div>
<p v-if="selectedType && hasRequiredCustomFields && !requiredCustomFieldsFilled" class="text-xs text-error text-right">
Merci de renseigner tous les champs personnalisés obligatoires avant de créer la pièce.
</p>
</div>
</section>
</main>
@@ -311,7 +317,9 @@ const { fields: customFieldInputs, requiredFilled: requiredCustomFieldsFilled, s
values: [] as any[],
entityType: 'piece' as CustomFieldEntityType,
entityId: createdEntityId,
context: 'standalone',
})
const hasRequiredCustomFields = computed(() => customFieldInputs.value.some(f => f.required))
const selectedDocuments = ref<File[]>([])
const uploadingDocuments = ref(false)

View File

@@ -274,6 +274,9 @@
</header>
<template v-if="isEditMode">
<CustomFieldInputGrid :fields="customFieldInputs" :disabled="!canEdit || saving" />
<p v-if="hasRequiredCustomFields && !requiredCustomFieldsFilled" class="text-xs text-warning">
Certains champs personnalisés sont obligatoires. Veuillez les renseigner avant de valider.
</p>
</template>
<template v-else>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
@@ -338,7 +341,7 @@
Enregistrer les modifications
</button>
</div>
<p v-if="isEditMode && product && !requiredCustomFieldsFilled" class="text-xs text-error text-right">
<p v-if="isEditMode && hasRequiredCustomFields && !requiredCustomFieldsFilled" class="text-xs text-error text-right">
Merci de renseigner tous les champs personnalisés obligatoires.
</p>
</div>
@@ -409,6 +412,7 @@ const {
values: cfValues,
entityType: 'product' as CustomFieldEntityType,
entityId,
context: 'standalone',
})
const loading = ref(true)
const saving = ref(false)
@@ -446,7 +450,7 @@ const editionForm = reactive({
supplierPrice: '' as string,
})
// requiredCustomFieldsFilled comes from useCustomFieldInputs composable
const hasRequiredCustomFields = computed(() => customFieldInputs.value.some(f => f.required))
const canSubmit = computed(() =>
Boolean(canEdit.value && product.value && editionForm.name.trim().length >= 2 && requiredCustomFieldsFilled.value && !saving.value),

View File

@@ -158,6 +158,9 @@
</p>
</header>
<CustomFieldInputGrid :fields="customFieldInputs" :disabled="!canEdit || submitting" />
<p v-if="hasRequiredCustomFields && !requiredCustomFieldsFilled" class="text-xs text-warning">
Certains champs personnalisés sont obligatoires. Veuillez les renseigner avant de valider.
</p>
</div>
<EmptyState
v-else
@@ -177,7 +180,7 @@
Créer le produit
</button>
</div>
<p v-if="selectedType && !requiredCustomFieldsFilled" class="text-xs text-error text-right">
<p v-if="selectedType && hasRequiredCustomFields && !requiredCustomFieldsFilled" class="text-xs text-error text-right">
Merci de renseigner tous les champs personnalisés obligatoires.
</p>
</div>
@@ -241,7 +244,9 @@ const { fields: customFieldInputs, requiredFilled: requiredCustomFieldsFilled, s
values: [] as any[],
entityType: 'product' as CustomFieldEntityType,
entityId: createdEntityId,
context: 'standalone',
})
const hasRequiredCustomFields = computed(() => customFieldInputs.value.some(f => f.required))
const productTypeList = computed<ProductCatalogType[]>(() =>
(productTypes.value || []) as ProductCatalogType[],