custom/plugins/WbfkBundles/src/Service/BundleProductDeliveryAndAvailabilityCalculator.php line 46

Open in your IDE?
  1. <?php
  2. namespace Wbfk\Bundles\Service;
  3. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
  4. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  5. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  6. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  7. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  8. use Shopware\Core\System\SystemConfig\SystemConfigService;
  9. use Wbfk\Bundles\Entity\Bundle\BundleProductCollection;
  10. use Wbfk\Bundles\Entity\Bundle\BundleProductEntity;
  11. use Wbfk\Bundles\Helper\IsSalesChannelProductBuyable;
  12. use WbfkExtensions\Core\Checkout\Cart\Delivery\ShippingAndDeliveryInformation\ShippingAndDeliveryDateRange;
  13. use WbfkExtensions\Core\Checkout\Cart\Delivery\ShippingAndDeliveryInformation\ShippingAndDeliveryInformation;
  14. use WbfkExtensions\Core\Checkout\Cart\Delivery\ShippingAndDeliveryInformation\ShippingAndDeliveryInformationCollection;
  15. use WbfkExtensions\Core\Checkout\Cart\Delivery\ShippingAndDeliveryInformationService;
  16. use WbfkExtensions\Core\Content\WbfkProductExtension\WbfkProductExtensionEntity;
  17. class BundleProductDeliveryAndAvailabilityCalculator
  18. {
  19. use IsSalesChannelProductBuyable;
  20. public function __construct(
  21. protected readonly EntityRepository $bundleProductRepository,
  22. protected readonly ShippingAndDeliveryInformationService $expectedProductDeliveryTimeService,
  23. protected readonly SystemConfigService $systemConfigService
  24. ) {
  25. }
  26. public function calculatedDeliveryAndAvailability(SalesChannelProductEntity $product, SalesChannelContext $salesChannelContext): void
  27. {
  28. $bundles = $this->getBundles($product->getId(), $salesChannelContext);
  29. if ($bundles->count() === 0) {
  30. return;
  31. }
  32. /** @noinspection PhpDeprecationInspection */
  33. $product->addExtension('wbfk_bundle_product', $bundles);
  34. $this->findDeliveryTimeAndAvailabilityBasedOnChildProducts(
  35. $product,
  36. $bundles,
  37. $salesChannelContext
  38. );
  39. }
  40. public function getBundles(
  41. string $productId,
  42. SalesChannelContext $salesChannelContext
  43. ): BundleProductCollection {
  44. $context = $salesChannelContext->getContext();
  45. $criteria = new Criteria();
  46. $criteria->setTitle("Getting bundle products child products data");
  47. $criteria->addFilter(new EqualsFilter('productId', $productId));
  48. $criteria->addAssociation('salesChannelChildProduct.productSuppliers.wbfkSupplier.deliveryTimes');
  49. $productSupplierAssociation = $criteria->getAssociation('salesChannelChildProduct');
  50. $productSupplierAssociation->addFilter(new EqualsFilter('productSuppliers.active', true));
  51. $productSupplierAssociation->addFilter(new EqualsFilter('wbfkSupplier.isActive', true));
  52. /** @var BundleProductCollection|BundleProductEntity[] $bundles */
  53. $bundles = $this->bundleProductRepository->search($criteria, $context)->getEntities();
  54. $this->findChildProductsDeliveryTimes($bundles, $salesChannelContext);
  55. return $bundles;
  56. }
  57. private function findDeliveryTimeAndAvailabilityBasedOnChildProducts(
  58. SalesChannelProductEntity $product,
  59. BundleProductCollection $bundleProducts,
  60. SalesChannelContext $salesChannelContext
  61. ): void {
  62. static $sysMaxQuantity = 0;
  63. if ($sysMaxQuantity === 0) {
  64. $sysMaxQuantity = $this->systemConfigService->getInt(
  65. 'core.cart.maxQuantity',
  66. $salesChannelContext->getSalesChannel()->getId()
  67. );
  68. }
  69. $minimumDeliveryDate = null;
  70. $maximumDeliveryDate = null;
  71. $fastestDeliveryInformation = null;
  72. // First calculate the max available Quantity from the Parent-Product configuration
  73. // Later we will add the max available Quantity given by the child products
  74. // For reference:
  75. // \Shopware\Core\Content\Product\ProductMaxPurchaseCalculator->calculate()
  76. $calculatedMaxPurchase = (function (SalesChannelProductEntity $product) use ($sysMaxQuantity): int {
  77. $steps = $product->getPurchaseSteps() ?? 1;
  78. $min = $product->getMinPurchase() ?? 1;
  79. $max = $product->getMaxPurchase() ?? $sysMaxQuantity;
  80. // the amount of times the purchase step is fitting in between min and max added to the minimum
  81. return \floor(($max - $min) / $steps) * $steps + $min;
  82. })($product);
  83. // Also calculate the stock. This is only derived by the children products
  84. $stock = PHP_INT_MAX;
  85. $parentCloseOut = $product->getIsCloseout();
  86. $areAllChildrenDeliverable = true;
  87. $areAllChildrenBuyable = true;
  88. foreach ($bundleProducts as $bundleProduct) {
  89. $childProduct = $bundleProduct->getSalesChannelChildProduct();
  90. if (!$this->isBuyable($childProduct)) {
  91. $areAllChildrenBuyable = false;
  92. $calculatedMaxPurchase = 0;
  93. }
  94. /*
  95. * Calculate the Bundle-Stock from the child products
  96. * If we have a stock in our own Product-Extension, use this
  97. * It is set for products using the warehouses system from shopware commerce
  98. */
  99. $stockByChild = (function (BundleProductEntity $bundleProduct, SalesChannelProductEntity $childProduct) {
  100. $stock = (int)$childProduct->getAvailableStock();
  101. /** @var WbfkProductExtensionEntity $wpe */
  102. if ($wpe = $childProduct->getExtension('WbfkProductExtension')) {
  103. $stock = $wpe->getSuppliersTotalStock();
  104. }
  105. // Also consider if this child is multiple times in the bundle
  106. return (int)($stock / $bundleProduct->getQuantity());
  107. })($bundleProduct, $childProduct);
  108. $stock = min($stock, $stockByChild);
  109. /*
  110. * If the bundle or the child is in clearance sale (closeout) we restrict the bundle by the stock of the child article.
  111. * But we ignore the configured max purchase quantities on the child, since quantities should be configured on the bundle product itself.
  112. * We might not sell more than 100 cables. But we might sell more than 10 Bundle-Products each containing 10 cables or more.
  113. */
  114. if ($childProduct->getIsCloseout() || $parentCloseOut) {
  115. $calculatedMaxPurchase = min($calculatedMaxPurchase, $stockByChild);
  116. }
  117. /** @var ShippingAndDeliveryInformationCollection $shippingAndDeliveryInformations */
  118. $shippingAndDeliveryInformations = $childProduct->getExtension('shippingAndDeliveryInformations');
  119. $fastestDeliveryInformation = $shippingAndDeliveryInformations->fastestDeliveryInformation();
  120. if ($fastestDeliveryInformation === null) {
  121. $areAllChildrenDeliverable = false;
  122. continue;
  123. }
  124. /*
  125. * Delivery time of the bundle is given by its slowest component / article
  126. * Get the latest minimum delivery time
  127. * Get the latest maximum delivery time
  128. */
  129. if ($minimumDeliveryDate === null || $minimumDeliveryDate < $fastestDeliveryInformation->earliestDeliveryDate()) {
  130. $minimumDeliveryDate = $fastestDeliveryInformation->earliestDeliveryDate();
  131. }
  132. if ($maximumDeliveryDate === null || $maximumDeliveryDate < $fastestDeliveryInformation->latestDeliveryDate()) {
  133. $maximumDeliveryDate = $fastestDeliveryInformation->latestDeliveryDate();
  134. }
  135. }
  136. if ($areAllChildrenBuyable === false) {
  137. $product->setCalculatedMaxPurchase(0);
  138. return;
  139. }
  140. $product->setCalculatedMaxPurchase($calculatedMaxPurchase);
  141. $lastProductDeliveryInformation = $fastestDeliveryInformation;
  142. if ($areAllChildrenDeliverable && $lastProductDeliveryInformation) {
  143. $shippingAndDeliveryDateRange = new ShippingAndDeliveryDateRange(
  144. null,
  145. null,
  146. $minimumDeliveryDate,
  147. $maximumDeliveryDate
  148. );
  149. $shippingAndDeliveryInformation = new ShippingAndDeliveryInformation(
  150. $product->getId(),
  151. $lastProductDeliveryInformation->supplierId,
  152. $lastProductDeliveryInformation->supplierDisplayName,
  153. $lastProductDeliveryInformation->supplierPriority,
  154. $stock,
  155. $shippingAndDeliveryDateRange,
  156. $shippingAndDeliveryDateRange
  157. );
  158. $shippingAndDeliveryInformationsForBundle = new ShippingAndDeliveryInformationCollection([$shippingAndDeliveryInformation]);
  159. /** @noinspection PhpDeprecationInspection */
  160. $product->addExtension('shippingAndDeliveryInformations', $shippingAndDeliveryInformationsForBundle);
  161. }
  162. }
  163. private function findChildProductsDeliveryTimes(
  164. BundleProductCollection $bundles,
  165. SalesChannelContext $context
  166. ): void {
  167. $bundles->map(function (BundleProductEntity $bundle) use ($context) {
  168. $product = $bundle->getSalesChannelChildProduct();
  169. /** @noinspection PhpDeprecationInspection */
  170. $product->addExtension(
  171. 'shippingAndDeliveryInformations',
  172. $this->expectedProductDeliveryTimeService->getShippingAndDeliveryInformationSortedBySupplierPriority(
  173. $product,
  174. $context
  175. )
  176. );
  177. });
  178. }
  179. }