<?php
namespace App\Repository;
use App\Entity\ProduitDeclinationValue;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use App\Entity\ValueDeclination;
/**
* @method ProduitDeclinationValue|null find($id, $lockMode = null, $lockVersion = null)
* @method ProduitDeclinationValue|null findOneBy(array $criteria, array $orderBy = null)
* @method ProduitDeclinationValue[] findAll()
* @method ProduitDeclinationValue[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ProduitDeclinationValueRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ProduitDeclinationValue::class);
}
public function findProduitGroup(
$page,
$limit,
int $idProduit,
?string $reference,
array $declinations = [], // [declinationId => valueId|valueId[]]
bool $withDeleted = false
) {
$qb = $this->createQueryBuilder('p')
->leftJoin('p.produit', 'pp')
->andWhere('pp.id = :produit')->setParameter('produit', $idProduit);
if ($reference) {
$qb->andWhere('UPPER(p.reference) LIKE :reference')
->setParameter('reference', '%'.mb_strtoupper($reference).'%');
}
// AND entre chaque declination
$i = 0;
foreach ($declinations as $declId => $val) {
if ($val === null || $val === '' || $val === [] ) { continue; }
$i++;
$alias = 'gdv'.$i;
$paramDecl = 'd'.$i;
$paramVal = 'v'.$i;
$vals = is_array($val) ? array_values(array_unique($val)) : [ (int)$val ];
// innerJoin = critĂšre obligatoire
$qb->innerJoin('p.groupDeclinationValues', $alias, 'WITH',
$alias.'.declination = :'.$paramDecl.' AND '.$alias.'.value IN (:'.$paramVal.')'
)
->setParameter($paramDecl, (int)$declId)
->setParameter($paramVal, $vals);
}
if (!$withDeleted) {
$qb->andWhere('pp.deletedAt IS NULL');
}
$qb->orderBy('p.reference', 'ASC');
if ($page !== false) {
$qb->setMaxResults($limit)
->setFirstResult($page * $limit);
}
return $qb->getQuery()->getResult();
}
public function countProduitGroup(
int $idProduit,
?string $reference,
array $declinations = [], // [declinationId => valueId|valueId[]]
bool $withDeleted = false
) {
$qb = $this->createQueryBuilder('p')
->select('COUNT(p)')
->leftJoin('p.produit', 'pp')
->andWhere('pp.id = :produit')->setParameter('produit', $idProduit);
if ($reference) {
$qb->andWhere('UPPER(p.reference) LIKE :reference')
->setParameter('reference', '%'.mb_strtoupper($reference).'%');
}
$i = 0;
foreach ($declinations as $declId => $val) {
if ($val === null || $val === '' || $val === [] ) { continue; }
$i++;
$alias = 'gdv'.$i;
$paramDecl = 'd'.$i;
$paramVal = 'v'.$i;
$vals = is_array($val) ? array_values(array_unique($val)) : [ (int)$val ];
$qb->innerJoin('p.groupDeclinationValues', $alias, 'WITH',
$alias.'.declination = :'.$paramDecl.' AND '.$alias.'.value IN (:'.$paramVal.')'
)
->setParameter($paramDecl, (int)$declId)
->setParameter($paramVal, $vals);
}
if (!$withDeleted) {
$qb->andWhere('pp.deletedAt IS NULL');
}
return (int)$qb->getQuery()->getSingleScalarResult();
}
public function searchItem($query,$isAvailable=false,$maxResult=5,$withDeleted=false) {
$qb = $this->createQueryBuilder('p');
$qb->select('p.id, p.name, p.reference, p.description, p.price_ht, p.buyingPriceTtc')
->innerJoin("p.produit", "pp")
->addSelect("pp.unit As unit")
->leftJoin("pp.tva", "pptva")
->addSelect("pptva.id As tva_id")
->addSelect("pptva.number As tva")
->where('UPPER(p.name) LIKE :name')->setParameter('name', '%' . strtoupper($query) . '%')
->orWhere('UPPER(p.reference) LIKE :reference')->setParameter('reference', '%' . strtoupper($query) . '%')
->andWhere('pp.deletedAt IS NULL');
$qb->leftJoin('p.stocks','s');
if($isAvailable) $qb->andWhere('(s.qtStock - s.qtReserved) >= 1');
$qb->orderBy('p.createdAt', 'DESC');
$qb->setMaxResults($maxResult);
return $qb->getQuery()->getResult();
}
public function searchAndCountProduitDeclinations(
int $page,
int $limit,
$reference,
$name,
$categories,
array $declinationFilters = [],
$isAvailable,
$inPromo,
$qtMin = null,
$qtMax = null,
$buyingPriceMin = null,
$buyingPriceMax = null,
$priceMin = null,
$priceMax = null,
string $sortField = 'createdAt',
string $sortType = 'DESC',
bool $withDeleted = false
): array {
/* ===============================
1) QueryBuilder de base
=============================== */
$qb = $this->createQueryBuilder('p');
$this->QueryBuilderSearch(
$qb,
$reference,
$name,
$categories,
$declinationFilters,
$isAvailable,
$inPromo,
$qtMin,
$qtMax,
$buyingPriceMin,
$buyingPriceMax,
$priceMin,
$priceMax,
$withDeleted
);
/* ===============================
2) COUNT (clone)
=============================== */
$qbCount = clone $qb;
$total = (int) $qbCount
->select('COUNT(DISTINCT p.id)')
->resetDQLPart('orderBy')
->getQuery()
->getSingleScalarResult();
/* ===============================
3) RESULTS (clone)
=============================== */
$qbResult = clone $qb;
$qbResult
->orderBy(($sortField === 'qtStock' ? '' : 'p.') . $sortField, $sortType)
->setFirstResult($page * $limit)
->setMaxResults($limit);
$data = $qbResult->getQuery()->getResult();
return [
'data' => $data,
'total' => $total,
];
}
public function searchProduitDeclinations(
$page,
$limit,
$reference,
$name,
$categories,
array $declinationFilters = [],
$isAvailable,
$inPromo,
$qtMin = null,
$qtMax = null,
$buyingPriceMin = null,
$buyingPriceMax = null,
$priceMin = null,
$priceMax = null,
$sortField = 'createdAt',
$sortType = 'DESC',
$withDeleted = false
) {
$qb = $this->createQueryBuilder('p');
$qb = $this->QueryBuilderSearch(
$qb,
$reference,
$name,
$categories,
$declinationFilters,
$isAvailable,
$inPromo,
$qtMin,
$qtMax,
$buyingPriceMin,
$buyingPriceMax,
$priceMin,
$priceMax,
$withDeleted
);
$qb->orderBy(($sortField === 'qtStock' ? '' : 'p.') . $sortField, $sortType);
if ($page !== false) $qb->setMaxResults($limit)->setFirstResult($page * $limit);
return $qb->getQuery()->getResult();
}
public function countProduitDeclinations(
$reference,
$name,
$categories,
array $declinationFilters = [],
$isAvailable,
$inPromo,
$qtMin = null,
$qtMax = null,
$buyingPriceMin = null,
$buyingPriceMax = null,
$priceMin = null,
$priceMax = null,
$withDeleted = false
) {
$qb = $this->createQueryBuilder('p');
$qb->select('COUNT(p)');
$qb = $this->QueryBuilderSearch($qb, $reference, $name, $categories, $declinationFilters,$isAvailable, $inPromo, $qtMin, $qtMax, $buyingPriceMin, $buyingPriceMax, $priceMin, $priceMax, $withDeleted );
return $qb->getQuery()->getSingleScalarResult();
}
/*--Filtre liste declinaisons--*/
private function QueryBuilderSearch($qb,$reference, $name, $categories, array $declinationFilters = [], $isAvailable, $inPromo, $qtMin = null, $qtMax = null,$buyingPriceMin = null, $buyingPriceMax = null, $priceMin = null, $priceMax = null, $withDeleted = false) {
if ($name) {
$qb->andWhere('UPPER(p.name) LIKE :name')
->setParameter('name', '%' . strtoupper($name) . '%');
}
if ($reference) {
$qb->andWhere('UPPER(p.reference) LIKE :reference')
->setParameter('reference', strtoupper($reference) . '%');
}
// đ Filtres dynamiques sur les dĂ©clinaisons
foreach ($declinationFilters as $declinationId => $valueId) {
if (empty($valueId)) {
continue;
}
$alias = 'gdv_' . (int)$declinationId;
$qb
->innerJoin('p.groupDeclinationValues', $alias)
->andWhere($alias . '.declination = :decl_' . $declinationId)
->andWhere($alias . '.value = :val_' . $declinationId)
->setParameter('decl_' . $declinationId, (int)$declinationId)
->setParameter('val_' . $declinationId, (int)$valueId);
}
// đ Stock
$qb->leftJoin('p.stocks', 's');
if ($isAvailable !== "") {
if ($isAvailable) {
$qb->andWhere('(s.qtStock - s.qtReserved) >= 1');
} else {
$qb->andWhere('(s.qtStock - s.qtReserved) <= 0');
}
}
// đ Produit
$qb->leftJoin('p.produit', 'pp');
if ($categories) {
$qb->leftJoin('pp.categories', 'ppc')
->andWhere('ppc.id = :categories')
->setParameter('categories', $categories);
}
if ($inPromo !== "") {
if ($inPromo == '1') {
$qb->join('pp.promotion', 'promo')
->andWhere(':now >= promo.startAt')
->andWhere(':now <= promo.endAt')
->setParameter('now', new \DateTime('now'));
} elseif ($inPromo == '0') {
$qb->andWhere('pp.promotion IS NULL');
}
}
// đ Prix
if ($buyingPriceMin) $qb->andWhere('pp.buyingPriceTtc >= :buyingPriceMin')->setParameter('buyingPriceMin', $buyingPriceMin);
if ($buyingPriceMax) $qb->andWhere('pp.buyingPriceTtc <= :buyingPriceMax')->setParameter('buyingPriceMax', $buyingPriceMax);
if ($priceMin) $qb->andWhere('pp.price_ttc >= :priceMin')->setParameter('priceMin', $priceMin);
if ($priceMax) $qb->andWhere('pp.price_ttc <= :priceMax')->setParameter('priceMax', $priceMax);
// đ QuantitĂ©
if ($qtMin) $qb->andWhere('s.qtStock >= :qtMin')->setParameter('qtMin', $qtMin);
if ($qtMax) $qb->andWhere('s.qtStock <= :qtMax')->setParameter('qtMax', $qtMax);
// đ Suppression
$qb->andWhere('pp.deletedAt IS ' . ($withDeleted ? 'NOT' : '') . ' NULL');
return $qb;
}
public function findDeclinationValueWithDeclination($idDeclination, $idProduit) {
$qb = $this->createQueryBuilder('p');
$qb->leftJoin('p.produit', 'pp')->andWhere('pp.deletedAt IS NULL')
->andWhere('pp.id = :produit')->setParameter('produit', $idProduit);
$qb->leftJoin('p.groupDeclinationValues', 'pgc')
->leftJoin('pgc.declination', 'pgcd')
->leftJoin('pgc.value', 'pgcv')
->andWhere('pgcv.id = :idDeclination')->setParameter('idDeclination', $idDeclination );
$qb->orderBy('p.reference', 'ASC');
return $qb->getQuery()->getResult();
}
public function qtyAvailable($prdDec)
{
$qb = $this->createQueryBuilder('p');
$qb->leftJoin('p.stocks','s')
->Select('(s.qtStock - s.qtReserved) as qtAvailable')
->where('p.id = :id')
->setParameter('id', $prdDec->getId());
dd($qb->getQuery()->getSingleScalarResult());
}
public function qtyReserved($prdDec)
{
$qb = $this->createQueryBuilder('p');
$qb->leftJoin('p.stocks','s')
->Select('s.qtReserved as qtReserved')
->where('p.id = :id')
->setParameter('id', $prdDec->getId());
dd($qb->getQuery()->getSingleScalarResult());
}
//fonction et requĂȘte pour dĂ©clinaion 1 (Exemple : Couleur)
public function getStatsByDeclinationPosition($idProduit, ?string $dateBefore, ?string $dateAfter): array
{
$conn = $this->getEntityManager()->getConnection();
$sql = "
SELECT
dcl.name AS declinaison_label,
v.name AS valeur,
(
SELECT f.image_name
FROM produit_declination_value_file pdf
JOIN file f ON f.id = pdf.file_id
WHERE pdf.produit_declination_value_id = pdv.id
LIMIT 1
) AS decli_image,
SUM(ddp.quantity) AS qtTotal,
SUM(CASE WHEN d.status NOT IN ('annule', 'retourne', 'retour-en-cours') THEN ddp.quantity ELSE 0 END) AS qtVendu,
SUM(CASE WHEN d.status = 'annule' THEN ddp.quantity ELSE 0 END) AS qtAnnulee,
SUM(CASE WHEN d.status IN ('retourne', 'retour-en-cours') THEN ddp.quantity ELSE 0 END) AS qtRetour,
SUM(ddp.total_amount_ttc) AS montantTotal
FROM document_declination_produit ddp
JOIN produit_declination_value pdv ON ddp.produit_declination_value_id = pdv.id
JOIN group_declination_value gdv ON gdv.produit_declination_id = pdv.id
JOIN value_declination v ON v.id = gdv.value_id
JOIN declination dcl ON dcl.id = v.declination_id AND dcl.position = 1
JOIN document d ON d.id = ddp.document_id
WHERE pdv.produit_id = :idProduit
AND d.type = 'commande'
AND d.category = 'client'
";
$params = ['idProduit' => $idProduit];
if ($dateBefore && $dateAfter) {
$sql .= " AND d.created_at BETWEEN :dateBefore AND :dateAfter";
$params['dateBefore'] = $dateBefore;
$params['dateAfter'] = $dateAfter;
} elseif ($dateBefore) {
$sql .= " AND d.created_at >= :dateBefore";
$params['dateBefore'] = $dateBefore;
} elseif ($dateAfter) {
$sql .= " AND d.created_at <= :dateAfter";
$params['dateAfter'] = $dateAfter;
}
$sql .= "
GROUP BY dcl.name, v.name
ORDER BY qtTotal DESC
";
$stmt = $conn->prepare($sql);
$result = $stmt->executeQuery($params);
return $result->fetchAllAssociative();
}
//fonction et requĂȘte pour toutes les dĂ©clinaisons
public function getAllDeclinationsStats($idProduit, ?string $dateBefore, ?string $dateAfter): array
{
$conn = $this->getEntityManager()->getConnection();
$sql = "
SELECT
dcl1.name AS declinaison1_label,
v1.name AS declinaison1_valeur,
pdv.id AS decli_id,
-- Déclinaison 2 (ex : Taille)
dcl2.name AS declinaison2_label,
v2.name AS declinaison2_valeur,
-- Totaux
SUM(ddp.quantity) AS qtTotal,
SUM(
CASE
WHEN d.status NOT IN ('annule', 'retourne', 'retour-en-cours')
THEN ddp.quantity
ELSE 0
END
) AS qtVendu,
SUM(
CASE
WHEN d.status = 'annule'
THEN ddp.quantity
ELSE 0
END
) AS qtAnnulee,
SUM(
CASE
WHEN d.status IN ('retourne', 'retour-en-cours')
THEN ddp.quantity
ELSE 0
END
) AS qtRetour,
SUM(ddp.total_amount_ttc) AS montantTotal
FROM document_declination_produit ddp
JOIN produit_declination_value pdv
ON ddp.produit_declination_value_id = pdv.id
-- Déclinaison 1 (ex : Couleur)
JOIN group_declination_value gdv1
ON gdv1.produit_declination_id = pdv.id
JOIN value_declination v1
ON v1.id = gdv1.value_id
JOIN declination dcl1
ON dcl1.id = v1.declination_id
AND dcl1.position = 1
-- Déclinaison 2 (ex : Taille)
JOIN group_declination_value gdv2
ON gdv2.produit_declination_id = pdv.id
JOIN value_declination v2
ON v2.id = gdv2.value_id
JOIN declination dcl2
ON dcl2.id = v2.declination_id
AND dcl2.position = 2
JOIN document d
ON d.id = ddp.document_id
WHERE pdv.produit_id = :idProduit
AND d.type = 'commande'
AND d.category = 'client'
";
$params = ['idProduit' => $idProduit];
/* Gestion dynamique des dates */
if ($dateBefore !== null && $dateAfter !== null) {
$sql .= " AND d.created_at BETWEEN :dateBefore AND :dateAfter";
$params['dateBefore'] = $dateBefore;
$params['dateAfter'] = $dateAfter;
} elseif ($dateBefore !== null) {
$sql .= " AND d.created_at >= :dateBefore";
$params['dateBefore'] = $dateBefore;
} elseif ($dateAfter !== null) {
$sql .= " AND d.created_at <= :dateAfter";
$params['dateAfter'] = $dateAfter;
}
$sql .= "
GROUP BY dcl1.name, v1.name, v2.name, pdv.id
ORDER BY v1.name ASC, v2.name ASC
";
$stmt = $conn->prepare($sql);
$result = $stmt->executeQuery($params);
return $result->fetchAllAssociative();
}
public function countByValue(ValueDeclination $value): int
{
return $this->createQueryBuilder('gdv')
->select('COUNT(gdv.id)')
->where('gdv.value = :val')
->setParameter('val', $value)
->getQuery()
->getSingleScalarResult();
}
}