@@ -627,7 +627,7 @@ class OrderShopBuilder | |||
$this->productSolver | |||
); | |||
$productsSalesStatistic->init($section, $this->orderShopSolver, $this->openingResolver); | |||
$productsSalesStatistic->init($section, $this->distributionBuilder, $this->openingResolver); | |||
$productsSalesStatistic->populateProperties($this->orderShopStore); | |||
return $productsSalesStatistic->getAsArray(); |
@@ -35,6 +35,7 @@ abstract class ProductFamilyAdminController extends AbstractAdminController | |||
$queryBuilder->leftJoin('entity.productFamilySectionProperties', 'pfsp'); | |||
$queryBuilder->andWhereSection('pfsp', $this->getSectionCurrent()); | |||
$queryBuilder->addOrderBy('entity.id'); | |||
return $queryBuilder; | |||
} | |||
@@ -11,6 +11,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; | |||
use Lc\CaracoleBundle\Context\MerchantContextTrait; | |||
use Lc\CaracoleBundle\Field\AssociationField; | |||
use Lc\SovBundle\Definition\Field\AbstractFieldDefinition; | |||
use Symfony\Component\Form\Extension\Core\Type\TextType; | |||
class OrderShopFieldDefinition extends AbstractFieldDefinition | |||
{ | |||
@@ -30,11 +31,11 @@ class OrderShopFieldDefinition extends AbstractFieldDefinition | |||
{ | |||
return [ | |||
'id' => IntegerField::new('id', 'ID')->setSortable(true), | |||
'userLastname' => TextareaField::new('user.lastname', 'field.default.lastname')->setSortable(true), | |||
'userLastname' => TextareaField::new('user.lastname')->setSortable(true), | |||
//->setTemplatePath('@LcShop/backend/default/field/textorempty.html.twig'), | |||
'userFirstname' => TextareaField::new('user.firstname', 'field.default.firstname')->setSortable(true), | |||
'userFirstname' => TextareaField::new('user.firstname')->setSortable(true), | |||
//->setTemplatePath('@LcShop/backend/default/field/textorempty.html.twig'), | |||
'userEmail' => TextareaField::new('user.email', 'field.default.email')->setSortable(true), | |||
'userEmail' => TextareaField::new('user.email')->setSortable(true), | |||
//->setTemplatePath('@LcShop/backend/default/field/user.html.twig'), | |||
'total' => NumberField::new('total') | |||
->setTemplatePath('@LcCaracole/admin/order/field/total.html.twig'), | |||
@@ -43,16 +44,24 @@ class OrderShopFieldDefinition extends AbstractFieldDefinition | |||
'createdAt' => DateTimeField::new('createdAt')->setSortable(true), | |||
'updatedAt' => DateTimeField::new('updatedAt')->setSortable(true), | |||
'orderShopCreatedAt' => DateTimeField::new('orderShopCreatedAt')->setSortable(true), | |||
'distribution' => AssociationField::new('distribution')->setSortable(true), | |||
'distribution' => AssociationField::new('distribution') | |||
->setSortable(true) | |||
->setCustomOption('filter_type', TextType::class) | |||
->setCustomOption('filter_on', 'cycleNumber') | |||
, | |||
'cycleDeliveryId' => IntegerField::new('cycleDeliveryId')->setSortable(true), | |||
'cycleId' => IntegerField::new('cycleId')->setSortable(true), | |||
'deliveryType' => Field::new('deliveryType')->setSortable(true), | |||
//->setTemplatePath('@LcShop/backend/default/field/options_translatable.html.twig'), | |||
'reference' => TextField::new('reference')->setSortable(true), | |||
'complementaryOrderShops' => AssociationField::new('complementaryOrderShops')->setFormTypeOption('mapped', false) | |||
->setTemplatePath('@LcCaracole/admin/order/field/complementary.html.twig'), | |||
'complementaryOrderShops' => AssociationField::new('complementaryOrderShops') | |||
->setFormTypeOption('mapped', false) | |||
->setTemplatePath('@LcCaracole/admin/order/field/complementary.html.twig') | |||
->setCustomOption('filter', false) | |||
, | |||
'orderPayments' => AssociationField::new('orderPayments') | |||
->setTemplatePath('@LcCaracole/admin/order/field/order_payment.html.twig'), | |||
->setTemplatePath('@LcCaracole/admin/order/field/order_payment.html.twig') | |||
->setCustomOption('filter', false), | |||
'user'=> AssociationField::new('user')->setSortable(true) | |||
]; |
@@ -0,0 +1,51 @@ | |||
<?php | |||
namespace Lc\CaracoleBundle\Field\Filter; | |||
use Doctrine\ORM\EntityRepository; | |||
use Doctrine\ORM\QueryBuilder; | |||
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto; | |||
use Lc\SovBundle\Field\Filter\AssociationFilter; | |||
use Lc\SovBundle\Field\Filter\FilterTrait; | |||
use Symfony\Bridge\Doctrine\Form\Type\EntityType; | |||
use Symfony\Component\Form\FormBuilderInterface; | |||
/** | |||
* @author La clic ! <contact@laclic.fr> | |||
*/ | |||
class ProductCategoriesFilter extends AssociationFilter | |||
{ | |||
public function buildProperty(FormBuilderInterface $builder, FieldDto $fieldDto, $options = array()) | |||
{ | |||
$targetEntity = $options['entity_dto']->getPropertyMetadata($fieldDto->getProperty())->get('targetEntity'); | |||
$builder->add( | |||
$fieldDto->getProperty(), | |||
EntityType::class, | |||
array( | |||
'class' => $targetEntity, | |||
'placeholder' => '--', | |||
'choices' => $fieldDto->getFormTypeOption('choices'), | |||
'required' => false, | |||
'attr' => array( | |||
'class' => 'select2 input-sm', | |||
'form' => 'filters-form', | |||
), | |||
) | |||
); | |||
} | |||
public function applyFilter(QueryBuilder $queryBuilder, string $fieldProperty, $filteredValue = null) | |||
{ | |||
if ($filteredValue !== null) { | |||
$queryBuilder->leftJoin('entity.productCategories', 'product_categories'); | |||
$queryBuilder->andWhere(':' . $fieldProperty . ' MEMBER OF entity.' . $fieldProperty . ' OR product_categories.parent = :' . $fieldProperty); | |||
$queryBuilder->setParameter($fieldProperty, $filteredValue); | |||
} | |||
} | |||
} |
@@ -40,6 +40,11 @@ abstract class DistributionModel implements DistributionInterface, EntityInterfa | |||
*/ | |||
protected $cycleType; | |||
public function __toString() | |||
{ | |||
return $this->getCycleNumber().'/'.$this->getYear(); | |||
} | |||
public function getSection(): ?SectionInterface | |||
{ | |||
return $this->section; |
@@ -26,7 +26,7 @@ abstract class OrderProductRefundModel | |||
protected $price; | |||
/** | |||
* @ORM\OneToOne(targetEntity="Lc\CaracoleBundle\Model\Order\OrderProductInterface", inversedBy="orderProductRefund", cascade={"persist", "remove"}) | |||
* @ORM\OneToOne(targetEntity="Lc\CaracoleBundle\Model\Order\OrderProductInterface") | |||
* @ORM\JoinColumn(nullable=false) | |||
*/ | |||
protected $orderProduct; |
@@ -18,13 +18,13 @@ abstract class ProductCategoryModel extends AbstractFullEntity implements TreeIn | |||
ProductCategoryInterface | |||
{ | |||
/** | |||
* @ORM\ManyToOne(targetEntity="Lc\CaracoleBundle\Model\Section\SectionInterface") | |||
* @ORM\ManyToOne(targetEntity="Lc\CaracoleBundle\Model\Section\SectionInterface", inversedBy="productCategories") | |||
* @ORM\JoinColumn(nullable=false) | |||
*/ | |||
protected $section; | |||
/** | |||
* @ORM\ManyToOne(targetEntity="Lc\CaracoleBundle\Model\Product\ProductCategoryInterface", inversedBy="childrens") | |||
* @ORM\ManyToOne(targetEntity="Lc\CaracoleBundle\Model\Product\ProductCategoryInterface", inversedBy="childrens", fetch="EAGER") | |||
*/ | |||
protected $parent; | |||
@@ -50,11 +50,6 @@ abstract class SectionModel extends AbstractFullEntity implements FilterMerchant | |||
*/ | |||
protected $color; | |||
/** | |||
* @ORM\ManyToMany(targetEntity="Lc\CaracoleBundle\Model\Product\ProductFamilyInterface", mappedBy="sections") | |||
*/ | |||
protected $productFamilies; | |||
/** | |||
* @ORM\OneToMany(targetEntity="Lc\CaracoleBundle\Model\Order\OrderShopInterface", mappedBy="section") | |||
*/ | |||
@@ -97,7 +92,6 @@ abstract class SectionModel extends AbstractFullEntity implements FilterMerchant | |||
public function __construct() | |||
{ | |||
$this->productFamilies = new ArrayCollection(); | |||
$this->orderShops = new ArrayCollection(); | |||
$this->productCategories = new ArrayCollection(); | |||
$this->news = new ArrayCollection(); | |||
@@ -149,34 +143,6 @@ abstract class SectionModel extends AbstractFullEntity implements FilterMerchant | |||
return $this; | |||
} | |||
/** | |||
* @return Collection|ProductFamilyInterface[] | |||
*/ | |||
public function getProductFamilies(): Collection | |||
{ | |||
return $this->productFamilies; | |||
} | |||
public function addProductFamily(ProductFamilyInterface $productFamily): self | |||
{ | |||
if (!$this->productFamilies->contains($productFamily)) { | |||
$this->productFamilies[] = $productFamily; | |||
$productFamily->addSection($this); | |||
} | |||
return $this; | |||
} | |||
public function removeProductFamily(ProductFamilyInterface $productFamily): self | |||
{ | |||
if ($this->productFamilies->contains($productFamily)) { | |||
$this->productFamilies->removeElement($productFamily); | |||
$productFamily->removeSection($this); | |||
} | |||
return $this; | |||
} | |||
/** | |||
* @return Collection|OrderShopInterface[] | |||
*/ |
@@ -24,6 +24,7 @@ class OrderShopRepositoryQuery extends AbstractRepositoryQuery | |||
use SectionRepositoryQueryTrait; | |||
protected bool $isJoinProduct = false; | |||
protected bool $isJoinDistribution = false; | |||
protected bool $isJoinProductFamily = false; | |||
protected bool $isJoinOrderProduct = false; | |||
protected bool $isJoinOrderReductionCredits = false; | |||
@@ -63,7 +64,7 @@ class OrderShopRepositoryQuery extends AbstractRepositoryQuery | |||
$this->joinDistribution(); | |||
return $this | |||
->select( | |||
'SUM(orderProduct.quantityOrder) as quantity, .cycleNumber as cycleNumber, product.id as productId' | |||
'SUM(orderProduct.quantityOrder) as quantity, distribution.cycleNumber as cycleNumber, distribution.year as year , product.id as productId' | |||
); | |||
} | |||
@@ -247,6 +248,20 @@ class OrderShopRepositoryQuery extends AbstractRepositoryQuery | |||
return $this; | |||
} | |||
public function joinDistribution(bool $addSelect = false): self | |||
{ | |||
if (!$this->isJoinDistribution) { | |||
$this->isJoinDistribution = true; | |||
$this->leftJoin('.distribution', 'distribution'); | |||
if ($addSelect) { | |||
$this->addSelect('distribution'); | |||
} | |||
} | |||
return $this; | |||
} | |||
public function joinProductFamily(bool $addSelect = false): self | |||
{ | |||
$this->joinProduct($addSelect); |
@@ -547,13 +547,12 @@ class OrderShopStore extends AbstractStore | |||
$query = null | |||
): array { | |||
$query = $this->createDefaultQuery($query); | |||
$query | |||
->filterByAlias(OrderStatusModel::$statusAliasAsValid) | |||
->filterByDistributions($distributions) | |||
->filterByProducts($products) | |||
->selectSum() | |||
->groupBy('.distribution, product.id'); | |||
->groupBy('distribution.cycleNumber, product.id'); | |||
return $query->find(); | |||
@@ -569,7 +568,7 @@ class OrderShopStore extends AbstractStore | |||
->filterByDistribution($distribution) | |||
->filterByProduct($productId) | |||
->selectSumQuantityOrder() | |||
->groupBy('.distribution, product.id'); | |||
->groupBy('distribution.cycleNumber, product.id'); | |||
$result = $query->findOne(); | |||
@@ -15,6 +15,8 @@ use Symfony\Component\Security\Core\Security; | |||
class SectionResolver | |||
{ | |||
protected ?SectionInterface $section = null; | |||
protected EntityManagerInterface $entityManager; | |||
protected MerchantResolver $merchantResolver; | |||
protected SectionStore $sectionStore; | |||
@@ -22,12 +24,13 @@ class SectionResolver | |||
protected UrlResolver $urlResolver; | |||
public function __construct( | |||
EntityManagerInterface $entityManager, | |||
MerchantResolver $merchantResolver, | |||
SectionStore $sectionStore, | |||
RequestStack $requestStack, | |||
UrlResolver $urlResolver | |||
) { | |||
EntityManagerInterface $entityManager, | |||
MerchantResolver $merchantResolver, | |||
SectionStore $sectionStore, | |||
RequestStack $requestStack, | |||
UrlResolver $urlResolver | |||
) | |||
{ | |||
$this->entityManager = $entityManager; | |||
$this->merchantResolver = $merchantResolver; | |||
$this->sectionStore = $sectionStore; | |||
@@ -41,24 +44,28 @@ class SectionResolver | |||
// admin | |||
if (isset($requestAttributesArray['easyadmin_context'])) { | |||
$currentAdminSection = null; | |||
$userMerchant = $this->merchantResolver->getUserMerchant(); | |||
if ($this->section === null) { | |||
$currentAdminSection = null; | |||
$userMerchant = $this->merchantResolver->getUserMerchant(); | |||
if ($userMerchant !== null) { | |||
$currentAdminSection = $userMerchant->getCurrentAdminSection(); | |||
} | |||
if ($userMerchant !== null) { | |||
$currentAdminSection = $userMerchant->getCurrentAdminSection(); | |||
} | |||
if ($currentAdminSection === null) { | |||
$currentAdminSection = $this->sectionStore | |||
if ($currentAdminSection === null) { | |||
$currentAdminSection = $this->sectionStore | |||
->setMerchant($userMerchant->getMerchant()) | |||
->getOneDefault(); | |||
if ($currentAdminSection === null) { | |||
throw new \ErrorException('Aucune section par défaut définie pour ce merchant'); | |||
if ($currentAdminSection === null) { | |||
throw new \ErrorException('Aucune section par défaut définie pour ce merchant'); | |||
} | |||
} | |||
$this->section = $currentAdminSection; | |||
return $currentAdminSection; | |||
}else{ | |||
return $this->section; | |||
} | |||
return $currentAdminSection; | |||
} // front | |||
else { | |||
$merchantCurrent = $this->merchantResolver->getCurrent(); | |||
@@ -66,17 +73,17 @@ class SectionResolver | |||
$sectionCurrent = null; | |||
$sectionDefault = $sectionStore->getOneDefault(); | |||
if(isset($requestAttributesArray['section'])) { | |||
if (isset($requestAttributesArray['section'])) { | |||
$sectionCurrent = $sectionStore | |||
->setMerchant($merchantCurrent) | |||
->getOneBySlug($requestAttributesArray['section']); | |||
->setMerchant($merchantCurrent) | |||
->getOneBySlug($requestAttributesArray['section']); | |||
} | |||
return $sectionCurrent ?: $sectionDefault; | |||
} | |||
} | |||
public function getDefault():SectionInterface | |||
public function getDefault(): SectionInterface | |||
{ | |||
return $this->sectionStore->setMerchant($this->merchantResolver->getCurrent())->getOneDefault(); | |||
} |
@@ -1,6 +1,5 @@ | |||
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} | |||
{% import '@LcCaracole/admin/product/macro/product_family_macro.html.twig' as pfm %} | |||
{% set section_current = section_container.resolver.getCurrent() %} | |||
{% set distribution = distribution_container.builder.guessCurrentDistributionDelivery(section_current) %} | |||
{# | |||
{{ pfm.product_family_sales_statistic(order_shop_container.builder.getProductsSalesStatistic(section_current, entity.instance, 2), entity.instance) }}#} | |||
{{ pfm.product_family_sales_statistic(order_shop_container.builder.getProductsSalesStatistic(section_current, entity.instance, 2), entity.instance) }} |
@@ -4,10 +4,11 @@ | |||
{% if productsSalesStatistic %} | |||
<button type="button" data-product-family="{{ productFamily.id }}" | |||
class="lc-show-products-sales-statistic btn btn-sm" | |||
data-toggle="tooltip" title="{{ 'action.product.statSales'|trans }}" | |||
data-toggle="tooltip" title="{{ 'showHistorySales'|sov_trans_admin_action }}" | |||
data-url="{{ ea_url({crudAction : 'showSalesStatistic', entityId: productFamily.id }) }}"> | |||
{% for weekNumber, weekNumberQuantity in productsSalesStatistic['data']['total_sales']['data']|reverse(true) %} | |||
<span class="text-success"><i class="fa fa-calendar"></i> {{ weekNumber }}</span> | |||
{% for key, weekNumberQuantity in productsSalesStatistic['data']['total_sales']['data'] %} | |||
<span class="text-success"><i | |||
class="fa fa-calendar"></i> {{ productsSalesStatistic['label'][key] }}</span> | |||
<span class="text-info"><i class="fa fa-shopping-basket"></i> | |||
<strong> | |||
{{ weekNumberQuantity is null ? 0 : weekNumberQuantity }} |
@@ -1,7 +1,7 @@ | |||
{% embed "@LcSov/adminlte/embed/modal.twig" %} | |||
{% import '@LcCaracole/admin/product/macro/product_family_macro.html.twig' as pfm %} | |||
{% block size %}modal-lg{% endblock %} | |||
{% block size %}modal-xl{% endblock %} | |||
{% block id %}modal-products-sales-statistic{% endblock %} | |||
{% block title %}Total ventes/semaines : {{ productFamily.title }}{% endblock %} | |||
@@ -18,12 +18,12 @@ | |||
{% endif %} | |||
</div> | |||
<!-- | |||
<div class="col-2"> | |||
<h5>Stock actuel</h5> | |||
{#{% include '@LcShop/backend/default/field/product_family_available_quantity.html.twig' with {item:productFamily, value: productFamily.availableQuantity} %}#} | |||
{% include 'admin/product/field/product_family_available_quantity.html.twig' with {item:productFamily, value: productFamily.availableQuantity} %} | |||
</div> | |||
--> | |||
<div class="col-12"> | |||
{{ _self.table_pss(productsSalesStatistic, productFamily, 'total_sales') }} |
@@ -2,6 +2,8 @@ | |||
namespace Lc\CaracoleBundle\Solver\Distribution; | |||
use App\Entity\Distribution\Distribution; | |||
class DistributionSolver | |||
{ | |||
@@ -10,15 +10,17 @@ use Lc\CaracoleBundle\Model\Section\SectionInterface; | |||
use Lc\CaracoleBundle\Repository\Order\OrderProductStore; | |||
use Lc\CaracoleBundle\Repository\Order\OrderShopStore; | |||
use Lc\CaracoleBundle\Resolver\OpeningResolver; | |||
use Lc\CaracoleBundle\Solver\Distribution\DistributionSolver; | |||
use Lc\CaracoleBundle\Solver\Order\OrderShopSolver; | |||
use Lc\CaracoleBundle\Solver\Product\ProductSolver; | |||
use Lc\CaracoleBundle\Statistic\Statistic; | |||
use function Symfony\Component\Translation\t; | |||
class ProductsSalesStatistic extends Statistic | |||
{ | |||
protected int $nbCycle; | |||
protected $productFamily; | |||
protected $cycleNumbers = array(); | |||
protected $distributionList = array(); | |||
protected $productIds = array(); | |||
protected ProductSolver $productSolver; | |||
@@ -54,45 +56,46 @@ class ProductsSalesStatistic extends Statistic | |||
// Initialise les valeurs des données pour chaque Interval de date | |||
public function init(SectionInterface $section, DistributionBuilder $distributionBuilder, OpeningResolver $openingResolver) | |||
{ | |||
$currentDistribution = $distributionBuilder->guessCurrentDistributionOrder($section); | |||
$this->distributionList = $distributionBuilder->getDistributionListFromCurrentOrder($section, $this->nbCycle); | |||
// if ($openingResolver->isOpenSale($section, null,OpeningResolver::OPENING_CONTEXT_BACKEND) == false && date('w') > 2) { | |||
// $currentCycleNumber = $currentCycleNumber - 1; | |||
// } | |||
$this->cycleNumbers = array(); | |||
for ($w = $currentCycleNumber - $this->nbCycle + 1; $w <= $currentCycleNumber; $w++) { | |||
$this->cycleNumbers[] = $w; | |||
$this->labels[$w] = 'S ' . $w; | |||
foreach ($this->distributionList as $distribution){ | |||
$this->labels[$this->getKey($distribution->getCycleNumber(),$distribution->getYear())] = $distribution->getCycleNumber(); | |||
foreach ($this->getProperties() as $propertyName => $property) { | |||
$this->properties[$propertyName]['data'][$w] = 0; | |||
$this->properties[$propertyName]['data'][$this->getKey($distribution->getCycleNumber(),$distribution->getYear())] = 0; | |||
} | |||
foreach ($this->getAverageProperties() as $propertyName => $property) { | |||
$this->averageProperties[$propertyName]['data'][$w] = 0; | |||
$this->averageProperties[$propertyName]['data'][$this->getKey($distribution->getCycleNumber(),$distribution->getYear())] = 0; | |||
} | |||
} | |||
} | |||
public function populateProperties(OrderShopStore $orderShopStore) | |||
{ | |||
$countsOrderedByCyclesAndProducts = $orderShopStore->countValidOrderProductsOfCyclesByProducts( | |||
$this->cycleNumbers, | |||
$countsOrderedByCyclesAndProducts = $orderShopStore->countValidOrderProductsOfDistributionsByProducts( | |||
$this->distributionList, | |||
$this->productIds | |||
); | |||
foreach ($countsOrderedByCyclesAndProducts as $result) { | |||
$this->setData($result['productId'], $result['cycleNumber'], $result['quantity']); | |||
$this->setData($result['productId'], $this->getKey($result['cycleNumber'],$result['year']), $result['quantity']); | |||
$product = $this->productIds[$result['productId']]; | |||
if ($this->productFamily->getBehaviorDisplaySale() == ProductFamilyModel::BEHAVIOR_DISPLAY_SALE_BY_MEASURE) { | |||
$ratioByMeasure = $this->productSolver->getQuantityInherited($product) / $this->productSolver->getUnitInherited($product)->getCoefficient(); | |||
$this->setData('total_sales', $result['cycleNumber'], intval($result['quantity']) * $ratioByMeasure); | |||
$this->setData('total_sales', $this->getKey($result['cycleNumber'],$result['year']), intval($result['quantity']) * $ratioByMeasure); | |||
} else { | |||
$this->setData('total_sales', $result['cycleNumber'], intval($result['quantity'])); | |||
$this->setData('total_sales', $this->getKey($result['cycleNumber'],$result['year']), intval($result['quantity'])); | |||
} | |||
} | |||
$this->setAveragePropertiesData(); | |||
} | |||
protected function getKey($cycleNumber, $year){ | |||
return $cycleNumber.'/'.substr($year,2); | |||
} | |||
} |