<?php
namespace CioPurchaseVolumesLimits\Subscriber;
use CioBudget\Definition\Budget\BudgetEntity;
use CioBudget\Definition\BudgetStore\BudgetStoreEntity;
use CioBudget\Service\BudgetLoaderService;
use CioBudget\Service\SessionService;
use CioCustomerPermissionGroups\Event\CustomerGroupsLoadedEvent;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Checkout\Customer\CustomerEntity;
use Shopware\Core\Checkout\Order\Aggregate\OrderCustomer\OrderCustomerEntity;
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter;
use Shopware\Core\System\SalesChannel\Entity\SalesChannelEntityLoadedEvent;
use Shopware\Storefront\Event\StorefrontRenderEvent;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use CioCustomerPermissionGroups\Event\CustomerAclRolesEvent;
use CioCustomerPermissionGroups\Service\CheckCustomerPermissionsService;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Validator\Constraints\DateTime;
use CioCustomerPermissionGroups\CioCustomerPermissionGroups;
class PurchaseLimitsSubscriber implements EventSubscriberInterface
{
private ContainerInterface $container;
private SessionService $sessionService;
private BudgetLoaderService $budgetLoaderService;
public function __construct(ContainerInterface $container, SessionService $sessionService, BudgetLoaderService $budgetLoaderService)
{
$this->container = $container;
$this->sessionService = $sessionService;
$this->budgetLoaderService = $budgetLoaderService;
}
public static function getSubscribedEvents(): array
{
// Return the events to listen to as array like this: <event to listen to> => <method to execute>
return [
'sales_channel.product.loaded' => 'onSalesChannelEntityLoadedEvent',
];
}
public function onSalesChannelEntityLoadedEvent(SalesChannelEntityLoadedEvent $event)
{
/** @var EntityRepository $budgetRepository */
$budgetRepository = $this->container->get('cio_budget.repository');
/** @var EntityRepository $storeRepository */
$storeRepository = $this->container->get('cio_budget_store.repository');
/** @var BudgetEntity|null $budget */
$budget = null;
/** @var string|null $budgetId */
$budgetId = $this->container->get('session')->get('cio_current_budget');
if ($budgetId) {
$searchResult = $budgetRepository->search(new Criteria([$budgetId]), $event->getContext());
if ($searchResult->count() === 1) {
$budget = $searchResult->first();
}
}
foreach ($event->getEntities() as $loadedProduct) {
if ($loadedProduct instanceof SalesChannelProductEntity) {
if ($budget) {
/** @var BudgetStoreEntity $budgetStore */
$budgetStore = $storeRepository->search(new Criteria([$budget->getStoreId()]), $event->getContext())->first();
if ($budgetStore) {
if ($limit = $this->matchingStoreLimit($loadedProduct, $budgetStore->getSize())) {
$loadedProduct->setCalculatedMaxPurchase(min([$limit['_limit'], $loadedProduct->getAvailableStock()]) - $this->currentVolumeByBudgetStore($loadedProduct, $event->getSalesChannelContext()->getCustomer(), $limit, $event->getContext()));
}
}
}
if ($event->getSalesChannelContext()->getCustomer() instanceof CustomerEntity) {
if ($limit = $this->matchingGroupLimit($event->getSalesChannelContext()->getCustomer(), $loadedProduct)) {
$loadedProduct->setCalculatedMaxPurchase(min([$limit['_limit'], $loadedProduct->getAvailableStock()]) - $this->currentVolumeByCustomerId($loadedProduct, $event->getSalesChannelContext()->getCustomer(), $limit, $event->getContext()));
}
}
}
}
}
protected function matchingStoreLimit(SalesChannelProductEntity $productEntity, $storeSize)
{
if (is_array($productEntity->getCustomFields()) && array_key_exists('cioStoreLimits', $productEntity->getCustomFields()) && is_array($productEntity->getCustomFields()['cioStoreLimits'])) {
foreach ($productEntity->getCustomFields()['cioStoreLimits'] as $limit) {
if ($limit['_storeSize'] === $storeSize) {
return $limit;
}
}
}
return null;
}
protected function matchingGroupLimit(CustomerEntity $customer, SalesChannelProductEntity $productEntity)
{
$resultLimit = null;
if (is_array($productEntity->getCustomFields()) && array_key_exists('cioGroupLimits', $productEntity->getCustomFields()) && is_array($productEntity->getCustomFields()['cioGroupLimits'])) {
foreach ($productEntity->getCustomFields()['cioGroupLimits'] as $limit) {
$limitGroupId = $limit['_groupId'];
$userGroupsIds = array();
$userGroups = $customer->getCustomFields()['custom_acl_roles'];
$userGroupsIds = CioCustomerPermissionGroups::getAclIds($userGroups);
$eventDispatcher = $this->container->get('event_dispatcher');
$customerGroupsLodedEvent = new CustomerGroupsLoadedEvent($userGroupsIds, $customer);
$eventDispatcher->dispatch($customerGroupsLodedEvent);
$userGroups = $customerGroupsLodedEvent->getGroups();
foreach ($userGroups as $userGroupId) {
if ($limitGroupId === $userGroupId) {
if ($resultLimit === null) {
$resultLimit = $limit;
} else {
if ($resultLimit['_limit'] < $limit['_limit']) {
$resultLimit = $limit;
}
}
}
}
}
return $resultLimit;
}
return null;
}
protected function currentVolumeByBudgetStore(SalesChannelProductEntity $productEntity, CustomerEntity $customerEntity, $limit, $context)
{
$currentSelectedBudgetId = $this->sessionService->getBudgetIdFromSession();
$currentBudget = $this->budgetLoaderService->getBudgetById($currentSelectedBudgetId);
/** @var EntityRepository $orderCustomerRepository */
$orderCustomerRepository = $this->container->get('order_customer.repository');
if ($limit['_timePeriod'] === 'order') {
return 0;
} else if ($limit['_timePeriod'] === 'weekly') {
// get first day of current week
$day = date('w') == 0 ? 6 : date('w') - 1;
$start = date('Y-m-d', strtotime("-{$day} days"));
} else if ($limit['_timePeriod'] === 'monthly') {
$start = date('Y-m-01');
} else if ($limit['_timePeriod'] === 'quartily') {
$start = self::datesOfQuarter('current', null, 'Y-m-d')['start'];
} else {
// yearly
$start = date('Y-01-01');
}
$criteria = (new Criteria())
->addFilter(new EqualsFilter('order.history.budget.store_id', $currentBudget->getStore()->getId()))
->addFilter(new RangeFilter('order.orderDate', [
RangeFilter::GTE => $start
]))
->addAssociation('order')->addAssociation('order.lineItems');
$orderCustomers = $orderCustomerRepository->search($criteria, $context);
$result = 0;
/** @var OrderCustomerEntity $orderCustomer */
foreach ($orderCustomers as $orderCustomer) {
$order = $orderCustomer->getOrder();
if ($order) {
/** @var LineItem $lineItem */
foreach ($order->getLineItems() as $lineItem) {
if ($lineItem->getPayload()['productNumber'] === $productEntity->getProductNumber()) {
$result = $result + $lineItem->getQuantity();
}
}
}
}
return $result;
}
protected function currentVolumeByCustomerId(SalesChannelProductEntity $productEntity, CustomerEntity $customerEntity, $limit, $context)
{
/** @var EntityRepository $orderCustomerRepository */
$orderCustomerRepository = $this->container->get('order_customer.repository');
if ($limit['_timePeriod'] === 'order') {
return 0;
} else if ($limit['_timePeriod'] === 'weekly') {
// get first day of current week
$day = date('w') == 0 ? 6 : date('w') - 1;
$start = date('Y-m-d', strtotime("-{$day} days"));
} else if ($limit['_timePeriod'] === 'monthly') {
$start = date('Y-m-01');
} else if ($limit['_timePeriod'] === 'quartily') {
$start = self::datesOfQuarter('current', null, 'Y-m-d')['start'];
} else {
// yearly
$start = date('Y-01-01');
}
$criteria = (new Criteria())
->addFilter(new EqualsFilter('customerId', $customerEntity->getId()))
->addFilter(new RangeFilter('order.orderDate', [
RangeFilter::GTE => $start
]))
->addAssociation('order')->addAssociation('order.lineItems');
$orderCustomers = $orderCustomerRepository->search($criteria, $context);
$result = 0;
/** @var OrderCustomerEntity $orderCustomer */
foreach ($orderCustomers as $orderCustomer) {
$order = $orderCustomer->getOrder();
if ($order) {
/** @var LineItem $lineItem */
foreach ($order->getLineItems() as $lineItem) {
if ($lineItem->getPayload()['productNumber'] === $productEntity->getProductNumber()) {
$result = $result + $lineItem->getQuantity();
}
}
}
}
return $result;
}
protected static function datesOfQuarter($quarter = 'current', $year = null, $format = null)
{
if (!is_int($year)) {
$year = (new \DateTime)->format('Y');
}
$current_quarter = ceil((new \DateTime)->format('n') / 3);
switch (strtolower($quarter)) {
case 'this':
case 'current':
$quarter = ceil((new \DateTime)->format('n') / 3);
break;
case 'previous':
$year = (new \DateTime)->format('Y');
if ($current_quarter == 1) {
$quarter = 4;
$year--;
} else {
$quarter = $current_quarter - 1;
}
break;
case 'first':
$quarter = 1;
break;
case 'last':
$quarter = 4;
break;
default:
$quarter = (!is_int($quarter) || $quarter < 1 || $quarter > 4) ? $current_quarter : $quarter;
break;
}
if ($quarter === 'this') {
$quarter = ceil((new \DateTime)->format('n') / 3);
}
$start = new \DateTime($year . '-' . (3 * $quarter - 2) . '-1 00:00:00');
$end = new \DateTime($year . '-' . (3 * $quarter) . '-' . ($quarter == 1 || $quarter == 4 ? 31 : 30) . ' 23:59:59');
return array(
'start' => $format ? $start->format($format) : $start,
'end' => $format ? $end->format($format) : $end,
);
}
}