BelmilB2B

<?php

namespace Drupal\bb2b_baselinker;

use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\Core\Asset\AssetCollectionRendererInterface;
use Drupal\Core\Asset\AssetResolverInterface;
use Drupal\Core\Asset\AttachedAssets;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ExtensionPathResolver;
use Drupal\Core\Extension\InfoParserInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\Queue\RequeueException;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Theme\ThemeInitializationInterface;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use GuzzleHttp\RequestOptions;
use Mpdf\Mpdf;
use Mpdf\Output\Destination;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * BaseLinker Sync Service.
 */
class SyncService {

  use StringTranslationTrait;

  /**
   * Get orders method name.
   *
   * @var string
   */
  const GET_ORDERS_METHOD = 'getOrders';

  /**
   * Last order confirmed time key.
   *
   * @var string
   */
  const LAST_ORDER_CONFIRMED_TIME = 'bb2b_baselinker.last_order_confirmed_time';

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The config.
   *
   * @var \Drupal\Core\Config\ImmutableConfig
   */
  protected $config;

  /**
   * The config for athenapdf.
   *
   * @var \Drupal\Core\Config\ImmutableConfig
   */
  protected $athenapdfConfig;

  /**
   * The logger.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $logger;

  /**
   * The http client.
   *
   * @var \GuzzleHttp\Client
   */
  protected $httpClient;

  /**
   * The order queue.
   *
   * @var \Drupal\Core\Queue\QueueFactory
   */
  protected $getOrderQueue;

  /**
   * The state.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;

  /**
   * The renderer.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * The theme handler.
   *
   * @var \Drupal\Core\Extension\ThemeHandlerInterface
   */
  protected $themeHandler;

  /**
   * The extension path resolver.
   *
   * @var \Drupal\Core\Extension\ExtensionPathResolver
   */
  protected $extensionPathResolver;

  /**
   * The info parser.
   *
   * @var \Drupal\Core\Extension\InfoParserInterface
   */
  protected $infoParser;

  /**
   * The theme initialization.
   *
   * @var \Drupal\Core\Theme\ThemeInitializationInterface
   */
  protected $themeInitialization;

  /**
   * The asset resolver.
   *
   * @var \Drupal\Core\Asset\AssetResolverInterface
   */
  protected $assetResolver;

  /**
   * The CSS collection renderer.
   *
   * @var \Drupal\Core\Asset\AssetCollectionRendererInterface
   */
  protected $cssCollectionRenderer;

  /**
   * The current request.
   *
   * @var \Symfony\Component\HttpFoundation\Request
   */
  protected $request;

  /**
   * File system.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * The file URL generator.
   *
   * @var \Drupal\Core\File\FileUrlGeneratorInterface
   */
  protected $fileUrlGenerator;

  /**
   * Constructs a new SyncService object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The configuration factory.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory_athenapdf
   *   The configuration factory for athenapdf api.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger.
   * @param \GuzzleHttp\Client $http_client
   *   The http client.
   * @param \Drupal\Core\Queue\QueueFactory $queue_factory
   *   The queue factory.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state.
   * @param \Drupal\Core\Database\Connection $database
   *   The database connection.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer.
   * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
   *   The theme handler.
   * @param \Drupal\Core\Extension\ExtensionPathResolver $extension_path_resolver
   *   The extension path resolver.
   * @param \Drupal\Core\Extension\InfoParserInterface $info_parser
   *   The info parser.
   * @param \Drupal\Core\Theme\ThemeInitializationInterface $theme_initialization
   *   The theme initialization.
   * @param \Drupal\Core\Asset\AssetResolverInterface $asset_resolver
   *   The asset resolver.
   * @param \Drupal\Core\Asset\AssetCollectionRendererInterface $css_collection_renderer
   *   The CSS collection renderer.
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
   *   File System.
   * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator
   *   The file URL generator.
   */
  public function __construct(
    EntityTypeManagerInterface $entity_type_manager,
    ConfigFactoryInterface $config_factory,
    ConfigFactoryInterface $config_factory_athenapdf,
    LoggerChannelFactoryInterface $logger_factory,
    Client $http_client,
    QueueFactory $queue_factory,
    StateInterface $state,
    Connection $database,
    RendererInterface $renderer,
    ThemeHandlerInterface $theme_handler,
    ExtensionPathResolver $extension_path_resolver,
    InfoParserInterface $info_parser,
    ThemeInitializationInterface $theme_initialization,
    AssetResolverInterface $asset_resolver,
    AssetCollectionRendererInterface $css_collection_renderer,
    RequestStack $request_stack,
    FileSystemInterface $fileSystem,
    FileUrlGeneratorInterface $file_url_generator
  ) {
    $this->entityTypeManager = $entity_type_manager;
    $this->config = $config_factory->get('bb2b_baselinker.settings');
    $this->athenapdfConfig = $config_factory_athenapdf->get('athenapdf_api.settings');
    $this->logger = $logger_factory->get('bb2b_baselinker');
    $this->httpClient = $http_client;
    $this->getOrderQueue = $queue_factory->get('get_orders_from_baselinker');
    $this->addProductQueue = $queue_factory->get('bb2b_baselinker_addproduct');
    $this->state = $state;
    $this->database = $database;
    $this->renderer = $renderer;
    $this->themeHandler = $theme_handler;
    $this->extensionPathResolver = $extension_path_resolver;
    $this->infoParser = $info_parser;
    $this->themeInitialization = $theme_initialization;
    $this->assetResolver = $asset_resolver;
    $this->cssCollectionRenderer = $css_collection_renderer;
    $this->request = $request_stack->getCurrentRequest();
    $this->fileSystem = $fileSystem;
    $this->fileUrlGenerator = $file_url_generator;
  }

  /**
   * Get orders from BaseLinker API.
   *
   * A maximum of 100 orders are returned at a time.
   */
  public function getOrders() {
    // The buyer's data are deleted after 30 days of shipment.
    $date_confirmed_from = $this->state
      ->get(self::LAST_ORDER_CONFIRMED_TIME, strtotime('-30 days'));

    $parameters = [
      'date_confirmed_from' => $date_confirmed_from,
    ];
    $items = $this->getItems(self::GET_ORDERS_METHOD, $parameters);

    $api_orders = $items['orders'] ?? [];
    if ($api_orders && $items['status'] === 'SUCCESS') {
      foreach ($api_orders as $order) {
        if ($order['delivery_fullname'] !== '-') {
          $query = $this->database->select('queue', 'q');
          $query->condition('name', 'get_orders_from_baselinker');
          $query->condition('data', serialize($order));
          $query->fields('q', ['item_id']);

          // Skip same queue items.
          if (empty($query->execute()->fetchAll())) {
            $this->getOrderQueue->createItem($order);
          }
        }
      }
    }

  }

  /**
   * Sync products to BaseLinker API.
   */
  public function addProduct() {
    // BaseLinker API credentials.
    $data = [];
    $data['token'] = $this->config->get('token');
    $data['connected_domain'] = $this->config->get('connected_domain');

    // Get products that we want to integrate in BaseLinker.
    $products = $this->entityTypeManager
      ->getStorage('commerce_product')
      ->loadMultiple();

    // Send request to BaseLinker.
    foreach ($products as $product) {
      $data['product'] = $product;

      $params = [
        'storage_id' => '4721',
        'product_id' => $data['product']->id(),
      ];

      try {
        $response = $this->httpClient->request('post', $data['connected_domain'], [
          'headers' => [
            'Content-Type' => 'application/x-www-form-urlencoded',
            'X-BLToken' => $data['token'],
          ],
          'form_params' => [
            'method' => 'addProduct',
            'parameters' => $params,
          ]
        ]);
      }
      catch (RequeueException $e) {
        $this->logger->debug('BaseLinker request failed with the message: @message', [
          '@message' => $e->getMessage(),
        ]);
      }

      $response = json_decode($response->getStatusCode());
    }
  }

  /**
   * Request to the connected domain.
   *
   * @param string $method
   *   Which method is used for request.
   * @param array $parameters
   *   Order parameters for request.
   *
   * @return array
   *   Return items.
   */
  public function getItems(string $method, array $parameters = []): array {
    $token = $this->config->get('token');
    $connected_domain = $this->config->get('connected_domain');

    $base_parameters = [];
    if ($method === self::GET_ORDERS_METHOD) {
      $base_parameters = [
        'get_unconfirmed_orders' => FALSE,
      ];
    }
    $parameters = array_merge($base_parameters, $parameters);

    try {
      $response = $this->httpClient->request('post', $connected_domain, [
        'headers' => [
          'Content-Type' => 'application/x-www-form-urlencoded',
          'X-BLToken' => $token,
        ],
        'form_params' => [
          'method' => $method,
          'parameters' => json_encode($parameters),
        ],
        'verify' => FALSE,
        'http_errors' => FALSE,
      ]);
    }
    catch (RequestException $e) {
      $this->logger->debug('BaseLinker request failed with the message: @message', [
        '@message' => $e->getMessage(),
      ]);
    }

    $this->logger->debug('BaseLinker API response: <pre>@body</pre>', [
      '@body' => $response ? $response->getBody()->getContents() : '',
    ]);

    $items = json_decode($response->getBody(), TRUE);

    return $items ?: [];
  }

  /**
   * Generate PDF file.
   *
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   Order Interface.
   * @param bool $is_html
   *   Html.
   * @param bool $return_with_file
   *   Return with file.
   *
   * @return string|\Symfony\Component\HttpFoundation\Response|null
   *   Retrun response.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Theme\MissingThemeDependencyException
   * @throws \GuzzleHttp\Exception\GuzzleException
   */
  public function generatePdf(OrderInterface $order, bool $is_html = FALSE, bool $return_with_file = FALSE) {
    $renderer = $this->renderer;
    $theme = $this->themeHandler->getDefault();

    $theme_path = $this->extensionPathResolver->getPath('theme', $theme);
    $theme_info = $this->infoParser->parse("$theme_path/$theme.info.yml");
    $pdf_title = $this->t('Baselinker');

    $applying_guidelines = NULL;

    $order_items = [];

    foreach ($order->getItems() as $order_item) {
      $title = $order_item->get('title')->value;
      $sku = substr($title, strpos($title, ":") + 1);
      $shipping = $this->entityTypeManager->getStorage('commerce_shipment')
        ->load($order->get('shipments')[0]->target_id);

      // Get asin from product variation.
      $product = $this->entityTypeManager->getStorage('commerce_product')->load($order_item->getPurchasedEntityId());

      if ($product == NULL) {
        continue;
      }

      $order_items[] = [
        'quantity' => $order_item->get('quantity')->value,
        'title' => $title,
        'price' => $order_item->get('total_price')->number,
        'sku' => $sku,
        'shipping' => $shipping->get('amount')[0]->number,
      ];
    }

    $save_order = [
      'delivery_fullname' => $order->getBillingProfile()->get('address')[0]->given_name . ' ' . $order->getBillingProfile()->get('address')->family_name,
      'delivery_address' => $order->getBillingProfile()->get('address')->address_line1,
      'delivery_postal_code' => $order->getBillingProfile()->get('address')->postal_code,
      'delivery_city' => $order->getBillingProfile()->get('address')[0]->locality,
      'delivery_country' => $order->getBillingProfile()->get('address')[0]->country_code,
      'order_number' => $order->getOrderNumber(),
      'date_add' => date('D., d.M.Y', $order->get('created')->value),
      'order_items' => $order_items,
    ];

    $currency = $this->entityTypeManager->getStorage('commerce_currency')->load($order->getTotalPrice()->getCurrencyCode());

    $build = [
      '#theme' => 'belmilb2b_baselinker_pdf',
      '#order_total_paid' => $order->getTotalPrice()->getNumber(),
      '#pdf_title' => $pdf_title,
      '#title' => 'test',
      '#applying_guidelines' => $applying_guidelines,
      '#order' => $save_order,
      '#attached' => [
        'library' => $theme_info['libraries'],
      ],
      '#currency' => $currency->getSymbol(),
    ];

    $logo = $this->themeInitialization->getActiveThemeByName($theme)
      ->getLogo();
    if (strpos($logo, '.svg')) {
      $logo = str_replace('.svg', '.png', $logo);
    }

    $build['#site_logo'] = $logo;

    $context = new RenderContext();

    $css_assets = $this->assetResolver->getCssAssets(AttachedAssets::createFromRenderArray($build), TRUE);
    $rendered_css = $this->cssCollectionRenderer->render($css_assets);

    /** @var \Drupal\Core\Cache\CacheableDependencyInterface $rendered_css_build */
    $rendered_css_build = $this->renderer->executeInRenderContext($context, function () use ($rendered_css, $renderer) {
      return $renderer->render($rendered_css);
    });

    if (!$context->isEmpty()) {
      $bubbleable_metadata = $context->pop();
      BubbleableMetadata::createFromObject($rendered_css_build)
        ->merge($bubbleable_metadata);
    }

    $build['#rendered_css'] = $rendered_css_build;

    /** @var \Drupal\Core\Cache\CacheableDependencyInterface $html */
    $html = $this->renderer->executeInRenderContext($context, function () use ($build, $renderer) {
      return $renderer->render($build);
    });

    if (!$context->isEmpty()) {
      $bubbleable_metadata = $context->pop();
      BubbleableMetadata::createFromObject($html)
        ->merge($bubbleable_metadata);
    }
    $host = $this->request->getSchemeAndHttpHost();

    if (!$is_html && strpos($host, 'docker') !== FALSE) {
      $host = 'http://nginx';
    }

    $media_string = '<link rel="stylesheet" media="all" href=""';
    $css_string = '<link rel="stylesheet" type="text/css" href=""';

    $html = str_replace('<img src=""', '<img src=""' . $host, $html);
    $html = str_replace($media_string, $media_string . $host, $html);
    $html = str_replace($css_string, $css_string . $host, $html);

    if (!$is_html) {
      $destination_dir = 'public://baselinker_order_pdf';
      $destination = $destination_dir . '/baselinker-pdf- ' . $save_order['order_number'] . '.pdf';
      $this->fileSystem->prepareDirectory($destination_dir, FileSystemInterface::CREATE_DIRECTORY || FileSystemInterface::MODIFY_PERMISSIONS);

      $pdf_data = $this->httpClient
        ->request('POST', $this->athenapdfConfig->get('endpoint') . '?auth=' . $this->athenapdfConfig->get('auth_key') . '&ext=html', [
          RequestOptions::MULTIPART => [
            [
              'name' => 'file',
              'contents' => $html,
              'filename' => 'input.html',
              'headers' => [
                'Content-Type' => 'text/html; charset=utf-8',
              ],
            ],

          ],

          RequestOptions::SINK => $destination,
        ])->getBody()->getContents();

      $pdf_file = $this->fileSystem
        ->saveData($pdf_data, $destination, FileSystemInterface::EXISTS_REPLACE);

      $order->field_documents->target_id = $pdf_file;
      $order->save();

      if ($return_with_file) {
        return $pdf_file ? $this->fileUrlGenerator->generateAbsoluteString($pdf_file) : NULL;
      }
      else {
        $this->setPDFTitle($destination, $pdf_title);

        $response = new Response();
        $response->headers->set('Content-Type', 'application/pdf');
        $response->headers->set('Content-Disposition', 'inline; filename="' . $pdf_title . '.pdf"');
        $response->setContent($pdf_data);

        return $response;
      }
    }
    else {
      return new Response($html);
    }

    throw new NotFoundHttpException();

  }

