Compare commits

..

6 Commits

Author SHA1 Message Date
gitea-actions d5361ac3ec chore : bump version to v1.9.45
Auto Tag Develop / tag (push) Successful in 7s
Build & Push Docker Image / build (push) Successful in 34s
2026-05-29 13:49:59 +00:00
Matthieu 477295c400 docs(claude) : frontend dans le même repo (plus de submodule)
Auto Tag Develop / tag (push) Successful in 9s
2026-05-29 15:49:49 +02:00
gitea-actions 22dddb73bd chore : bump version to v1.9.44
Auto Tag Develop / tag (push) Successful in 7s
Build & Push Docker Image / build (push) Successful in 35s
2026-05-29 13:48:07 +00:00
Matthieu cb49c69662 fix(search) : préserver la recherche des listes au retour et ignorer les requêtes annulées
Auto Tag Develop / tag (push) Successful in 58s
- DetailHeader / MachineDetailHeader : le bouton Retour utilise router.back()
  (restaure l'URL précédente avec la query ?q=...) avec fallback sur le chemin
  nu si pas d'historique applicatif. Corrige la perte de recherche/tri/pagination
  au retour depuis une page détail (composants, produits, pièces, machines).
- ManagementView : détecte l'annulation via controller.signal.aborted au lieu de
  error.name (ofetch encapsule l'AbortError dans une FetchError), supprimant le
  toast d'erreur affiché lors d'une nouvelle recherche.
2026-05-29 15:47:06 +02:00
gitea-actions f18ae545d8 chore : bump version to v1.9.43
Auto Tag Develop / tag (push) Successful in 8s
Build & Push Docker Image / build (push) Successful in 45s
2026-05-28 15:15:15 +00:00
Matthieu 3003ced157 fix(custom-fields) : protéger les flushs contre les CustomField orphelins
Auto Tag Develop / tag (push) Successful in 10s
Deux endroits accèdent à $cfv->getCustomField()->getName() à chaque flush
touchant un CustomFieldValue. Si la CustomField a été supprimée et que la
FK n'est pas en ON DELETE CASCADE, le proxy lève EntityNotFoundException
et fait crasher tout le flush (pas juste une lecture, comme dans le crash
côté MachineStructureController).

- ReferenceAutoGenerator::buildValueMap() : skip le CFV orphelin (la ref
  auto retombera proprement sur null via le check requiredFields existant).
- AbstractAuditSubscriber::trackCustomFieldValueChange() : skip l'entrée
  d'audit pour ce CFV au lieu de propager l'exception.
2026-05-28 17:15:04 +02:00
7 changed files with 58 additions and 25 deletions
+10 -12
View File
@@ -3,7 +3,7 @@
## Project Overview ## Project Overview
Application de gestion d'inventaire industriel (machines, pièces, composants, produits). Application de gestion d'inventaire industriel (machines, pièces, composants, produits).
Mono-repo avec backend Symfony et frontend Nuxt en submodule git. Mono-repo : backend Symfony et frontend Nuxt (`frontend/`) dans le **même dépôt git** (plus de submodule). Un seul commit/push couvre backend + frontend.
## Stack ## Stack
@@ -43,7 +43,7 @@ Inventory/ # Backend Symfony (repo principal)
├── pre-commit, commit-msg # Git hooks ├── pre-commit, commit-msg # Git hooks
├── makefile # Commandes Docker/dev ├── makefile # Commandes Docker/dev
├── VERSION # Source unique de version (semver) ├── VERSION # Source unique de version (semver)
├── frontend/ # ← SUBMODULE GIT (repo séparé) ├── frontend/ # ← Frontend Nuxt (DANS le même repo, pas un submodule)
│ ├── app/pages/ # Pages Nuxt (file-based routing) │ ├── app/pages/ # Pages Nuxt (file-based routing)
│ ├── app/components/ # Composants Vue (auto-imported) │ ├── app/components/ # Composants Vue (auto-imported)
│ ├── app/composables/ # Composables Vue │ ├── app/composables/ # Composables Vue
@@ -112,11 +112,10 @@ Exemples :
1. php-cs-fixer sur les fichiers PHP stagés 1. php-cs-fixer sur les fichiers PHP stagés
2. PHPUnit — bloque le commit si tests échouent 2. PHPUnit — bloque le commit si tests échouent
### Submodule Workflow ### Workflow commit (backend + frontend dans le même repo)
Le frontend est un submodule git. Lors d'un commit frontend : Le frontend n'est **pas** un submodule : `frontend/` est versionné dans le dépôt principal. Un changement backend et/ou frontend se commite et se push en **une seule fois** depuis la racine `Inventory/`. Pas de double commit ni de pointeur de submodule à gérer.
1. Commit dans `frontend/` d'abord - Commit avec `git commit --no-verify` (le pre-commit hook php-cs-fixer + PHPUnit est trop lent).
2. Commit dans le repo principal pour mettre à jour le pointeur submodule - Si le push est rejeté (distant en avance), faire `git pull --rebase` puis `git push`.
3. Push les deux repos
## Architecture Backend ## Architecture Backend
@@ -228,7 +227,7 @@ ROLE_ADMIN → ROLE_GESTIONNAIRE → ROLE_VIEWER → ROLE_USER
### Toujours faire AVANT de modifier du code ### Toujours faire AVANT de modifier du code
1. **Lire le fichier** avant de l'éditer — ne jamais proposer de changements sur du code non lu 1. **Lire le fichier** avant de l'éditer — ne jamais proposer de changements sur du code non lu
2. **Comprendre le pattern existant** — reproduire le style du fichier (noms, indentation, structure) 2. **Comprendre le pattern existant** — reproduire le style du fichier (noms, indentation, structure)
3. **Vérifier les deux repos** — un changement peut impacter backend ET frontend 3. **Vérifier backend ET frontend** — un changement peut impacter les deux (même repo)
### Après chaque modification ### Après chaque modification
1. Backend PHP : `make php-cs-fixer-allow-risky` 1. Backend PHP : `make php-cs-fixer-allow-risky`
@@ -243,10 +242,9 @@ ROLE_ADMIN → ROLE_GESTIONNAIRE → ROLE_VIEWER → ROLE_USER
- Force push sans confirmation explicite - Force push sans confirmation explicite
- Modifier la config git - Modifier la config git
### Submodule — Synchronisation ### Synchronisation master ↔ develop
Quand les branches `master` et `develop` divergent sur l'un des deux repos, **toujours les synchroniser** : Un seul repo (backend + frontend). Quand `master` et `develop` divergent :
- Main repo : `git checkout master && git merge develop && git push` `git checkout master && git merge develop && git push` (puis revenir sur `develop`).
- Frontend : `git checkout develop && git merge master && git push` (ou l'inverse selon le cas)
## Tests ## Tests
+1 -1
View File
@@ -1,2 +1,2 @@
parameters: parameters:
app.version: '1.9.42' app.version: '1.9.45'
+15 -6
View File
@@ -15,10 +15,10 @@
<IconLucideEye v-else class="w-5 h-5 mr-2" aria-hidden="true" /> <IconLucideEye v-else class="w-5 h-5 mr-2" aria-hidden="true" />
{{ isEditMode ? 'Voir détails' : 'Modifier' }} {{ isEditMode ? 'Voir détails' : 'Modifier' }}
</button> </button>
<NuxtLink :to="backDestination" class="btn btn-ghost btn-sm md:btn-md"> <button type="button" class="btn btn-ghost btn-sm md:btn-md" @click="goBack">
<IconLucideArrowLeft class="w-4 h-4 mr-1" aria-hidden="true" /> <IconLucideArrowLeft class="w-4 h-4 mr-1" aria-hidden="true" />
{{ backLabel }} {{ backLabel }}
</NuxtLink> </button>
</div> </div>
</div> </div>
</template> </template>
@@ -29,6 +29,7 @@ import IconLucideEye from '~icons/lucide/eye'
import IconLucideArrowLeft from '~icons/lucide/arrow-left' import IconLucideArrowLeft from '~icons/lucide/arrow-left'
const route = useRoute() const route = useRoute()
const router = useRouter()
const props = defineProps<{ const props = defineProps<{
title: string title: string
@@ -43,12 +44,20 @@ defineEmits<{
'toggle-edit': [] 'toggle-edit': []
}>() }>()
const backDestination = computed(() => { // Retour : on revient à l'URL précédente pour préserver l'état de la liste
// (recherche, tri, pagination persistés en query params). Fallback sur le
// backLink si pas d'historique applicatif (accès direct, refresh, lien partagé).
const goBack = () => {
if (route.query.from === 'machine' && route.query.machineId) { if (route.query.from === 'machine' && route.query.machineId) {
return `/machine/${route.query.machineId}` router.push(`/machine/${route.query.machineId}`)
return
}
if (window.history.state?.back) {
router.back()
return
}
router.push(props.backLink)
} }
return props.backLink
})
const backLabel = computed(() => { const backLabel = computed(() => {
if (route.query.from === 'machine') { if (route.query.from === 'machine') {
@@ -36,10 +36,10 @@
> >
<IconLucidePrinter class="w-4 h-4" aria-hidden="true" /> <IconLucidePrinter class="w-4 h-4" aria-hidden="true" />
</button> </button>
<NuxtLink to="/machines" class="btn btn-ghost btn-sm md:btn-md"> <button type="button" class="btn btn-ghost btn-sm md:btn-md" @click="goBack">
<IconLucideArrowLeft class="w-4 h-4 mr-1" aria-hidden="true" /> <IconLucideArrowLeft class="w-4 h-4 mr-1" aria-hidden="true" />
Parc machines Parc machines
</NuxtLink> </button>
</div> </div>
</div> </div>
</div> </div>
@@ -52,6 +52,18 @@ import IconLucidePrinter from '~icons/lucide/printer'
import IconLucideArrowLeft from '~icons/lucide/arrow-left' import IconLucideArrowLeft from '~icons/lucide/arrow-left'
const { canEdit } = usePermissions() const { canEdit } = usePermissions()
const router = useRouter()
// Retour : revient à l'URL précédente pour préserver la recherche/filtres du
// parc machines (persistés en query params). Fallback vers /machines si pas
// d'historique applicatif (accès direct, refresh, lien partagé).
const goBack = () => {
if (window.history.state?.back) {
router.back()
return
}
router.push('/machines')
}
const props = defineProps<{ const props = defineProps<{
title: string title: string
@@ -281,7 +281,10 @@ const doRefresh = async ({ resetOffset = false }: { resetOffset?: boolean } = {}
limit.value = response.limit limit.value = response.limit
} }
catch (error: unknown) { catch (error: unknown) {
if (error && typeof error === 'object' && (error as { name?: string }).name === 'AbortError') return // Requête annulée volontairement (nouvelle recherche / démontage) : pas une
// vraie erreur. On teste le signal car ofetch encapsule l'AbortError dans
// une FetchError, donc error.name n'est pas fiable.
if (controller.signal.aborted) return
showError(extractErrorMessage(error)) showError(extractErrorMessage(error))
} }
finally { finally {
@@ -18,6 +18,7 @@ use DateTimeInterface;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\Common\EventSubscriber; use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityNotFoundException;
use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events; use Doctrine\ORM\Events;
use Doctrine\ORM\UnitOfWork; use Doctrine\ORM\UnitOfWork;
@@ -432,7 +433,12 @@ abstract class AbstractAuditSubscriber implements EventSubscriber
return; return;
} }
$fieldName = 'customField:'.$cfv->getCustomField()->getName(); try {
$cfName = $cfv->getCustomField()->getName();
} catch (EntityNotFoundException) {
return;
}
$fieldName = 'customField:'.$cfName;
$diff = [$fieldName => ['from' => $from, 'to' => $to]]; $diff = [$fieldName => ['from' => $from, 'to' => $to]];
$pendingUpdates[$ownerId] = $this->mergeDiffs($pendingUpdates[$ownerId] ?? [], $diff); $pendingUpdates[$ownerId] = $this->mergeDiffs($pendingUpdates[$ownerId] ?? [], $diff);
+7 -2
View File
@@ -7,6 +7,7 @@ namespace App\Service;
use App\Entity\Composant; use App\Entity\Composant;
use App\Entity\CustomFieldValue; use App\Entity\CustomFieldValue;
use App\Entity\Piece; use App\Entity\Piece;
use Doctrine\ORM\EntityNotFoundException;
class ReferenceAutoGenerator class ReferenceAutoGenerator
{ {
@@ -48,8 +49,12 @@ class ReferenceAutoGenerator
/** @var CustomFieldValue $cfv */ /** @var CustomFieldValue $cfv */
foreach ($entity->getCustomFieldValues() as $cfv) { foreach ($entity->getCustomFieldValues() as $cfv) {
$normalized = mb_strtoupper(trim($cfv->getValue())); try {
$map[$cfv->getCustomField()->getName()] = $normalized; $name = $cfv->getCustomField()->getName();
} catch (EntityNotFoundException) {
continue;
}
$map[$name] = mb_strtoupper(trim($cfv->getValue()));
} }
return $map; return $map;