Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d5361ac3ec | |||
| 477295c400 | |||
| 22dddb73bd | |||
| cb49c69662 | |||
| f18ae545d8 | |||
| 3003ced157 |
@@ -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
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '1.9.42'
|
app.version: '1.9.45'
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
|||||||
Reference in New Issue
Block a user