  /**
   * Set PDF title.
   *
   * @param string $outputFilePath
   *   Output file path.
   * @param string $title
   *   Title.
   */
  protected function setPdfTitle(string $outputFilePath, string $title) {
    try {
      $mpdf = new Mpdf(['tempDir' => $this->fileSystem->getTempDirectory()]);
      $page_count = $mpdf->setSourceFile($outputFilePath);
      for ($i = 1; $i <= $page_count; $i++) {
        $import_page = $mpdf->ImportPage($i);
        $mpdf->UseTemplate($import_page);

        if ($i < $page_count) {
          $mpdf->AddPage();
        }
      }

      $mpdf->setTitle($title);
      $mpdf->Output($outputFilePath, Destination::FILE);
    }
    catch (\Exception $e) {
      // Do nothing.
    }
  }

}
<?php

namespace Drupal\bb2b_baselinker\Plugin\QueueWorker;

use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Queue\QueueWorkerBase;
use Drupal\Core\Queue\RequeueException;
use Drupal\Tests\Core\Logger\LoggerChannelFactoryTest;
use GuzzleHttp\Client;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Defines 'bb2b_baselinker_addproduct' queue worker.
 *
 * @QueueWorker(
 *   id = "bb2b_baselinker_addproduct",
 *   title = @Translation("AddProduct"),
 *   cron = {"time" = 60}
 * )
 */
class AddProductQueue extends QueueWorkerBase {

  /**
   * The http client.
   *
   * @var \GuzzleHttp\Client
   */
  protected $httpClient;

  /**
   * The logger.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $logger;

  public function __construct(
    array $configuration, $plugin_id,
    $plugin_definition,
    Client $httpClient,
    LoggerChannelFactoryInterface $logger_factory
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->httpClient = $httpClient;
    $this->logger = $logger_factory->get('bb2b_baselinker');
  }

  public static function create (
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition
  ) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('http_client'),
      $container->get('logger.factory')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function processItem($data) {
    // @todo Process data here.
    $params = [
      'storage_id' => '4721',
      'product_id' => $data['product']->id(),
      'ean' => $data['product']->get('ean')->value,
    ];

    try {
      $response = $this->httpClient->request('post', $data['connected_domain'], [
        'headers' => [
          'Content-Type' => 'application/x-www-form-urlencoded',
          'X-BLToken' => $data['token'],
        ],
        'form_params' => [
          'method' => 'addProduct',
          'parameters' => $params,
        ]
      ]);
    }
    catch (RequeueException $e) {
      $this->logger->debug('BaseLinker request failed with the message: @message', [
        '@message' => $e->getMessage(),
      ]);
    }

    $response = json_decode($response->getStatusCode());
    return $response;

  }

}
<?php

/**
 * @file
 * BaseLinker module functions.
 */

/**
 * Implements hook_cron().
 */
function bb2b_baselinker_cron() {
  /** @var \Drupal\bb2b_baselinker\SyncService $sync_service */
  $sync_service = \Drupal::service('bb2b_baselinker.sync_service');
  $sync_service->getOrders();
  $sync_service->addProduct();
}

/**
 * Implements hook_theme().
 */
function bb2b_baselinker_theme($existing, $type, $theme, $path) {
  return [
    'belmilb2b_baselinker_pdf' => [
      'variables' => [
        'title' => '',
        'order' => [],
        'order_total_paid' => '',
        'currency' => '',
      ],
    ],
  ];
}
<?php

namespace Drupal\bb2b_order\Plugin\views\filter;

use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\filter\FilterPluginBase;

/**
 * Filter products by stock status.
 *
 * @ingroup views_filter_handlers
 *
 * @ViewsFilter("stock_status")
 */
class StockStatus extends FilterPluginBase {

  /**
   * {@inheritDoc}
   */
  public function acceptExposedInput($input) {
    return TRUE;
  }

  /**
   * {@inheritDoc}
   */
  public function buildExposedForm(&$form, FormStateInterface $form_state) {
    if (empty($this->options['exposed'])) {
      return;
    }
    $identifier = $this->options['expose']['identifier'];
    $exposed_input = $this->view->getExposedInput();
    $options = [
      NULL => t('Show all products'),
      'only_available' => t('Show only available products'),
    ];

    $form[$identifier] = [
      '#type' => 'select',
      '#title' => t('Stock status'),
      '#options' => $options,
      '#default_value' => $exposed_input,
    ];
    return $form;
  }

  /**
   * {@inheritDoc}
   */
  public function query() {
    /** @var \Drupal\views\Plugin\views\query\Sql $query */
    $query = $this->query;
    $stock_status = $this->view->getExposedInput();
    if (isset($stock_status['stock_status']) && $stock_status['stock_status'] === 'only_available') {
      $query->addWhereExpression($this->options['group'], "commerce_product_field_data.stock > 0");
    }
  }

}
<?php

namespace Drupal\bb2b_order;

use Drupal\bb2b_company\CompanyInterface;
use Drupal\commerce_order\Adjustment;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_price\Price;
use Drupal\commerce_product\Entity\ProductInterface;
use Drupal\commerce_product\Entity\ProductVariationInterface;
use Drupal\commerce_shipping\Entity\ShipmentInterface;
use Drupal\commerce_shipping\Entity\ShippingMethod;
use Drupal\commerce_shipping\Entity\ShippingMethodInterface;
use Drupal\commerce_shipping\ShippingOrderManagerInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\paragraphs\ParagraphInterface;
use Drupal\profile\Entity\ProfileInterface;

/**
 * Class Order Service.
 */
class OrderService implements OrderServiceInterface {

  use StringTranslationTrait;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The messenger.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * The cache.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cache;

  /**
   * The shipping order manager.
   *
   * @var \Drupal\commerce_shipping\ShippingOrderManagerInterface
   */
  protected $shippingOrderManager;

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;

  /**
   * The private temp store.
   *
   * @var \Drupal\Core\TempStore\PrivateTempStore
   */
  protected $tempStore;

  /**
   * The Base Linker config.
   *
   * @var \Drupal\Core\Config\Config
   */
  protected $baseLinkerConfig;

  /**
   * Constructs a new OrderService object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
   *   The cache.
   * @param \Drupal\commerce_shipping\ShippingOrderManagerInterface $shipping_order_manager
   *   The shipping order manager.
   * @param \Drupal\Core\Database\Connection $database
   *   The database connection.
   * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
   *   The tempstore factory.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The configuration factory.
   */
  public function __construct(
    EntityTypeManagerInterface $entity_type_manager,
    MessengerInterface $messenger,
    CacheBackendInterface $cache,
    ShippingOrderManagerInterface $shipping_order_manager,
    Connection $database,
    PrivateTempStoreFactory $temp_store_factory,
    ConfigFactoryInterface $config_factory
  ) {
    $this->entityTypeManager = $entity_type_manager;
    $this->messenger = $messenger;
    $this->cache = $cache;
    $this->shippingOrderManager = $shipping_order_manager;
    $this->database = $database;
    $this->tempStore = $temp_store_factory->get('bb2b_order');
    $this->baseLinkerConfig = $config_factory->get('bb2b_baselinker.settings');
  }

