feat(api) : add machine count to category related items endpoint
This commit is contained in:
@@ -48,6 +48,39 @@
|
|||||||
<span class="whitespace-nowrap">{{ formatDate(row.createdAt) }}</span>
|
<span class="whitespace-nowrap">{{ formatDate(row.createdAt) }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #cell-composantCount="{ row }">
|
||||||
|
<NuxtLink
|
||||||
|
v-if="stats[row.id]?.composantCount"
|
||||||
|
:to="`/catalogues/composants?constructeur=${row.id}`"
|
||||||
|
class="badge badge-ghost badge-sm hover:badge-primary transition-colors"
|
||||||
|
>
|
||||||
|
{{ stats[row.id].composantCount }}
|
||||||
|
</NuxtLink>
|
||||||
|
<span v-else class="text-base-content/30">—</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell-pieceCount="{ row }">
|
||||||
|
<NuxtLink
|
||||||
|
v-if="stats[row.id]?.pieceCount"
|
||||||
|
:to="`/catalogues/pieces?constructeur=${row.id}`"
|
||||||
|
class="badge badge-ghost badge-sm hover:badge-primary transition-colors"
|
||||||
|
>
|
||||||
|
{{ stats[row.id].pieceCount }}
|
||||||
|
</NuxtLink>
|
||||||
|
<span v-else class="text-base-content/30">—</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell-machineCount="{ row }">
|
||||||
|
<NuxtLink
|
||||||
|
v-if="stats[row.id]?.machineCount"
|
||||||
|
:to="`/machines?constructeur=${row.id}`"
|
||||||
|
class="badge badge-ghost badge-sm hover:badge-primary transition-colors"
|
||||||
|
>
|
||||||
|
{{ stats[row.id].machineCount }}
|
||||||
|
</NuxtLink>
|
||||||
|
<span v-else class="text-base-content/30">—</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #cell-actions="{ row }">
|
<template #cell-actions="{ row }">
|
||||||
<div class="flex items-center justify-end gap-2">
|
<div class="flex items-center justify-end gap-2">
|
||||||
<button class="btn btn-ghost btn-xs" @click="openEditModal(row)">
|
<button class="btn btn-ghost btn-xs" @click="openEditModal(row)">
|
||||||
@@ -103,6 +136,7 @@ import { formatPhone } from '~/utils/formatters/phone'
|
|||||||
import { formatFrenchDate } from '~/utils/date'
|
import { formatFrenchDate } from '~/utils/date'
|
||||||
import IconLucidePlus from '~icons/lucide/plus'
|
import IconLucidePlus from '~icons/lucide/plus'
|
||||||
|
|
||||||
|
const api = useApi()
|
||||||
const { canEdit } = usePermissions()
|
const { canEdit } = usePermissions()
|
||||||
const { constructeurs, loading, searchConstructeurs, createConstructeur, updateConstructeur, deleteConstructeur, loadConstructeurs } = useConstructeurs()
|
const { constructeurs, loading, searchConstructeurs, createConstructeur, updateConstructeur, deleteConstructeur, loadConstructeurs } = useConstructeurs()
|
||||||
const { showError } = useToast()
|
const { showError } = useToast()
|
||||||
@@ -112,12 +146,16 @@ const columns = [
|
|||||||
{ key: 'email', label: 'Email', sortable: true },
|
{ key: 'email', label: 'Email', sortable: true },
|
||||||
{ key: 'phone', label: 'Téléphone', sortable: true },
|
{ key: 'phone', label: 'Téléphone', sortable: true },
|
||||||
{ key: 'createdAt', label: 'Date de création', sortable: true },
|
{ key: 'createdAt', label: 'Date de création', sortable: true },
|
||||||
|
{ key: 'composantCount', label: 'Composants', align: 'center' },
|
||||||
|
{ key: 'pieceCount', label: 'Pièces', align: 'center' },
|
||||||
|
{ key: 'machineCount', label: 'Machines', align: 'center' },
|
||||||
{ key: 'actions', label: 'Actions', align: 'right' },
|
{ key: 'actions', label: 'Actions', align: 'right' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const sortKey = usePersistedValue('constructeurs-sort', 'name')
|
const sortKey = usePersistedValue('constructeurs-sort', 'name')
|
||||||
const sortDir = ref('asc')
|
const sortDir = ref('asc')
|
||||||
|
const stats = ref({})
|
||||||
|
|
||||||
const currentSort = computed(() => ({
|
const currentSort = computed(() => ({
|
||||||
field: sortKey.value,
|
field: sortKey.value,
|
||||||
@@ -236,5 +274,15 @@ const confirmDelete = async (constructeur) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => loadConstructeurs())
|
const loadStats = async () => {
|
||||||
|
const result = await api.get('/constructeurs/stats')
|
||||||
|
if (result.success && result.data) {
|
||||||
|
stats.value = result.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadConstructeurs()
|
||||||
|
loadStats()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
142
src/Controller/ModelTypeRelatedItemsController.php
Normal file
142
src/Controller/ModelTypeRelatedItemsController.php
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Composant;
|
||||||
|
use App\Entity\MachineComponentLink;
|
||||||
|
use App\Entity\MachinePieceLink;
|
||||||
|
use App\Entity\MachineProductLink;
|
||||||
|
use App\Entity\Piece;
|
||||||
|
use App\Entity\Product;
|
||||||
|
use App\Enum\ModelCategory;
|
||||||
|
use App\Repository\ModelTypeRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
#[Route('/api/model_types/{id}')]
|
||||||
|
final class ModelTypeRelatedItemsController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ModelTypeRepository $modelTypes,
|
||||||
|
private readonly EntityManagerInterface $em,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/related-items', name: 'api_model_type_related_items', methods: ['GET'])]
|
||||||
|
public function relatedItems(string $id): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
|
$modelType = $this->modelTypes->find($id);
|
||||||
|
|
||||||
|
if (!$modelType) {
|
||||||
|
return new JsonResponse(
|
||||||
|
['message' => 'Catégorie introuvable.'],
|
||||||
|
Response::HTTP_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = match ($modelType->getCategory()) {
|
||||||
|
ModelCategory::COMPONENT => $this->fetchComposantsWithMachineCount($id),
|
||||||
|
ModelCategory::PIECE => $this->fetchPiecesWithMachineCount($id),
|
||||||
|
ModelCategory::PRODUCT => $this->fetchProductsWithMachineCount($id),
|
||||||
|
};
|
||||||
|
|
||||||
|
return new JsonResponse($items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<array{id: string, name: string, reference: null|string, machineCount: int}>
|
||||||
|
*/
|
||||||
|
private function fetchComposantsWithMachineCount(string $modelTypeId): array
|
||||||
|
{
|
||||||
|
$qb = $this->em->createQueryBuilder();
|
||||||
|
$qb->select(
|
||||||
|
'c.id',
|
||||||
|
'c.name',
|
||||||
|
'c.reference',
|
||||||
|
'COALESCE(c.referenceAuto, c.reference) as displayReference',
|
||||||
|
'COUNT(DISTINCT mcl.id) as machineCount',
|
||||||
|
)
|
||||||
|
->from(Composant::class, 'c')
|
||||||
|
->leftJoin(MachineComponentLink::class, 'mcl', 'WITH', 'mcl.composant = c')
|
||||||
|
->where('c.typeComposant = :modelTypeId')
|
||||||
|
->setParameter('modelTypeId', $modelTypeId)
|
||||||
|
->groupBy('c.id', 'c.name', 'c.reference', 'c.referenceAuto')
|
||||||
|
->orderBy('c.name', 'ASC')
|
||||||
|
;
|
||||||
|
|
||||||
|
return $this->formatResults($qb->getQuery()->getArrayResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<array{id: string, name: string, reference: null|string, machineCount: int}>
|
||||||
|
*/
|
||||||
|
private function fetchPiecesWithMachineCount(string $modelTypeId): array
|
||||||
|
{
|
||||||
|
$qb = $this->em->createQueryBuilder();
|
||||||
|
$qb->select(
|
||||||
|
'p.id',
|
||||||
|
'p.name',
|
||||||
|
'p.reference',
|
||||||
|
'COALESCE(p.referenceAuto, p.reference) as displayReference',
|
||||||
|
'COUNT(DISTINCT mpl.id) as machineCount',
|
||||||
|
)
|
||||||
|
->from(Piece::class, 'p')
|
||||||
|
->leftJoin(MachinePieceLink::class, 'mpl', 'WITH', 'mpl.piece = p')
|
||||||
|
->where('p.typePiece = :modelTypeId')
|
||||||
|
->setParameter('modelTypeId', $modelTypeId)
|
||||||
|
->groupBy('p.id', 'p.name', 'p.reference', 'p.referenceAuto')
|
||||||
|
->orderBy('p.name', 'ASC')
|
||||||
|
;
|
||||||
|
|
||||||
|
return $this->formatResults($qb->getQuery()->getArrayResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<array{id: string, name: string, reference: null|string, machineCount: int}>
|
||||||
|
*/
|
||||||
|
private function fetchProductsWithMachineCount(string $modelTypeId): array
|
||||||
|
{
|
||||||
|
$qb = $this->em->createQueryBuilder();
|
||||||
|
$qb->select(
|
||||||
|
'pr.id',
|
||||||
|
'pr.name',
|
||||||
|
'pr.reference',
|
||||||
|
'COUNT(DISTINCT mpl.id) as machineCount',
|
||||||
|
)
|
||||||
|
->from(Product::class, 'pr')
|
||||||
|
->leftJoin(MachineProductLink::class, 'mpl', 'WITH', 'mpl.product = pr')
|
||||||
|
->where('pr.typeProduct = :modelTypeId')
|
||||||
|
->setParameter('modelTypeId', $modelTypeId)
|
||||||
|
->groupBy('pr.id', 'pr.name', 'pr.reference')
|
||||||
|
->orderBy('pr.name', 'ASC')
|
||||||
|
;
|
||||||
|
|
||||||
|
return $this->formatResults($qb->getQuery()->getArrayResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array<string, mixed>> $rows
|
||||||
|
*
|
||||||
|
* @return list<array{id: string, name: string, reference: null|string, machineCount: int}>
|
||||||
|
*/
|
||||||
|
private function formatResults(array $rows): array
|
||||||
|
{
|
||||||
|
return array_values(array_map(
|
||||||
|
static fn (array $row): array => [
|
||||||
|
'id' => (string) $row['id'],
|
||||||
|
'name' => (string) ($row['name'] ?? ''),
|
||||||
|
'reference' => isset($row['displayReference']) && '' !== $row['displayReference']
|
||||||
|
? (string) $row['displayReference']
|
||||||
|
: (isset($row['reference']) && '' !== $row['reference'] ? (string) $row['reference'] : null),
|
||||||
|
'machineCount' => (int) $row['machineCount'],
|
||||||
|
],
|
||||||
|
$rows,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user