  /**
   * {@inheritdoc}
   */
  public function getApplicableWarehouse(ProductInterface $product) {
    $cid = 'get_applicable_warehouse_' . $product->id();
    $cache_data = $this->cache->get($cid);

    if (isset($cache_data->data)) {
      return $cache_data->data;
    }
    else {
      // @todo Logic to check which warehouse will be used in reality.
      $warehouse = $this->entityTypeManager->getStorage('taxonomy_term')->loadByProperties([
        'vid' => 'warehouse',
        'field_warehouse_code' => 'PL',
      ]);

      $warehouse = reset($warehouse);

      $product_warehouses = $product->get('field_warehouse')->referencedEntities();

      $applicable_warehouse = NULL;
      foreach ($product_warehouses as $product_warehouse) {
        $warehouse_target_id = $product_warehouse->get('field_warehouse')->target_id;

        if ($warehouse_target_id == $warehouse->id()) {
          $applicable_warehouse = $product_warehouse;
        }
      }

      if (!empty($applicable_warehouse)) {
        $cache_tags = [
          'commerce_product:' . $product->id(),
          'paragraph:' . $applicable_warehouse->id(),
        ];
        $this->cache->set($cid, $applicable_warehouse, CacheBackendInterface::CACHE_PERMANENT, $cache_tags);
      }

      return $applicable_warehouse;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function setStockOnWarehouse(ParagraphInterface $warehouse, int $stock) {
    if ($stock <= 0) {
      $warehouse->setUnpublished();
    }
    elseif (!$warehouse->isPublished() && $stock > 0) {
      $warehouse->setPublished();
    }

    $warehouse->set('field_available', $stock);
    $warehouse->set('field_last_linker_stock_sync', time());
    $warehouse->save();
  }

  /**
   * {@inheritdoc}
   */
  public function getAvailableStock(ProductInterface $product, ?ParagraphInterface $product_warehouse = NULL) {
    $cid = 'get_available_stock_' . $product->id();
    $cache_data = $this->cache->get($cid);

    if (isset($cache_data->data)) {
      return $cache_data->data;
    }
    else {
      // For regular non-bundle products.
      if ($product->get('field_bundle_product_skus')->isEmpty()) {
        $available_stock = $this->doGetAvailableStock($product, $product_warehouse);
      }
      // For bundle products we need to check all products that are part of the
      // bundle.
      else {
        /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $variation_item */
        $available_stock_for_product = [];

        foreach ($product->get('field_bundle_product_skus')->referencedEntities() as $variation_item) {
          $product_item = $variation_item->getProduct();
          $available_stock_for_product[] = $this->doGetAvailableStock($product_item, $product_warehouse);
        }

        $available_stock = min($available_stock_for_product);
      }

      $cache_tags = [
        'commerce_product:' . $product->id(),
      ];

      if ($product_warehouse !== NULL) {
        $cache_tags[] = 'paragraph:' . $product_warehouse->id();
      }

      $this->cache->set($cid, $available_stock, CacheBackendInterface::CACHE_PERMANENT, $cache_tags);
      return $available_stock;
    }
  }

  /**
   * Gets the available stock value.
   *
   * @param \Drupal\commerce_product\Entity\ProductInterface $product
   *   The product.
   * @param \Drupal\paragraphs\ParagraphInterface|null $product_warehouse
   *   The warehouse.
   *
   * @return int
   *   The available stock.
   */
  protected function doGetAvailableStock(ProductInterface $product, ?ParagraphInterface $product_warehouse = NULL) {
    if (!$product_warehouse) {
      /** @var \Drupal\paragraphs\ParagraphInterface $product_warehouse */
      $product_warehouse = $this->getApplicableWarehouse($product);

      if (!$product_warehouse) {
        return 0;
      }
    }

    $local_stock = (int) $product_warehouse->get('field_available')->value;

    $stock_reserved_belmil_de = (int) $product->get('field_stock_reserved_belmil_de')->value;
    if ($stock_reserved_belmil_de > 0) {
      $local_stock -= $stock_reserved_belmil_de;
    }

    $stock_reserved = (int) $product_warehouse->get('field_reserved')->value;
    if ($stock_reserved > 0) {
      $local_stock -= $stock_reserved;
    }

    return $local_stock > 0 ? $local_stock : 0;
  }

  /**
   * {@inheritdoc}
   */
  public function checkAccessForProduct(ProductInterface $product) {
    $cid = 'access_for_product_' . $product->id();
    $cache_data = $this->cache->get($cid);

    if (isset($cache_data->data)) {
      return $cache_data->data;
    }
    else {
      /** @var \Drupal\paragraphs\ParagraphInterface $warehouse */
      $warehouse = $this->getApplicableWarehouse($product);
      $available = $this->getAvailableStock($product, $warehouse);
      $result = $warehouse && $available > 0;

      $cache_tags = [
        'commerce_product:' . $product->id(),
      ];

      if ($warehouse !== NULL) {
        $cache_tags[] = 'paragraph:' . $warehouse->id();
      }

      $this->cache->set($cid, $result, CacheBackendInterface::CACHE_PERMANENT, $cache_tags);
      return $result;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getCompanyForClient(OrderInterface $order) {
    $customer_id = $order->getData('client_id', $order->getCustomerId());
    if (!$customer_id) {
      return NULL;
    }

    return $this->getCompanyForCustomer($customer_id);
  }

  /**
   * {@inheritdoc}
   */
  public function getCompanyForCustomer($customer_id) {
    $company_storage = $this->entityTypeManager->getStorage('company');
    $company = $company_storage->loadByProperties([
      'employees' => $customer_id,
    ]);
    $company = reset($company);

    if (!$company) {
      return NULL;
    }

    return $company;
  }

  /**
   * {@inheritdoc}
   */
  public function getProductPriceForClient(ProductVariationInterface $variation, OrderInterface $order, CompanyInterface $company = NULL) {
    $price = $variation->getPrice();
    $list_price = $variation->getListPrice();
    $calculated_price = [
      'price' => $price,
      'list_price' => $list_price,
    ];

    if (!$company) {
      return $calculated_price;
    }

    $order_type = $order->get('field_order_type')->value;

    $client_price_groups = $company->get('price_group')->referencedEntities() ?? NULL;
    $with_return = $company->get('auto_generate_return_label')->value;

    if (!$client_price_groups) {
      return $calculated_price;
    }

    $client_price_group_ids = array_map(function ($client_price_group) {
      return $client_price_group->id();
    }, $client_price_groups);

    $product = $variation->getProduct();
    $product_price_groups = $product->get('field_price_group')->referencedEntities();

    $price_groups = array_filter($product_price_groups, function ($product_price_group) use ($client_price_group_ids) {
      $price_group_terms = $product_price_group->get('field_price_group')->referencedEntities();

      /** @var \Drupal\taxonomy\Entity\Term $price_group_term */
      $price_group_term = reset($price_group_terms);

      return $price_group_term && in_array($price_group_term->id(), $client_price_group_ids);
    });

    if (!$price_groups) {
      return $calculated_price;
    }

    $price_group_price = 0;
    foreach ($price_groups as $price_group) {
      if ($order_type === 'ddp' || $order_type === 'ddp_reservation') {
        $price_group_price = $price_group->get('field_ddp')->value;
      }
      elseif ($order_type === 'drop_shipping' && !$with_return) {
        $price_group_price = $price_group->get('field_dsnr')->value;
      }
      elseif ($order_type === 'drop_shipping' && $with_return) {
        $price_group_price = $price_group->get('field_dswr')->value;
      }

      if ($price_group_price > 0) {
        break;
      }
    }

    if ($price_group_price == 0) {
      return $calculated_price;
    }

    $currency = $price->getCurrencyCode();

    $calculated_price['price'] = new Price((string) $price_group_price, $currency);
    if ($list_price) {
      $calculated_price['list_price'] = new Price((string) $price_group_price, $currency);
    }

    return $calculated_price;
  }

  /**
   * {@inheritdoc}
   */
  public function packageMinQuantityIsValid(OrderInterface $order): bool {
    return $this->getNotFullPackageCount($order) === 0;
  }

  /**
   * {@inheritdoc}
   */
  public function getNotFullPackageCount(OrderInterface $order): int {
    $order_type = $order->get('field_order_type')->value;
    $not_valid_packages = 0;

    if ($order_type === 'drop_shipping') {
      return 0;
    }

    // Sum order item quantities by Tag.
    $quantities = $this->getOrderedQuantitiesByTag($order->getItems());

    if (!$quantities) {
      return 1;
    }

    $shipment_packages = $this->entityTypeManager
      ->getStorage('shipment_package')
      ->loadMultiple();

    // Check min quantities.
    foreach ($shipment_packages as $shipment_package) {
      $ordered_quantity = 0;
      $min_order_quantity = (int) $shipment_package->get('max_item_per_package')->value;
      $articles = $shipment_package->get('article_no')->referencedEntities();
      foreach ($articles as $article) {
        if (isset($quantities[$article->id()])) {
          $ordered_quantity += $quantities[$article->id()];
        }
      }

      if ($ordered_quantity > 0 &&
        (!$min_order_quantity
        || $ordered_quantity < $min_order_quantity
        || $ordered_quantity % $min_order_quantity != 0)
      ) {
        $not_valid_packages++;
      }
    }

    return $not_valid_packages;
  }

  /**
   * {@inheritdoc}
   */
  public function firstPackageIsNotFull(OrderInterface $order): bool {
    $order_type = $order->get('field_order_type')->value;
    if ($order_type === 'drop_shipping') {
      return FALSE;
    }

    // Sum order item quantities by Tag.
    $quantities = $this->getOrderedQuantitiesByTag($order->getItems());

    return $this->minQuantityIsNotValid($quantities);
  }

  /**
   * Check ordered quantity.
   *
   * @param array $quantities
   *   The ordered quantities.
   *
   * @return bool
   *   Return TRUE if minimum ordered quantity is not valid.
   */
  public function minQuantityIsNotValid(array $quantities): bool {
    $shipment_packages = $this->entityTypeManager
      ->getStorage('shipment_package')
      ->loadMultiple();

    // Check min quantities.
    foreach ($shipment_packages as $shipment_package) {
      $ordered_quantity = 0;
      $min_order_quantity = (int) $shipment_package->get('max_item_per_package')->value;
      $articles = $shipment_package->get('article_no')->referencedEntities();
      foreach ($articles as $article) {
        if (isset($quantities[$article->id()])) {
          $ordered_quantity += $quantities[$article->id()];
        }
      }

      if ($ordered_quantity > 0 && $ordered_quantity < $min_order_quantity) {
        return TRUE;
      }
    }

    return FALSE;
  }

  /**
   * Gets the order item quantities by tag.
   *
   * @param \Drupal\commerce_order\Entity\OrderItemInterface[] $order_items
   *   The order items.
   */
  public function getOrderedQuantitiesByTag(array $order_items): array {
    $quantities = [];

    foreach ($order_items as $order_item) {
      /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $purchased_entity */
      $purchased_entity = $order_item->getPurchasedEntity();
      if (!$purchased_entity || $purchased_entity->getEntityTypeId() != 'commerce_product_variation') {
        continue;
      }

      /** @var \Drupal\commerce_product\Entity\ProductInterface $product */
      $product = $purchased_entity->getProduct();

      $tag = $product->get('field_tag')->referencedEntities();
      $tag = reset($tag);

      if (!$tag) {
        return [];
      }

      $tag_id = $tag->id();

      if (isset($quantities[$tag_id])) {
        $quantities[$tag_id] += (int) $order_item->getQuantity();
      }
      else {
        $quantities[$tag_id] = (int) $order_item->getQuantity();
      }
    }

    return $quantities;
  }

  /**
   * {@inheritdoc}
   */
  public function getMinOrderQuantity(ProductInterface $product) {
    $min_order_quantity = 1;

    $tag = $product->get('field_tag')->referencedEntities();
    $tag = reset($tag);

    if ($tag) {
      $shipment_packages = $this->entityTypeManager
        ->getStorage('shipment_package')
        ->loadMultiple();

      $package = array_filter($shipment_packages, function ($shipment_package) use ($tag) {
        $terms = $shipment_package->get('article_no')->referencedEntities();

        $term_ids = array_map(function ($term) {
          return $term->id();
        }, $terms);

        return in_array($tag->id(), $term_ids);
      });

      $package = reset($package);

      $min_order_quantity = $package ? $package->get('max_item_per_package')->value : 1;
    }

    return $min_order_quantity;
  }

  /**
   * Resolve shipping for order.
   *
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   The order.
   * @param \Drupal\profile\Entity\ProfileInterface|null $billing_profile
   *   The billing profile.
   * @param \Drupal\profile\Entity\ProfileInterface|null $shipping_profile
   *   The shipping profile.
   * @param string|null $shipping_country
   *   The shipping country.
   */
  public function resolveShipping(
    OrderInterface $order,
    ?ProfileInterface $billing_profile = NULL,
    ?ProfileInterface $shipping_profile = NULL,
    ?string $shipping_country = NULL
  ) {

    if (!$billing_profile) {
      $billing_profile = $order->getBillingProfile();
    }

    if (!$shipping_profile) {
      $shipping_profile = $order->collectProfiles()['shipping'] ?? NULL;
    }

    if (!$shipping_profile && $billing_profile) {
      $shipping_profile = $billing_profile->createDuplicate();
      $shipping_profile->unsetData('copy_to_address_book');
    }

    if (!$billing_profile && $shipping_profile) {
      $billing_profile = $shipping_profile->createDuplicate();
      $billing_profile->unsetData('copy_to_address_book');
    }

    if (!$billing_profile || !$shipping_profile) {
      return;
    }

    $shipments = $this->shippingOrderManager->pack($order, $shipping_profile);

    $store_key = 'shipping_force_resolve:' . $order->id();
    $shipping_force_resolve = $this->tempStore->get($store_key);
    $this->tempStore->delete($store_key);

    $shipping_country = $shipping_country ?: $shipping_profile
      ->get('address')->country_code;
    $shipping_method = $this->getShippingMethodForOrder($order, $billing_profile, $shipping_country);

    if ($shipping_method) {
      $shipping_adjustments = $order->getAdjustments(['shipping']);
      foreach ($shipping_adjustments as $adjustment) {
        $order->get('adjustments')->removeAdjustment($adjustment);
      }

      $adjustments = $order->getAdjustments();

      /** @var \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment */
      foreach ($shipments as $key => $shipment) {
        $this->setShippingMethodForShipment($order, $shipment, $shipping_method, $key, $adjustments);
        if ($shipping_force_resolve) {
          $shipment->save();
        }
      }

      $order->set('adjustments', $adjustments);
    }

    $order->set('shipments', $shipments);
  }

  /**
   * Get shipping method for order.
   *
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   The order.
   * @param \Drupal\profile\Entity\ProfileInterface|null $profile
   *   The profile.
   * @param string|null $shipping_country
   *   The shipping country.
   *
   * @return \Drupal\commerce_shipping\Entity\ShippingMethodInterface|null
   *   The shipping method.
   */
  public function getShippingMethodForOrder(OrderInterface $order, ProfileInterface $profile, ?string $shipping_country = NULL) {
    $shipping_method = NULL;

    /** @var \Drupal\commerce_shipping\Entity\ShippingMethodInterface[] $shipping_methods */
    $shipping_methods = ShippingMethod::loadMultiple();
    $shipping_methods = array_filter($shipping_methods, function ($item) {
      return $item->getPlugin()->getPluginId() == 'belmil_shipping' && $item->isEnabled();
    });

    usort($shipping_methods, function ($a, $b) {
      return $a->getWeight() <=> $b->getWeight();
    });

    $time_now = DrupalDateTime::createFromTimestamp(time());
    $time_now->setTimezone(new \DateTimeZone('Europe/Belgrade'));
    // Saturday is also working day. Only sunday is weekend.
    $is_weekend = $time_now->format('N') == 7;

    $order_type = $order->get('field_order_type')->value;
    $order_source = $order->get('field_source')->value;
    $belmil_de_shipping_method = NULL;
    if ($order_source === 'belmilde') {
      $belmil_de_shipping_method = $order->getData('belmil_de_shipping_method', 'standard');
    }

    $amazon_shipping_method = NULL;
    if ($order_source === 'amazon') {
      $amazon_shipping_method = $order->getData('amazon_shipping_method', 'Std DE Dom_3');
    }

    $packstation_address = $profile->hasField('field_packstation_address') &&
      $profile->get('field_packstation_address')->value;

    $packstation_postnumber = $profile->hasField('field_packstation_postnumber') ?
      $profile->get('field_packstation_postnumber')->value : NULL;

    $shipping_profile_country = $shipping_country ?: $profile->get('address')->country_code;

    $shipping_with_packstation = $shipping_profile_country === 'DE' &&
      $packstation_address && $packstation_postnumber;

    foreach ($shipping_methods as $shipping_method_entity) {
      $method_configuration = $shipping_method_entity->getPlugin()
        ->getConfiguration();
      $method_countries = $method_configuration['belmil'] ?? [];

      // Set amazon related shipping method.
      if ($order_source == 'amazon') {
        // DE Standard or Express.
        if ($shipping_method_entity->label() == 'DEDHL' &&
          in_array($amazon_shipping_method, ['Std DE Dom_3', 'Exp DE Dom_1'])
        ) {
          $shipping_method = $shipping_method_entity;
          break;
        }
        // International EU.
        elseif ($shipping_method_entity->label() == 'DHL DE INTERNATIONAL' &&
          $amazon_shipping_method == 'Std DE Intl_2'
        ) {
          $shipping_method = $shipping_method_entity;
          break;
        }
      }

      // DHL Packstation address.
      if ($shipping_with_packstation &&
        isset($method_configuration['address']) &&
        $method_configuration['address']['packstation']
      ) {
        foreach ($method_countries as $country_code => $country_config) {
          if ($shipping_profile_country == $country_code &&
            !empty($country_config['enabled'])
          ) {
            $shipping_method = $shipping_method_entity;
            break 2;
          }
        }
      }

      // Check store condition.
      if (!in_array($order->getStoreId(), $shipping_method_entity->getStoreIds())) {
        continue;
      }

      // Order total conditions check.
      $conditions = $shipping_method_entity->getConditions();
      if ($conditions) {
        /** @var \Drupal\commerce\Plugin\Commerce\Condition\ConditionInterface[] $order_total_conditions */
        $order_total_conditions = array_filter($conditions, function ($condition) {
          /** @var \Drupal\commerce\Plugin\Commerce\Condition\ConditionInterface $condition */
          return $condition->getEntityTypeId() == 'commerce_order' &&
            $condition->getPluginId() === 'order_total_price';
        });
        $order_total_condition = reset($order_total_conditions);
        if ($order_total_condition && !$order_total_condition->evaluate($order)) {
          continue;
        }
      }

      if (
        isset($method_configuration['belmil']) &&
        isset($method_configuration['order_types']) &&
        isset($method_configuration['time']) &&
        !$shipping_with_packstation
      ) {
        foreach ($method_countries as $country_code => $country_config) {
          if (
            $shipping_profile_country == $country_code &&
            !empty($country_config['enabled']) &&
            !empty($method_configuration['order_types'][$order_type])
          ) {
            // Check time conditions for weekend.
            if ($is_weekend === TRUE) {
              if (!empty($method_configuration['time']['use_on_weekends']) && !empty($method_configuration['time']['use_on_weekends_type'])) {
                $weekends_time_until = new DrupalDateTime($method_configuration['time']['weekends_time_until']);
                $weekends_time_after = new DrupalDateTime($method_configuration['time']['weekends_time_after']);

                // For DDP orders we don't have to check time conditions.
                $ignore_time_conditions_for_ddp = !empty($method_configuration['time']['ignore_for_ddp']);
                if ($ignore_time_conditions_for_ddp === TRUE &&
                  in_array($order_type, ['ddp', 'ddp_reservation'])
                ) {
                  $shipping_method = $shipping_method_entity;
                  break 2;
                }

                // Check if the current time is in range for non-ddp orders.
                if ($method_configuration['time']['use_on_weekends_type'] == 'included') {
                  if ($time_now >= $weekends_time_until && $time_now <= $weekends_time_after) {
                    $shipping_method = $shipping_method_entity;
                    break 2;
                  }
                }
                elseif ($method_configuration['time']['use_on_weekends_type'] == 'excluded') {
                  if ($time_now < $weekends_time_until || $time_now > $weekends_time_after) {
                    $shipping_method = $shipping_method_entity;
                    break 2;
                  }
                }
              }
            }
            // Check time conditions for weekdays.
            else {
              if (!empty($method_configuration['time']['use_on_workdays']) && !empty($method_configuration['time']['use_on_workdays_type'])) {
                $workdays_time_until = new DrupalDateTime($method_configuration['time']['workdays_time_until']);
                $workdays_time_after = new DrupalDateTime($method_configuration['time']['workdays_time_after']);

                // For DDP orders we don't have to check time conditions.
                $ignore_time_conditions_for_ddp = !empty($method_configuration['time']['ignore_for_ddp']);
                if ($ignore_time_conditions_for_ddp === TRUE &&
                  in_array($order_type, ['ddp', 'ddp_reservation'])
                ) {
                  $shipping_method = $shipping_method_entity;
                  break 2;
                }

                // Check if the current time is in range for non-ddp orders.
                if ($method_configuration['time']['use_on_workdays_type'] == 'included') {
                  if ($time_now >= $workdays_time_until && $time_now <= $workdays_time_after) {
                    $shipping_method = $shipping_method_entity;
                    break 2;
                  }
                }
                elseif ($method_configuration['time']['use_on_workdays_type'] == 'excluded') {
                  $excluded = $time_now < $workdays_time_until ||
                    $time_now > $workdays_time_after;
                  if ($excluded ||
                    (!$excluded && $belmil_de_shipping_method === 'standard')
                  ) {
                    $shipping_method = $shipping_method_entity;
                    break 2;
                  }
                }
              }
            }
          }
        }
      }
    }

    return $shipping_method;
  }

  /**
   * Set shipping method for shipment.
   *
   * @param \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment
   *   The shipment.
   * @param \Drupal\commerce_shipping\Entity\ShippingMethodInterface $shipping_method
   *   The shipping method.
   * @param int|null $key
   *   The shipment key.
   * @param \Drupal\commerce_order\Adjustment[] $adjustments
   *   The order adjustments.
   */
  public function setShippingMethodForShipment(OrderInterface $order, ShipmentInterface $shipment, ShippingMethodInterface $shipping_method, ?int $key = 0, array &$adjustments = []) {
    $shipment->setShippingMethod($shipping_method);

    $order_type = $order->get('field_order_type')->value;
    $order_source = $order->get('field_source')->value;
    $your_order_id = $order->get('field_your_order_id')->value;

    $shipping_method_plugin = $shipping_method->getPlugin();
    $shipping_rates = $shipping_method_plugin->calculateRates($shipment);
    $shipping_rate = reset($shipping_rates);
    $shipping_label = $shipping_rate->getService()->getLabel();
    /** @var \Drupal\commerce_price\Price $amount */
    $amount = $shipping_rate->getAmount();

    // Belmil.de express price is 10 EUR.
    if ($order_source === 'belmilde' &&
      $shipping_label === 'DEDHL Prime'
    ) {
      $amount = new Price('10', $amount->getCurrencyCode());
    }

    // DDP is FREE.
    if (in_array($order_type, ['ddp', 'ddp_reservation'])) {
      $amount = new Price('0', $amount->getCurrencyCode());
    }

    // Amazon.
    if ($order_source === 'amazon') {
      $amount_number = 0;
      $amazon_shipping_method = $order->getData('amazon_shipping_method', 'Std DE Dom_3');

      // DE Standard.
      if ($amazon_shipping_method === 'Std DE Dom_3') {
        $amount_number = $this->baseLinkerConfig->get('amazon_standard_de_price');
      }
      // DE Express.
      elseif ($amazon_shipping_method === 'Exp DE Dom_1') {
        $amount_number = $this->baseLinkerConfig->get('amazon_express_de_price');
      }
      // International EU.
      elseif ($amazon_shipping_method === 'Std DE Intl_2') {
        $amount_number = $this->baseLinkerConfig->get('amazon_eu_price');
      }

      $items = count($shipment->getItems());

      $amount = new Price($items * $amount_number, $amount->getCurrencyCode());
    }

    $shipment->setAmount($amount);

    $shipping_service = $shipping_rate->getService()->getId();
    $shipment->setShippingService($shipping_service);

    $shipment_number = str_pad(($key + 1), 2, '0', STR_PAD_LEFT);
    $shipment_number = $your_order_id ?
      $shipment_number . ' #' . $your_order_id : $shipment_number;

    $shipment_title = $this->t('Shipping for package @order_id', [
      '@order_id' => $shipment_number,
    ]);

    $shipment->setTitle($shipment_title);
    // In case there are some changes on placed order that are
    // affecting shipment information.
    if (!empty($order->get('placed')->value)) {
      $shipment->save();
    }

    $cloned_adjustment = NULL;
    foreach ($adjustments as $key => $adjustment) {
      // Edit existing shipment amount.
      if ($adjustment->getSourceId() == $shipment->id()) {
        $cloned_adjustment = $adjustment->toArray();
        $cloned_adjustment['amount'] = $amount;
        unset($adjustments[$key]);
        $order->get('adjustments')->removeAdjustment($adjustment);
        $adjustments[] = new Adjustment($cloned_adjustment);
      }
    }

    if (!$cloned_adjustment) {
      $adjustments[] = new Adjustment([
        'type' => 'shipping',
        'label' => $shipment_title,
        'amount' => $amount,
        'source_id' => $shipment->id(),
      ]);
    }
  }

  /**
   * Set surcharges on order.
   *
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   The order.
   */
  public function setOrderSurcharges(OrderInterface $order) {
    $adjustments = $order->getAdjustments(['custom']);
    foreach ($adjustments as $adjustment) {
      $order->get('adjustments')->removeAdjustment($adjustment);
    }

    $currency = $order->getTotalPrice()->getCurrencyCode();
    $not_full_package_cnt = $this->getNotFullPackageCount($order);

    // Convert back to while if we need more surcharge.
    if ($not_full_package_cnt > 0) {
      $order->addAdjustment(new Adjustment([
        'type' => 'custom',
        'label' => $this->t('Surcharge'),
        'amount' => new Price('4.00', $currency),
        'source_id' => $order->id() . '|' . $not_full_package_cnt,
      ]));

      // $not_full_package_cnt--;
    }
  }

  /**
   * Get bundle products variation ids.
   *
   * @return mixed
   *   The ids.
   */
  public function getBundleProductIds() {
    $query = $this->database->query("SELECT DISTINCT p.field_bundle_product_skus_target_id AS field_bundle_product_skus_target_id
      FROM {commerce_product__field_bundle_product_skus} p
      INNER JOIN {commerce_product_variation_field_data} pv ON pv.variation_id = p.field_bundle_product_skus_target_id
      LEFT OUTER JOIN {commerce_product__field_main_and_bundle_product} mb ON mb.entity_id = pv.product_id
      WHERE (p.deleted = 0) AND ((mb.field_main_and_bundle_product_value IS NULL) OR (mb.field_main_and_bundle_product_value <> 1))");

    return $query->fetchCol();
  }

}
<?php

/**
 * @file
 * Provides a bb2b_order.
 */

use Drupal\block\Entity\Block;
use Drupal\commerce_order\Entity\Order;
use Drupal\commerce_shipping\Entity\ShipmentInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AppendCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Ajax\RedirectCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\bb2b_order\Plugin\Commerce\CheckoutPane\OrderTypePane;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\views\Form\ViewsForm;
use Drupal\views\ViewExecutable;
use Drupal\Core\Entity\EntityInterface;

/**
 * Implements hook_cron().
 */
function bb2b_order_cron() {
  $request_time = \Drupal::time()->getRequestTime();
  $state_key = 'bb2b_order.last_queue_time';
  $last_queue_time = \Drupal::state()->get($state_key, 0);

  // Once daily.
  if ($request_time - $last_queue_time >= (24 * 60 * 60)) {
    \Drupal::service('bb2b_order.order_reservation_cron')->run();

    \Drupal::state()->set($state_key, $request_time);
  }
}

/**
 * Implements hook_form_alter().
 */
function bb2b_order_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Add to cart on product page. This page will be deactivated.
  if (strpos($form_id, 'commerce_order_item_add_to_cart_form') !== FALSE) {
    array_unshift($form['#validate'], 'validate_available_quantity');
  }

  // Cart page.
  if (strpos($form_id, 'views_form_commerce_cart_form_') !== FALSE) {
    array_unshift($form['#validate'], 'validate_min_quantity');
  }

  // Quick order page.
  if ($form_state->getFormObject() instanceof ViewsForm) {
    /** @var \Drupal\views\ViewExecutable $view */
    $view = reset($form_state->getBuildInfo()['args']);

    if ($view->storage->get('id') == 'quick_order' && !empty($view->result)) {
      $order_id = \Drupal::service('commerce_cart.cart_provider')->getCartIds(\Drupal::currentUser());
      $order_id = reset($order_id);
      $order = \Drupal::entityTypeManager()->getStorage('commerce_order')
        ->load($order_id);

      $form['ddp_package_list'] = [
        '#type' => 'details',
        '#title' => t('At DDP orders, every box must be filled with the MOQ / box quantity.'),
        '#weight' => -2,
        '#open' => FALSE,
      ];

      $form['ddp_package_list']['block'] = [
        '#type' => 'container',
        'block' => [
          '#markup' => bb2b_order_render_shipment_package_block(),
        ],
      ];

      $name = 'field_order_type';
      /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
      $form_display = \Drupal::entityTypeManager()->getStorage('entity_form_display')
        ->load('commerce_order.default.default');

      $widget = $form_display->getRenderer($name);

      $roles = \Drupal::currentUser()->getRoles();
      $is_admin = in_array('administrator', $roles) || in_array('site_administrator', $roles);

      if ($widget) {
        $items = $order->get($name);
        $items->filterEmptyItems();

        $store = \Drupal::service('commerce_store.current_store')->getStore();
        $cart_provider = \Drupal::service('commerce_cart.cart_provider');
        $cart = $cart_provider->getCart('default', $store);

        if (!$cart) {
          $cart = $cart_provider->createCart('default', $store);
        }

        $order_type = $cart->get('field_order_type')->value;

        $form[$name] = $widget->form($items, $form, $form_state);
        $form[$name]['#access'] = $items->access('edit');
        $form[$name]['#required'] = TRUE;
        $form[$name]['#weight'] = -1;
        $form[$name]['widget']['#default_value'] = $order_type ?? 'ddp';
        $form[$name]['widget']['#ajax'] = [
          'callback' => 'bb2b_order_update_min_values',
        ];

        if ($is_admin) {
          unset($form[$name]['widget']['#options']['ddp_reservation']);
        }
        else {
          $form[$name]['widget']['ddp_reservation'] = [
            '#description' => t('You have 5 days to pay the order, otherwise it will be canceled.'),
          ];
        }
      }

      $options = [];
      $companies = \Drupal::entityTypeManager()
        ->getStorage('company')
        ->loadMultiple();

      foreach ($companies as $company) {
        if ($employee = $company->getEmployeeByRole('company_manager')) {
          $options[$employee->id()] = $employee->getDisplayName() . ' (' . $company->getName() . ')';
        }
      }

      $form['client'] = [
        '#type' => 'select2',
        '#id' => 'update-client',
        '#options' => $options,
        '#title' => t('Client'),
        '#required' => FALSE,
        '#weight' => -2,
        '#access' => $is_admin,
        '#ajax' => [
          'callback' => 'bb2b_order_update_client',
        ],
      ];

      if ($client_id = $order->getData('client_id')) {
        $form['client']['#default_value'] = $client_id;
      }

      $form['actions']['checkout'] = [
        '#type' => 'submit',
        '#value' => t('Delivery data'),
        '#weight' => 5,
        '#access' => \Drupal::currentUser()->hasPermission('access checkout'),
        '#submit' => array_merge($form['#submit'], ['bb2b_order_checkout_views_form_submit']),
        '#order_id' => $order_id,
      ];

    }
  }

  if ($form_id == 'commerce_shipping_method_edit_form') {
    $form['plugin']['widget'][0]['target_plugin_configuration']['form']['rate_label']['#attributes'] = ['readonly' => 'readonly'];
    $form['plugin']['widget'][0]['target_plugin_configuration']['form']['rate_label']['#description'] = t('Shown to customers during checkout. This value cannot be changed because we need it to match the name in Linker.');
  }
}

/**
 * Implements hook_views_pre_render().
 */
function bb2b_order_views_pre_render(ViewExecutable $view) {
  if ($view->id() == 'commerce_cart_form') {
    $order_id = $view->args[0];
    /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
    $order = Order::load($order_id);

    /** @var \Drupal\bb2b_order\OrderServiceInterface $order_service */
    $order_service = \Drupal::service('bb2b_order.order_service');

    if (!$order_service->packageMinQuantityIsValid($order)) {
      $view->build_info['substitutions']['{{ admin_shipment_packages_block }}'] = bb2b_order_render_shipment_package_block();
    }
  }
  if ($view->id() === 'quick_order') {
    //@todo: This will go to history when we create stock base field on product.
    //@todo: Query will be in the StockStatus filter.
    $input = $view->getExposedInput();
    if (isset($input['stock_status']) && $input['stock_status'] === 'only_available') {
      // Get products from the view.
      $products = [];
      foreach ($view->result as $key => $product) {
        $products[$key] = $product->_entity;
      }
      // Check if those products are available.
      $order_service = \Drupal::service('bb2b_order.order_service');
      foreach ($products as $key => $product) {
        if (!$order_service->checkAccessForProduct($product))
        unset($view->result[$key]);
      }

    }
  }
}

/**
 * Gets the cart order for the given store and user.
 *
 * @param \Drupal\Core\Session\AccountInterface $account
 *   The user. If empty, the current user is assumed.
 *
 * @return \Drupal\commerce_order\Entity\OrderInterface|null
 *   The cart order, or NULL if none found.
 */
function bb2b_order_get_cart(AccountInterface $account = NULL) {
  $store = \Drupal::service('commerce_store.current_store')->getStore();
  $cart_provider = \Drupal::service('commerce_cart.cart_provider');
  $cart = $cart_provider->getCart('default', $store, $account);

  if (!$cart) {
    $cart = $cart_provider->createCart('default', $store, $account);
  }

  return $cart;
}

/**
 * Ajax handler used to remove min values validation for drop shipping.
 */
function bb2b_order_update_client($form, FormStateInterface $form_state) {
  $response = new AjaxResponse();

  $triggering_element = $form_state->getTriggeringElement();
  $trigger_name = array_shift($triggering_element['#parents']);

  if ($trigger_name === 'client') {
    $client_id = $triggering_element['#value'];
    $cart = bb2b_order_get_cart();

    $user = \Drupal::entityTypeManager()
      ->getStorage('user')
      ->load($client_id);

    $email = $user ? $user->getEmail() : NULL;

    $cart->setData('client_id', $client_id);
    $cart->setData('client_email', $email);
    $cart->save();

    $content = '<div class="ajax-progress-throbber"></div>';
    $response->addCommand(new AppendCommand('body', $content));
    $response->addCommand(new RedirectCommand(Url::fromUri('internal:/orders/quick-order')->toString()));
  }

  return $response;
}

/**
 * Ajax handler used to remove min values validation for drop shipping.
 */
function bb2b_order_update_min_values($form, FormStateInterface $form_state) {
  $response = new AjaxResponse();

  $triggering_element = $form_state->getTriggeringElement();
  $trigger_name = array_shift($triggering_element['#parents']);

  if ($trigger_name === 'field_order_type') {
    $order_type = $triggering_element['#value'];
    $rows = $form['output'][0]['#rows'];

    /** @var \Drupal\bb2b_order\OrderServiceInterface $order_service */
    $order_service = \Drupal::service('bb2b_order.order_service');

    $cart = bb2b_order_get_cart();

    /*if ($order_type !== 'drop_shipping') {
    $cart->setData('client_id', NULL);
    $cart->setData('client_email', NULL);
    }*/

    $cart->set('field_order_type', $order_type);
    $cart->save();

    foreach ($rows as $key => $row) {
      $field_stock = $form['warehouse_stock'][$key];
      $field_quantity = $form['edit_quantity'][$key];
      $field_chk = $form['chk_add_to_cart'][$key];
      $field_price = $form['price_group_price'][$key];
      $add_to_cart_button = $form['add_to_cart_button'][$key];

      /** @var \Drupal\commerce_product\Entity\ProductInterface $product */
      $product = $row->_entity;

      $access = $order_service->checkAccessForProduct($product);
      $available = $order_service->getAvailableStock($product);

      if ($order_type === 'drop_shipping') {
        $class = $field_quantity['#attributes']['class'] ?? [];
        $key = array_search('visually-hidden', $class);
        if ($key !== FALSE && $available > 0) {
          unset($field_quantity['#attributes']['class'][$key]);
        }

        $class = $field_chk['#attributes']['class'] ?? [];
        $key = array_search('visually-hidden', $class);
        if ($key !== FALSE && $available > 0) {
          unset($field_chk['#attributes']['class'][$key]);
        }

        $class = $add_to_cart_button['#attributes']['class'] ?? [];
        $key = array_search('visually-hidden', $class);
        if ($key !== FALSE && $available > 0) {
          unset($add_to_cart_button['#attributes']['class'][$key]);
        }
      }
      else {
        if (!$access || $available == 0) {
          if (!isset($field_quantity['#attributes']['class']['visually-hidden'])) {
            $field_quantity['#attributes']['class'][] = 'visually-hidden';
          }

          if (!isset($field_chk['#attributes']['class']['visually-hidden'])) {
            $field_chk['#attributes']['class'][] = 'visually-hidden';
          }

          $field_chk['#value'] = 0;
          if (!isset($add_to_cart_button['#attributes']['class']['visually-hidden'])) {
            $add_to_cart_button['#attributes']['class'][] = 'visually-hidden';
          }
        }
      }

      $method = $order_type === 'drop_shipping' ? 'addClass' : 'removeClass';
      $response->addCommand(new InvokeCommand('.min-qty', $method, ['visually-hidden']));

      $selector = $field_quantity['#attributes']['data-drupal-selector'];
      $quantity_selector = 'div[class*="' . preg_replace('/edit/', 'form-item', $selector, 1) . '"]';
      $response->addCommand(new ReplaceCommand($quantity_selector, $field_quantity));

      $selector = $field_chk['#attributes']['data-drupal-selector'];
      $chk_selector = 'div[class*="' . preg_replace('/edit/', 'form-item', $selector, 1) . '"]';
      $response->addCommand(new ReplaceCommand($chk_selector, $field_chk));

      $selector = $field_stock['#attributes']['data-drupal-selector'];
      $stock_selector = 'div[id="' . preg_replace('/edit/', 'form-item', $selector, 1) . '"]';
      $response->addCommand(new ReplaceCommand($stock_selector, $field_stock));

      $company = $order_service->getCompanyForClient($cart);
      $price = $order_service->getProductPriceForClient($product->getDefaultVariation(), $cart, $company);
      $field_price['list_price']['#value'] = $price['list_price'];
      $field_price['price']['#value'] = $price['price'];

      $selector = $field_price['#attributes']['data-drupal-selector'];
      $price_selector = 'div[id="' . preg_replace('/edit/', 'form-item', $selector, 1) . '"]';
      $response->addCommand(new ReplaceCommand($price_selector, $field_price));

      $button_selector = 'button[name="' . $add_to_cart_button['#name'] . '"]';
      $response->addCommand(new ReplaceCommand($button_selector, $add_to_cart_button));

      // Cart block.
      /** @var \Drupal\Core\Block\BlockPluginInterface $cart_block */
      $cart_block = \Drupal::service('plugin.manager.block')->createInstance('commerce_cart', []);
      if ($cart_block) {
        $response->addCommand(new ReplaceCommand('.cart--cart-block', $cart_block->build()));
      }
    }
  }

  return $response;
}

/**
 * Submit handler used to redirect to the checkout page.
 */
function bb2b_order_checkout_views_form_submit($form, FormStateInterface $form_state) {
  $order_id = $form_state->getTriggeringElement()['#order_id'];
  $form_state->setRedirect('commerce_checkout.form', ['commerce_order' => $order_id]);
}

/**
 * Form validation handler for commerce_order_item_add_to_cart_form*().
 *
 * @param array $form
 *   A build form array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state.
 */
function validate_available_quantity(array $form, FormStateInterface $form_state) {
  $product = $form_state->get('product');
  $quantity = (int) $form_state->getValue('quantity')[0]['value'];

  /** @var \Drupal\bb2b_order\OrderServiceInterface $order_service */
  $order_service = \Drupal::service('bb2b_order.order_service');

  $available = $order_service->getAvailableStock($product);
  if ($available < $quantity) {
    $form_state->setErrorByName('quantity][0][value', t('There are not enough quantity on the stock. Available: @available', [
      '@available' => $available,
    ]));
  }

}

/**
 * Form validation handler for views_form_commerce_cart_form_*().
 *
 * @param array $form
 *   A build form array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state.
 */
function validate_min_quantity(array $form, FormStateInterface $form_state) {
  $quantity = $form_state->getValue('edit_quantity');
  $rows = $form['output'][0]['#rows'];

  $triggering_element = $form_state->getTriggeringElement();
  $trigger_name = array_shift($triggering_element['#parents']);

  if ($trigger_name !== 'remove_button') {
    foreach ($rows as $key => $row) {
      /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $product_variation */
      $product_variation = $row->_relationship_entities['commerce_product_variation'];

      /** @var \Drupal\commerce_product\Entity\ProductInterface $product */
      $product = $product_variation->getProduct();

      /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
      $order = $row->_entity;

      /** @var \Drupal\bb2b_order\OrderServiceInterface $order_service */
      $order_service = \Drupal::service('bb2b_order.order_service');

      $available = $order_service->getAvailableStock($product);

      foreach ($order->getItems() as $order_item) {
        if ($order_item->getPurchasedEntityId() == $product_variation->id()) {
          $order_quantity = (int) $order_item->getQuantity();
          $diff_quantity = $quantity[$key] - $order_quantity;

          if ($available < $diff_quantity || ($diff_quantity == 0 && $available < $order_quantity)) {
            $form_state->setErrorByName('edit_quantity][' . $key, t('There are not enough quantity on the stock for product: @product_title. Available: @available', [
              '@product_title' => $product->getTitle(),
              '@available' => $diff_quantity == 0 ? $available : $available + $order_quantity,
            ]));
          }
        }
      }

    }
  }

}

/**
 * Implements hook_commerce_inline_form_PLUGIN_ID_alter().
 */
function bb2b_order_commerce_inline_form_customer_profile_alter(array &$inline_form, FormStateInterface $form_state, array &$complete_form) {
  if ($inline_form['#profile_scope'] === 'checkout' && !isset($inline_form['rendered'])) {
    $inline_form['address']['widget'][0]['address']['#after_build'] = [
      'change_country_code_ajax_callback',
      ['\Drupal\address\Element\Address', 'clearValues'],
    ];
  }
}

/**
 * Form API callback: Change ajax callback on country select.
 *
 * @param array $element
 *   The element array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state object.
 *
 * @return array
 *   The element array.
 */
function change_country_code_ajax_callback(array $element, FormStateInterface &$form_state) {
  $element['country_code']['country_code']['#ajax']['callback'] = [
    OrderTypePane::class,
    'ajaxRefresh',
  ];

  if ($form_state->getTriggeringElement()) {
    if ($form_state->getTriggeringElement()['#name'] === $element['country_code']['country_code']['#name']) {
      $form_state->setTriggeringElement($element['country_code']['country_code']);
    }
  }

  return $element;
}

/**
 * Implements hook_mail_alter().
 */
function bb2b_order_mail_alter(&$message) {
  if ($message['key'] == 'order_receipt' && !empty($message['params']['order'])) {
    /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
    $order = $message['params']['order'];
    $message['from'] = 'noreply@bescool.net';

    $subject_order_number = $order->getOrderNumber();
    if ($order->get('field_your_order_id')->isEmpty() === FALSE) {
      $client_order_number = $order->get('field_your_order_id')->value;
      $subject_order_number .= ' (' . $client_order_number . ')';
    }

    $message['subject'] = t('Order confirmation ID: @number', [
      '@number' => $subject_order_number,
    ]);
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function bb2b_order_form_views_exposed_form_alter(&$form, FormStateInterface $form_state) {
  $view = $form_state->get('view');

  if ($view && in_array($view->id(), ['ongoing_orders', 'orders'])) {
    $form['search']['#placeholder'] = t('By email, order IDs, recipient name');
  }
}

/**
 * Implements hook_views_data_alter().
 */
function bb2b_order_views_data_alter(array &$data) {
  $data['commerce_order']['own_company_orders'] = [
    'title' => t('Own company orders'),
    'help' => t('Filter orders by own company for company managers.'),
    'filter' => [
      'group' => 'Order',
      'field' => 'uid',
      'id' => 'own_company_orders_filter',
      'entity_type' => 'commerce_order',
    ],
  ];
}

/**
 * Implements hook_views_data().
 */
function bb2b_order_views_data() {
  $data = [];

  //@todo: Maybe we could put ["products"] instead of ["views"].
  $data['views']['stock_status'] = [
    'title' => t('Stock status - Custom Filter'),
    'filter' => [
      'title' => t('Stock status - Custom Filter'),
      'field' => 'id',
      'id' => 'stock_status',
    ]
  ];

  return $data;
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function bb2b_order_commerce_order_presave(EntityInterface $entity) {
  if ($entity->get('field_created_invoice')->isEmpty()) {
    $entity->set('field_created_invoice', 'no');
  }
  // In case the your_order_id is edited after the order has been created,
  // and before the order is being shipped.
  if (!empty($entity->get('placed')->value)) {
    $order_service = \Drupal::service('bb2b_order.order_service');
    $order_service->resolveShipping($entity);
  }
}

/**
 * Implements hook_entity_access().
 */
function bb2b_order_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
  $allowed_entities = [
    'commerce_order',
    'commerce_order_item',
    'commerce_shipment',
  ];

  if (in_array($entity->getEntityTypeId(), $allowed_entities)) {
    if ($account->hasPermission('update default commerce_order')) {
      return AccessResult::allowed();
    }
  }

  return AccessResult::neutral();
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function bb2b_order_form_commerce_shipment_default_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if (!\Drupal::currentUser()->hasPermission('access commerce administration pages')) {
    $form['title']['#access'] = FALSE;
    $form['shipping_method']['#access'] = FALSE;
    $form['tracking_code']['#access'] = FALSE;
    $form['shipment_items']['#access'] = FALSE;
    $form['recalculate_shipping']['#access'] = FALSE;
    $form['actions']['delete']['#access'] = FALSE;

    $request = \Drupal::requestStack()->getCurrentRequest();
    $shipment = $request->get('commerce_shipment');
    $form_state->set('shipment', $shipment);
    $shipment->setData('shipping_force_resolve', TRUE);

    if (isset($form['shipping_profile']['widget'][0]['profile']['edit_button'])) {
      $form['shipping_profile']['widget'][0]['profile']['edit_button']['#attributes']['class'][] = 'button-secondary';
    }

    // Redirect user to order view.
    $form['actions']['submit']['#submit'][] = 'bb2b_order_shipment_edit_form_submit';
  }
}

/**
 * Custom submit handler for the shipment edit form.
 */
function bb2b_order_shipment_edit_form_submit($form, FormStateInterface $form_state) {
  $shipment = $form_state->get('shipment');
  $account = \Drupal::currentUser();
  if ($shipment) {
    $admin_permission = $account
      ->hasPermission($shipment->getEntityType()->getAdminPermission());
    $order = $shipment->getOrder();

    if ($order && !$admin_permission) {
      /*$form_state->setRedirect('entity.commerce_order.user_view', [
      'commerce_order' => $order->id(),
      'user' => $order->getCustomerId(),
      ]);*/

      $form_state->setRedirect('entity.commerce_order.edit_form', [
        'commerce_order' => $order->id(),
      ]);
    }
  }
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function bb2b_order_form_commerce_order_default_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $user = \Drupal::currentUser();
  if (!$user->hasPermission('access commerce administration pages')) {
    $form['field_credit_note']['#access'] = FALSE;
    $form['field_internal_note']['#access'] = FALSE;
    $form['field_order_notes']['#access'] = FALSE;
    $form['field_order_type']['#access'] = FALSE;
    $form['field_vat_verified']['#access'] = FALSE;
    $form['field_tracking_code_date']['#access'] = FALSE;
    $form['coupons']['#access'] = FALSE;
    $form['billing_profile']['widget'][0]['profile']['select_address']['#access'] = FALSE;
    $form['billing_profile']['widget'][0]['profile']['edit_button']['#access'] = FALSE;
    $form['billing_profile']['widget'][0]['profile']['copy_fields']['enable']['#access'] = FALSE;
    $form['shipments']['#access'] = FALSE;

    $form['order_items']['widget']['actions']['ief_add']['#attributes']['class'][] = 'button-secondary';

    if (isset($form['order_items']['widget']['entities'])) {
      foreach ($form['order_items']['widget']['entities'] as $key => &$entity) {
        if (is_numeric($key)) {
          $entity['actions']['ief_entity_edit']['#attributes']['class'][] = 'button-secondary';
          $entity['actions']['ief_entity_remove']['#attributes']['class'][] = 'delete-order-item';
        }
      }
    }

    $form['ddp_package_list'] = [
      '#type' => 'details',
      '#title' => t('At DDP orders, every box must be filled with the MOQ / box quantity.'),
      '#weight' => -2,
      '#open' => FALSE,
    ];

    $form['ddp_package_list']['block'] = [
      '#type' => 'container',
      'block' => [
        '#markup' => bb2b_order_render_shipment_package_block(),
      ],
    ];

    $header = [t('Title'), t('Shipping method'), t('Price'), t('Actions')];
    $rows = [];
    $request = \Drupal::requestStack()->getCurrentRequest();

    /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
    $order = $request->get('commerce_order');
    if ($order && $user->hasPermission('update default commerce_order')) {
      $form_state->set('order', $order);
      /** @var \Drupal\commerce_shipping\Entity\ShipmentInterface[] $shipments */
      $shipments = $order->get('shipments')->referencedEntities();
      foreach ($shipments as $shipment) {
        $row = $links = [];
        $links['edit'] = [
          'title' => t('Edit'),
          'url' => $shipment->toUrl('edit-form'),
          'attributes' => [
            'role' => 'button',
            'class' => ['button button-secondary'],
            // 'target' => '_blank',
          ],
        ];

        $row[] = [
          'data' => [
            '#type' => 'markup',
            '#markup' => $shipment->label(),
          ],
        ];

        $row[] = [
          'data' => [
            '#type' => 'markup',
            '#markup' => $shipment->getShippingMethod()->label(),
          ],
        ];

        $row[] = [
          'data' => [
            '#type' => 'markup',
            '#markup' => $shipment->getAmount(),
          ],
        ];

        $row[] = [
          'data' => [
            '#type' => 'operations',
            '#links' => $links,
          ],
        ];

        $rows[] = $row;
      }
    }

    $form['shipments_list'] = [
      '#type' => 'container',
      '#weight' => $form['shipments']['#weight'] ?? 0,
      'shipments' => [
        '#type' => 'fieldset',
        '#title' => t('Shipments'),
        'entities' => [
          '#theme' => 'table',
          '#rows' => $rows,
          '#header' => $header,
        ],
      ],
    ];

    array_unshift($form['#validate'], 'validate_first_package_is_full');

    // Redirect user to order view.
    $form['actions']['submit']['#submit'][] = 'bb2b_order_order_edit_form_submit';
  }
}

/**
 * Custom submit handler for the order edit form.
 */
function bb2b_order_order_edit_form_submit($form, FormStateInterface $form_state) {
  $order = $form_state->get('order');
  if ($order) {
    $form_state->setRedirect('entity.commerce_order.user_view', [
      'commerce_order' => $order->id(),
      'user' => $order->getCustomerId(),
    ]);
  }
}

/**
 * Render shipment packages block content.
 *
 * @return mixed
 *   The rendered content.
 */
function bb2b_order_render_shipment_package_block() {
  $shipment_package_view = views_embed_view('shipment_package_list', 'block_1');

  return \Drupal::service('renderer')
    ->render($shipment_package_view);
}

/**
 * Form validation handler for commerce_order_default_edit_form().
 *
 * @param array $form
 *   A build form array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state.
 */
function validate_first_package_is_full(array $form, FormStateInterface $form_state) {
  $triggering_element = $form_state->getTriggeringElement();

  $parents = $triggering_element['#array_parents'];
  $triggering_element_name = array_pop($parents);

  if ($triggering_element_name === 'submit') {
    $storage = $form_state->getStorage();
    $order_items_array = $storage['inline_entity_form']['order_items-form']['entities'] ?? [];

    /** @var \Drupal\bb2b_order\OrderServiceInterface $order_service */
    $order_service = \Drupal::service('bb2b_order.order_service');

    $order_type = NULL;
    $order_items = [];
    foreach ($order_items_array as $order_item_array) {
      if (isset($order_item_array['entity'])) {
        $order_items[] = $order_item_array['entity'];
        if (!$order_type) {
          $order = $order_item_array['entity']->getOrder();
          $order_type = $order ? $order->get('field_order_type')->value : NULL;
        }
      }
    }

    if ($order_items && $order_type !== 'drop_shipping') {
      $quantities = $order_service->getOrderedQuantitiesByTag($order_items);
      if ($order_service->minQuantityIsNotValid($quantities)) {
        $form_state->setErrorByName('full_package', t('Your order can not be completed because the conditions for minimum order quantity is not met.'));
      }
    }
  }

}

/**
 * Implements hook_inline_entity_form_entity_form_alter().
 */
function bb2b_order_inline_entity_form_entity_form_alter(&$entity_form, &$form_state) {
  $access_commerce_admin_pages = \Drupal::currentUser()
    ->hasPermission('access commerce administration pages');
  if (!$access_commerce_admin_pages) {
    if ($entity_form['#entity_type'] === 'commerce_shipment') {
      $entity_form['title']['#access'] = FALSE;
      $entity_form['shipping_method']['#access'] = FALSE;
      $entity_form['tracking_code']['#access'] = FALSE;
    }
    elseif ($entity_form['#entity_type'] === 'commerce_order_item') {
      $entity_form['unit_price']['#access'] = FALSE;
      $default_quantity = (int) ($entity_form['quantity']['widget'][0]['value']['#default_value'] ?? 0);
      $entity_form['quantity']['widget'][0]['value']['#default_value'] = $default_quantity;
      $entity_form['purchased_entity']['widget'][0]['target_id']['#attributes']['class'][] = 'form-order-item-autocomplete';
    }
  }
}

/**
 * Implements hook_entity_base_field_info_alter().
 */
function bb2b_order_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
  if ($entity_type->id() === 'commerce_order_item' && isset($fields['quantity'])) {
    $fields['quantity']->addConstraint('CheckAvailable');
  }
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function bb2b_order_commerce_shipment_presave(ShipmentInterface $shipment) {
  $order = $shipment->getOrder();
  $pending_order = $order && $order->getState()->getId() === 'pending';
  $shipping_force_resolve = $shipment->getData('shipping_force_resolve', FALSE);

  if ($shipment->original && $pending_order && $shipping_force_resolve) {
    /** @var \Drupal\bb2b_order\OrderServiceInterface $order_service */
    $order_service = \Drupal::service('bb2b_order.order_service');
    $order = $shipment->getOrder();

    $shipping_method = $order_service
      ->getShippingMethodForOrder($order, $shipment->getShippingProfile());

    if ($shipping_method) {
      $adjustments = $order->getAdjustments();
      $order_service->setShippingMethodForShipment($shipment, $shipping_method, 0, $adjustments);
      $order->set('adjustments', $adjustments);
    }
    $shipment->setData('shipping_force_resolve', FALSE);
  }
}

/**
 * Implements hook_theme().
 */
function bb2b_order_theme($existing, $type, $theme, $path) {
  return [
    'commerce_checkout_form__with_sidebar' => [
      'base hook' => 'commerce_checkout_form',
      'variables' => [
        'ordered_products' => NULL,
      ],
    ],
  ];
}

/**
 * Implements hook_preprocess_HOOK().
 */
function bb2b_order_preprocess_commerce_checkout_form__with_sidebar(&$variables) {
  $block = Block::load('views_block__ordered_products_block_2');
  $step = $variables['form']['#step_id'] ?? NULL;

  if ($block && $step === 'complete') {
    $variables['ordered_products'] = $block->getPlugin()->build();
    $variables['ordered_products']['#attributes']['class'][] = 'ordered-products-block';
  }
}

/**
 * Implements hook_tokens_alter().
 */
function bb2b_order_tokens_alter(array &$replacements, array $context, BubbleableMetadata $bubbleable_metadata) {
  // Clear empty token.
  if (isset($context['data']['entity_type']) &&
    $context['data']['entity_type'] === 'commerce_order'
  ) {
    foreach ($context['tokens'] as $token) {
      if (!isset($replacements[$token])) {
        $replacements[$token] = '';
      }
    }
  }
}

/**
 * Implements hook_entity_view_alter().
 */
function bb2b_order_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
  if ($build['#view_mode'] === 'user' &&
    $build['#entity_type'] === 'commerce_order'
  ) {
    /** @var \Drupal\commerce_order\Entity\OrderInterface $entity */
    $entity_state = $entity->getState()->getId();

    if ($entity_state === 'pending') {
      $order = $build['#commerce_order'];
      $vat_verified = $order->get('field_vat_verified')->value;

      if (($vat_verified === '1') || ($vat_verified === '0')) {
        $ship_uri = '/orders/' . $entity->id() . '/send';
        $ship_url = Url::fromUri('internal:' . $ship_uri);

        $build['ship_the_order_link'] = [
          '#type' => 'link',
          '#title' => t('Ship the order'),
          '#url' => $ship_url,
          '#weight' => -11,
          '#attributes' => [
            'class' => ['button button-secondary'],
          ],
        ];
      }

      $user = $entity->getCustomer();
      if ($user->hasPermission('update default commerce_order')) {
        $build['edit_link'] = [
          '#type' => 'link',
          '#title' => t('Edit order'),
          '#url' => $entity->toUrl('edit-form'),
          '#weight' => -10,
          '#attributes' => [
            'class' => ['button button-secondary'],
          ],
        ];
      }
    }
  }
}
{#
/**
 * @file
 * Template for the commerce email.
 *
 * Available variables:
 * - order_entity: The order entity.
 * - body: The email body.
 * - totals: An array of order totals values with the following keys:
 *   - subtotal: The order subtotal price.
 *   - adjustments: An array of adjustment totals:
 *     - type: The adjustment type.
 *     - label: The adjustment label.
 *     - total: The adjustment total price.
 *     - weight: The adjustment weight, taken from the adjustment type.
 *
 * @ingroup themeable
 */
#}
<table style="margin: 15px auto 0 auto; max-width: 768px; font-family: arial,sans-serif">
  <tbody>
  <tr>
    <td>
      <table style="text-align: center; min-width: 450px; margin: 5px auto 0 auto; border: 1px solid #cccccc; border-radius: 5px; padding: 40px 30px 30px 30px;">
        <tbody>
        <tr>
          <td style="font-size: 30px; padding-bottom: 30px"><img src="/themes/custom/zadi_theme/belmil_wave_logo_email.png" style="height: 60px;" /></td>
        </tr>
        <tr>
          <td>
            {{ body|raw }}
          </td>

          <tr>
            <td style="font-weight: bold; padding-top:15px; padding-bottom: 15px; text-align: left; border-top: 1px solid #cccccc; border-bottom: 1px solid #cccccc">
              {{ 'Order #@number details:'|t({'@number': order_entity.getOrderNumber}) }}
            </td>
          </tr>
          <tr>
            <td>
              <table style="padding-top: 15px; padding-bottom:15px; width: 100%">
                <tbody style="text-align: left;">
                {% for order_item in order_entity.getItems %}
                  <tr>
                    <td>
                      {{ order_item.getQuantity|number_format }} x
                    </td>
                    <td>
                      <span>{{ order_item.label }}</span>
                      <span style="float: right;">{{ order_item.getTotalPrice|commerce_price_format }}</span>
                    </td>
                  </tr>
                {% endfor %}
                </tbody>
              </table>
            </td>
          </tr>
        <tr>
          <td>
            <p style="margin-bottom: 0;">
              {{ 'Subtotal: @subtotal'|t({'@subtotal': totals.subtotal|commerce_price_format}) }}
            </p>
          </td>
        </tr>
        {% for adjustment in totals.adjustments %}
          <tr>
            <td>
              <p style="margin-bottom: 0;">
                {{ adjustment.label }}: {{ adjustment.total|commerce_price_format }}
              </p>
            </td>
          </tr>
        {% endfor %}
        <tr>
          <td>
            <p style="font-size: 24px; padding-top: 15px; padding-bottom: 5px;">
              {{ 'Order Total: @total'|t({'@total': order_entity.getTotalPrice|commerce_price_format}) }}
            </p>
          </td>
        </tr>

          {% if store_info %}
          <tr>
            <td>
              {% block additional_information %}
                {{ store_info|raw }}
              {% endblock %}
            </td>
          </tr>
          {% endif %}
        </tr>
        </tbody>
      </table>
    </td>
  </tr>
  </tbody>
</table>

Similiar Collections

Python strftime reference pandas.Period.strftime python - Formatting Quarter time in pandas columns - Stack Overflow python - Pandas: Change day - Stack Overflow python - Check if multiple columns exist in a df - Stack Overflow Pandas DataFrame apply() - sending arguments examples python - How to filter a dataframe of dates by a particular month/day? - Stack Overflow python - replace a value in the entire pandas data frame - Stack Overflow python - Replacing blank values (white space) with NaN in pandas - Stack Overflow python - get list from pandas dataframe column - Stack Overflow python - How to drop rows of Pandas DataFrame whose value in a certain column is NaN - Stack Overflow python - How to drop rows of Pandas DataFrame whose value in a certain column is NaN - Stack Overflow python - How to lowercase a pandas dataframe string column if it has missing values? - Stack Overflow How to Convert Integers to Strings in Pandas DataFrame - Data to Fish How to Convert Integers to Strings in Pandas DataFrame - Data to Fish create a dictionary of two pandas Dataframe columns? - Stack Overflow python - ValueError: No axis named node2 for object type <class 'pandas.core.frame.DataFrame'> - Stack Overflow Python Pandas iterate over rows and access column names - Stack Overflow python - Creating dataframe from a dictionary where entries have different lengths - Stack Overflow python - Deleting DataFrame row in Pandas based on column value - Stack Overflow python - How to check if a column exists in Pandas - Stack Overflow python - Import pandas dataframe column as string not int - Stack Overflow python - What is the most efficient way to create a dictionary of two pandas Dataframe columns? - Stack Overflow Python Loop through Excel sheets, place into one df - Stack Overflow python - How do I get the row count of a Pandas DataFrame? - Stack Overflow python - How to save a new sheet in an existing excel file, using Pandas? - Stack Overflow Python Loop through Excel sheets, place into one df - Stack Overflow How do I select a subset of a DataFrame? — pandas 1.2.4 documentation python - Delete column from pandas DataFrame - Stack Overflow python - Convert list of dictionaries to a pandas DataFrame - Stack Overflow How to Add or Insert Row to Pandas DataFrame? - Python Examples python - Check if a value exists in pandas dataframe index - Stack Overflow python - Set value for particular cell in pandas DataFrame using index - Stack Overflow python - Pandas Dataframe How to cut off float decimal points without rounding? - Stack Overflow python - Pandas: Change day - Stack Overflow python - Clean way to convert quarterly periods to datetime in pandas - Stack Overflow Pandas - Number of Months Between Two Dates - Stack Overflow python - MonthEnd object result in <11 * MonthEnds> instead of number - Stack Overflow python - Extracting the first day of month of a datetime type column in pandas - Stack Overflow
MySQL MULTIPLES INNER JOIN How to Use EXISTS, UNIQUE, DISTINCT, and OVERLAPS in SQL Statements - dummies postgresql - SQL OVERLAPS PostgreSQL Joins: Inner, Outer, Left, Right, Natural with Examples PostgreSQL Joins: A Visual Explanation of PostgreSQL Joins PL/pgSQL Variables ( Format Dates ) The Ultimate Guide to PostgreSQL Date By Examples Data Type Formatting Functions PostgreSQL - How to calculate difference between two timestamps? | TablePlus Date/Time Functions and Operators PostgreSQL - DATEDIFF - Datetime Difference in Seconds, Days, Months, Weeks etc - SQLines CASE Statements in PostgreSQL - DataCamp SQL Optimizations in PostgreSQL: IN vs EXISTS vs ANY/ALL vs JOIN PostgreSQL DESCRIBE TABLE Quick and best way to Compare Two Tables in SQL - DWgeek.com sql - Best way to select random rows PostgreSQL - Stack Overflow PostgreSQL: Documentation: 13: 70.1. Row Estimation Examples Faster PostgreSQL Counting How to Add a Default Value to a Column in PostgreSQL - PopSQL How to Add a Default Value to a Column in PostgreSQL - PopSQL SQL Subquery - Dofactory SQL IN - SQL NOT IN - JournalDev DROP FUNCTION (Transact-SQL) - SQL Server | Microsoft Docs SQL : Multiple Row and Column Subqueries - w3resource PostgreSQL: Documentation: 9.5: CREATE FUNCTION PostgreSQL CREATE FUNCTION By Practical Examples datetime - PHP Sort a multidimensional array by element containing date - Stack Overflow database - Oracle order NULL LAST by default - Stack Overflow PostgreSQL: Documentation: 9.5: Modifying Tables PostgreSQL: Documentation: 14: SELECT postgresql - sql ORDER BY multiple values in specific order? - Stack Overflow How do I get the current unix timestamp from PostgreSQL? - Database Administrators Stack Exchange
Clear config cache Eloquent DB::Table RAW Query / WhereNull Laravel Eloquent "IN" Query get single column value in laravel eloquent php - How to use CASE WHEN in Eloquent ORM? - Stack Overflow AND-OR-AND + brackets with Eloquent - Laravel Daily Database: Query Builder - Laravel - The PHP Framework For Web Artisans ( RAW ) Combine Foreach Loop and Eloquent to perform a search | Laravel.io Access Controller method from another controller in Laravel 5 How to Call a controller function in another Controller in Laravel 5 php - Create a Laravel Request object on the fly php - Laravel 5.6 Upgrade caused Logging to break Artisan Console - Laravel - The PHP Framework For Web Artisans What to include in gitignore for a Laravel and PHPStorm project php - Create a Laravel Request object on the fly Process big DB table with chunk() method - Laravel Daily How to insert big data on the laravel? - Stack Overflow php - How can I build a condition based query in Laravel? - Stack Overflow Laravel Chunk Eloquent Method Example - Tuts Make Database: Migrations - Laravel - The PHP Framework For Web Artisans php - Laravel Model Error Handling when Creating - Exception Laravel - Inner Join with Multiple Conditions Example using Query Builder - ItSolutionStuff.com laravel cache disable phpunit code example | Newbedev In PHP, how to check if a multidimensional array is empty? · Humblix php - Laravel firstOrNew how to check if it's first or new? - Stack Overflow get base url laravel 8 Code Example Using gmail smtp via Laravel: Connection could not be established with host smtp.gmail.com [Connection timed out #110] - Stack Overflow
PostgreSQL POSITION() function PostgresQL ANY / SOME Operator ( IN vs ANY ) PostgreSQL Substring - Extracting a substring from a String How to add an auto-incrementing primary key to an existing table, in PostgreSQL PostgreSQL STRING_TO_ARRAY()function mysql FIND_IN_SET equivalent to postgresql PL/pgSQL Variables ( Format Dates ) The Ultimate Guide to PostgreSQL Date By Examples Data Type Formatting Functions PostgreSQL - How to calculate difference between two timestamps? | TablePlus Date/Time Functions and Operators PostgreSQL - DATEDIFF - Datetime Difference in Seconds, Days, Months, Weeks etc - SQLines CASE Statements in PostgreSQL - DataCamp SQL Optimizations in PostgreSQL: IN vs EXISTS vs ANY/ALL vs JOIN PL/pgSQL Variables PostgreSQL: Documentation: 11: CREATE PROCEDURE Reading a Postgres EXPLAIN ANALYZE Query Plan Faster PostgreSQL Counting sql - Fast way to discover the row count of a table in PostgreSQL - Stack Overflow PostgreSQL: Documentation: 9.1: tablefunc PostgreSQL DESCRIBE TABLE Quick and best way to Compare Two Tables in SQL - DWgeek.com sql - Best way to select random rows PostgreSQL - Stack Overflow How to Add a Default Value to a Column in PostgreSQL - PopSQL How to Add a Default Value to a Column in PostgreSQL - PopSQL PL/pgSQL IF Statement PostgreSQL: Documentation: 9.1: Declarations SQL Subquery - Dofactory SQL IN - SQL NOT IN - JournalDev PostgreSQL - IF Statement - GeeksforGeeks How to work with control structures in PostgreSQL stored procedures: Using IF, CASE, and LOOP statements | EDB PL/pgSQL IF Statement How to combine multiple selects in one query - Databases - ( loop reference ) DROP FUNCTION (Transact-SQL) - SQL Server | Microsoft Docs SQL : Multiple Row and Column Subqueries - w3resource PostgreSQL: Documentation: 9.5: CREATE FUNCTION PostgreSQL CREATE FUNCTION By Practical Examples datetime - PHP Sort a multidimensional array by element containing date - Stack Overflow database - Oracle order NULL LAST by default - Stack Overflow PostgreSQL: Documentation: 9.5: Modifying Tables PostgreSQL: Documentation: 14: SELECT PostgreSQL Array: The ANY and Contains trick - Postgres OnLine Journal postgresql - sql ORDER BY multiple values in specific order? - Stack Overflow sql - How to aggregate two PostgreSQL columns to an array separated by brackets - Stack Overflow How do I get the current unix timestamp from PostgreSQL? - Database Administrators Stack Exchange
כמה עוד נשאר למשלוח חינם גם לעגלה ולצקאאוט הוספת צ'קבוקס לאישור דיוור בצ'קאאוט הסתרת אפשרויות משלוח אחרות כאשר משלוח חינם זמין דילוג על מילוי כתובת במקרה שנבחרה אפשרות איסוף עצמי הוספת צ'קבוקס לאישור דיוור בצ'קאאוט שינוי האפשרויות בתפריט ה-סידור לפי בווקומרס שינוי הטקסט "אזל מהמלאי" הערה אישית לסוף עמוד העגלה הגבלת רכישה לכל המוצרים למקסימום 1 מכל מוצר קבלת שם המוצר לפי ה-ID בעזרת שורטקוד הוספת כפתור וואטסאפ לקנייה בלופ ארכיון מוצרים הפיכה של מיקוד בצ'קאאוט ללא חובה מעבר ישיר לצ'קאאוט בלחיתה על הוספה לסל (דילוג עגלה) התראה לקבלת משלוח חינם בדף עגלת הקניות גרסה 1 התראה לקבלת משלוח חינם בדף עגלת הקניות גרסה 2 קביעה של מחיר הזמנה מינימלי (מוצג בעגלה ובצ'קאאוט) העברת קוד הקופון ל-ORDER REVIEW העברת קוד הקופון ל-ORDER REVIEW Kadence WooCommerce Email Designer קביעת פונט אסיסנט לכל המייל בתוסף מוצרים שאזלו מהמלאי - יופיעו מסומנים באתר, אבל בתחתית הארכיון הוספת כפתור "קנה עכשיו" למוצרים הסתרת אפשרויות משלוח אחרות כאשר משלוח חינם זמין שיטה 2 שינוי סימן מטבע ש"ח ל-ILS להפוך סטטוס הזמנה מ"השהייה" ל"הושלם" באופן אוטומטי תצוגת הנחה באחוזים שינוי טקסט "בחר אפשרויות" במוצרים עם וריאציות חיפוש מוצר לפי מק"ט שינוי תמונת מוצר לפי וריאציה אחרי בחירה של וריאציה אחת במקרה של וריאציות מרובות הנחה קבועה לפי תפקיד בתעריף קבוע הנחה קבועה לפי תפקיד באחוזים הסרה של שדות משלוח לקבצים וירטואליים הסתרת טאבים מעמוד מוצר הצגת תגית "אזל מהמלאי" בלופ המוצרים להפוך שדות ל-לא חובה בצ'קאאוט שינוי טקסט "אזל מהמלאי" לוריאציות שינוי צבע ההודעות המובנות של ווקומרס הצגת ה-ID של קטגוריות המוצרים בעמוד הקטגוריות אזל מהמלאי- שינוי ההודעה, תגית בלופ, הודעה בדף המוצר והוספת אזל מהמלאי על וריאציה הוספת שדה מחיר ספק לדף העריכה שינוי טקסט אזל מהמלאי תמונות מוצר במאונך לצד תמונת המוצר הראשית באלמנטור הוספת כפתור קנה עכשיו לעמוד המוצר בקניה הזו חסכת XX ש''ח לאפשר למנהל חנות לנקות קאש ברוקט לאפשר רק מוצר אחד בעגלת קניות הוספת סימון אריזת מתנה ואזור להוראות בצ'קאאוט של ווקומרס הצגת הנחה במספר (גודל ההנחה) הוספת "אישור תקנון" לדף התשלום
החלפת טקסט באתר (מתאים גם לתרגום נקודתי) הסרת פונטים של גוגל מתבנית KAVA ביטול התראות במייל על עדכון וורדפרס אוטומטי הוספת תמיכה בקבצי VCF באתר (קבצי איש קשר VCARD) - חלק 1 להחריג קטגוריה מסוימת מתוצאות החיפוש שליפת תוכן של ריפיטר יצירת כפתור שיתוף למובייל זיהוי אלו אלמנטים גורמים לגלילה אופקית התקנת SMTP הגדרת טקסט חלופי לתמונות לפי שם הקובץ הוספת התאמת תוספים לגרסת WP הוספת טור ID למשתמשים הסרת כותרת בתבנית HELLO הסרת תגובות באופן גורף הרשאת SVG חילוץ החלק האחרון של כתובת העמוד הנוכחי חילוץ הסלאג של העמוד חילוץ כתובת העמוד הנוכחי מניעת יצירת תמונות מוקטנות התקנת SMTP הצגת ה-ID של קטגוריות בעמוד הקטגוריות להוריד מתפריט הניהול עמודים הוספת Favicon שונה לכל דף ודף הוספת אפשרות שכפול פוסטים ובכלל (של שמעון סביר) הסרת תגובות באופן גורף 2 בקניה הזו חסכת XX ש''ח חיפוש אלמנטים סוררים, גלישה צדית במובייל שיטה 1 לאפשר רק מוצר אחד בעגלת קניות הצגת הנחה במספר (גודל ההנחה) הוספת "אישור תקנון" לדף התשלום שינוי צבע האדמין לפי סטטוס העמוד/פוסט שינוי צבע אדמין לכולם לפי הסכמות של וורדפרס תצוגת כמות צפיות מתוך הדשבורד של וורדפרס הצגת סוג משתמש בפרונט
הודעת שגיאה מותאמת אישית בטפסים להפוך כל סקשן/עמודה לקליקבילית (לחיצה) - שיטה 1 להפוך כל סקשן/עמודה לקליקבילית (לחיצה) - שיטה 2 שינוי הגבלת הזיכרון בשרת הוספת לינק להורדת מסמך מהאתר במייל הנשלח ללקוח להפוך כל סקשן/עמודה לקליקבילית (לחיצה) - שיטה 3 יצירת כפתור שיתוף למובייל פתיחת דף תודה בטאב חדש בזמן שליחת טופס אלמנטור - טופס בודד בדף פתיחת דף תודה בטאב חדש בזמן שליחת טופס אלמנטור - טפסים מרובים בדף ביי ביי לאריק ג'ונס (חסימת ספאם בטפסים) זיהוי אלו אלמנטים גורמים לגלילה אופקית לייבלים מרחפים בטפסי אלמנטור יצירת אנימציה של "חדשות רצות" בג'ט (marquee) שינוי פונט באופן דינאמי בג'ט פונקציה ששולפת שדות מטא מתוך JET ומאפשרת לשים הכל בתוך שדה SELECT בטופס אלמנטור הוספת קו בין רכיבי התפריט בדסקטופ ולדציה למספרי טלפון בטפסי אלמנטור חיבור שני שדות בטופס לשדה אחד שאיבת נתון מתוך כתובת ה-URL לתוך שדה בטופס וקידוד לעברית מדיה קוורי למובייל לייבלים מרחפים בטפסי אלמנטור תמונות מוצר במאונך לצד תמונת המוצר הראשית באלמנטור הצגת תאריך עברי פורמט תאריך מותאם אישית תיקון שדה תאריך בטופס אלמנטור במובייל שאיבת פרמטר מתוך הכתובת והזנתו לתוך שדה בטופס (PARAMETER, URL, INPUT) עמודות ברוחב מלא באלמנטור עמודה דביקה בתוך אלמנטור יצירת "צל" אומנותי קוד לסוויצ'ר, שני כפתורים ושני אלמנטים סקריפט לסגירת פופאפ של תפריט לאחר לחיצה על אחד העמודים