Current File : /var/www/prestashop/modules/ps_eventbus/src/Service/SpecificPriceService.php
<?php

namespace PrestaShop\Module\PsEventbus\Service;

use PrestaShop\Module\PsEventbus\Repository\SpecificPriceRepository;

class SpecificPriceService
{
    /** @var array<mixed> */
    private static $_pricesLevel2;

    /**
     * @var SpecificPriceRepository
     */
    private $specificPriceRepository;

    public function __construct(SpecificPriceRepository $specificPriceRepository)
    {
        $this->specificPriceRepository = $specificPriceRepository;
    }

    /**
     * @param int $productId
     * @param int $attributeId
     * @param int $specificPriceId
     * @param bool $useTax
     * @param bool $usereduc
     * @param \Context|null $context
     *
     * @return float|int|void
     *
     * @@throws \PrestaShopException
     */
    public function getSpecificProductPrice($productId, $attributeId, $specificPriceId, $useTax, $usereduc, $context)
    {
        return $this->getPriceStatic($productId, $attributeId, $specificPriceId, $useTax, $usereduc, $context);
    }

    /**
     * @param int $id_product
     * @param int $id_product_attribute
     * @param int $specificPriceId
     * @param bool $usetax
     * @param bool $usereduc
     * @param \Context|null $context
     * @param int $decimals
     * @param null $divisor
     * @param bool $only_reduc
     * @param null $id_customer
     * @param null $id_cart
     * @param null $id_address
     * @param null $specific_price_output
     * @param bool $use_group_reduction
     *
     * @return float|int|void
     *
     * @@throws \PrestaShopException
     */
    private function getPriceStatic(
        $id_product,
        $id_product_attribute,
        $specificPriceId,
        $usetax = true,
        $usereduc = true,
        $context = null,
        $decimals = 6,
        $divisor = null,
        $only_reduc = false,
        $id_customer = null,
        $id_cart = null,
        $id_address = null,
        &$specific_price_output = null,
        $use_group_reduction = true
    ) {
        if (!$context) {
            /** @var \Context $context */
            $context = \Context::getContext();
        }

        \Tools::displayParameterAsDeprecated('divisor');

        if (!\Validate::isBool($usetax) || !\Validate::isUnsignedId($id_product)) {
            exit(\Tools::displayError());
        }

        // Initializations
        $id_group = (int) \Group::getCurrent()->id;

        /** @var \Currency $currency */
        $currency = $context->currency;
        $id_currency = \Validate::isLoadedObject($currency) ? (int) $currency->id : (int) \Configuration::get('PS_CURRENCY_DEFAULT');

        $current_cart = $context->cart;
        if ($current_cart != null && \Validate::isLoadedObject($current_cart)) {
            $id_address = $current_cart->{\Configuration::get('PS_TAX_ADDRESS_TYPE')};
        }

        // retrieve address informations
        $address = \Address::initialize($id_address, true);
        $id_country = (int) $address->id_country;
        $id_state = (int) $address->id_state;
        $zipcode = $address->postcode;

        if (\Tax::excludeTaxeOption()) {
            $usetax = false;
        }

        if (
            $usetax != false
            && !empty($address->vat_number)
            && $address->id_country != \Configuration::get('VATNUMBER_COUNTRY')
            && \Configuration::get('VATNUMBER_MANAGEMENT')
        ) {
            $usetax = false;
        }

        if ($context->shop == null) {
            throw new \PrestaShopException('No shop context');
        }

        $shopId = (int) $context->shop->id;

        return $this->priceCalculation(
            $shopId,
            $id_product,
            $id_product_attribute,
            $specificPriceId,
            $id_country,
            $id_state,
            $zipcode,
            $id_currency,
            $id_group,
            $usetax,
            $decimals,
            $only_reduc,
            $usereduc,
            $specific_price_output,
            $use_group_reduction
        );
    }

    /**
     * @param int $id_shop
     * @param int $id_product
     * @param int $id_product_attribute
     * @param int $specificPriceId
     * @param int $id_country
     * @param int $id_state
     * @param string $zipcode
     * @param int $id_currency
     * @param int $id_group
     * @param bool $use_tax
     * @param int $decimals
     * @param bool $only_reduc
     * @param bool $use_reduc
     * @param null $specific_price
     * @param bool $use_group_reduction
     * @param int $id_customization
     *
     * @return float|int|void
     *
     * @@throws \PrestaShopDatabaseException
     */
    private function priceCalculation(
        $id_shop,
        $id_product,
        $id_product_attribute,
        $specificPriceId,
        $id_country,
        $id_state,
        $zipcode,
        $id_currency,
        $id_group,
        $use_tax,
        $decimals,
        $only_reduc,
        $use_reduc,
        &$specific_price,
        $use_group_reduction,
        $id_customization = 0
    ) {
        static $address = null;
        static $context = null;

        if ($context == null) {
            /** @var \Context $context */
            $context = \Context::getContext();
            $context = $context->cloneContext();
        }

        if ($address === null) {
            if (is_object($context->cart) && $context->cart->{\Configuration::get('PS_TAX_ADDRESS_TYPE')} != null) {
                $id_address = $context->cart->{\Configuration::get('PS_TAX_ADDRESS_TYPE')};
                $address = new \Address($id_address);
            } else {
                $address = new \Address();
            }
        }

        if ($id_shop !== null && $context->shop->id != (int) $id_shop) {
            $context->shop = new \Shop((int) $id_shop);
        }

        if ($id_product_attribute == null) {
            $id_product_attribute = \Product::getDefaultAttribute($id_product);
        }

        // reference parameter is filled before any returns
        /** @var array<mixed> $specific_price */
        $specific_price = $this->getSpecificPrice($specificPriceId);

        // fetch price & attribute price
        $cache_id_2 = $id_product . '-' . $id_shop . '-' . $specificPriceId;
        if (!isset(self::$_pricesLevel2[$cache_id_2])) {
            $sql = new \DbQuery();
            $sql->select('product_shop.`price`, product_shop.`ecotax`');
            $sql->from('product', 'p');
            $sql->innerJoin('product_shop', 'product_shop', '(product_shop.id_product=p.id_product AND product_shop.id_shop = ' . (int) $id_shop . ')');
            $sql->where('p.`id_product` = ' . (int) $id_product);
            if (\Combination::isFeatureActive()) {
                $sql->select('IFNULL(product_attribute_shop.id_product_attribute,0) id_product_attribute, product_attribute_shop.`price` AS attribute_price, product_attribute_shop.default_on');
                $sql->leftJoin('product_attribute_shop', 'product_attribute_shop', '(product_attribute_shop.id_product = p.id_product AND product_attribute_shop.id_shop = ' . (int) $id_shop . ')');
            } else {
                $sql->select('0 as id_product_attribute');
            }

            $res = \Db::getInstance((bool) _PS_USE_SQL_SLAVE_)->executeS($sql);

            if (is_array($res) && count($res)) {
                foreach ($res as $row) {
                    $array_tmp = [
                        'price' => $row['price'],
                        'ecotax' => $row['ecotax'],
                        'attribute_price' => (isset($row['attribute_price']) ? $row['attribute_price'] : null),
                    ];
                    self::$_pricesLevel2[$cache_id_2][(int) $row['id_product_attribute']] = $array_tmp;

                    if (isset($row['default_on']) && $row['default_on'] == 1) {
                        self::$_pricesLevel2[$cache_id_2][0] = $array_tmp;
                    }
                }
            }
        }

        if (!isset(self::$_pricesLevel2[$cache_id_2][(int) $id_product_attribute])) {
            return;
        }

        $result = self::$_pricesLevel2[$cache_id_2][(int) $id_product_attribute];

        if (!$specific_price || $specific_price['price'] < 0) {
            $price = (float) $result['price'];
        } else {
            $price = (float) $specific_price['price'];
        }
        // convert only if the specific price is in the default currency (id_currency = 0)
        if (!$specific_price || !($specific_price['price'] >= 0 && $specific_price['id_currency'])) {
            $price = \Tools::convertPrice($price, $id_currency);

            if (isset($specific_price['price']) && $specific_price['price'] >= 0) {
                $specific_price['price'] = $price;
            }
        }

        // Attribute price
        if (is_array($result) && (!$specific_price || !$specific_price['id_product_attribute'] || $specific_price['price'] < 0)) {
            $attribute_price = \Tools::convertPrice($result['attribute_price'] !== null ? (float) $result['attribute_price'] : 0, $id_currency);
            // If you want the default combination, please use NULL value instead
            if ($id_product_attribute !== false) {
                $price += $attribute_price;
            }
        }

        if (defined('_PS_VERSION_') && version_compare(_PS_VERSION_, '1.7.0.0', '>=')) {
            // Customization price
            if ((int) $id_customization) {
                /* @phpstan-ignore-next-line */
                $price += \Tools::convertPrice(\Customization::getCustomizationPrice($id_customization), $id_currency);
            }
        }

        // Tax
        $address->id_country = $id_country;
        $address->id_state = $id_state;
        $address->postcode = $zipcode;

        $tax_manager = \TaxManagerFactory::getManager($address, \Product::getIdTaxRulesGroupByIdProduct((int) $id_product, $context));
        $product_tax_calculator = $tax_manager->getTaxCalculator();

        // Add Tax
        if ($use_tax) {
            $price = $product_tax_calculator->addTaxes((float) $price);
        }

        // Eco Tax
        if ($result['ecotax'] || isset($result['attribute_ecotax'])) {
            $ecotax = $result['ecotax'];
            if (isset($result['attribute_ecotax']) && $result['attribute_ecotax'] > 0) {
                $ecotax = $result['attribute_ecotax'];
            }

            if ($id_currency) {
                $ecotax = \Tools::convertPrice($ecotax, $id_currency);
            }
            if ($use_tax) {
                static $psEcotaxTaxRulesGroupId = null;
                if ($psEcotaxTaxRulesGroupId === null) {
                    $psEcotaxTaxRulesGroupId = (int) \Configuration::get('PS_ECOTAX_TAX_RULES_GROUP_ID');
                }
                // reinit the tax manager for ecotax handling
                $tax_manager = \TaxManagerFactory::getManager(
                    $address,
                    $psEcotaxTaxRulesGroupId
                );
                $ecotax_tax_calculator = $tax_manager->getTaxCalculator();
                $price += $ecotax_tax_calculator->addTaxes($ecotax);
            } else {
                $price += $ecotax;
            }
        }

        // Reduction
        $specific_price_reduction = 0;
        if (($only_reduc || $use_reduc) && $specific_price) {
            if ($specific_price['reduction_type'] == 'amount') {
                $reduction_amount = $specific_price['reduction'];

                if (!$specific_price['id_currency']) {
                    $reduction_amount = \Tools::convertPrice($reduction_amount, $id_currency);
                }

                $specific_price_reduction = $reduction_amount;

                // Adjust taxes if required

                if (!$use_tax && $specific_price['reduction_tax']) {
                    $specific_price_reduction = $product_tax_calculator->removeTaxes($specific_price_reduction);
                }
                if ($use_tax && !$specific_price['reduction_tax']) {
                    $specific_price_reduction = $product_tax_calculator->addTaxes($specific_price_reduction);
                }
            } else {
                $specific_price_reduction = $price * $specific_price['reduction'];
            }
        }

        if ($use_reduc) {
            $price -= $specific_price_reduction;
        }

        // Group reduction
        if ($use_group_reduction) {
            $reduction_from_category = \GroupReduction::getValueForProduct($id_product, $id_group);
            if ($reduction_from_category !== false) {
                $group_reduction = $price * (float) $reduction_from_category;
            } else { // apply group reduction if there is no group reduction for this category
                $group_reduction = (($reduc = \Group::getReductionByIdGroup($id_group)) != 0) ? ($price * $reduc / 100) : 0;
            }

            $price -= $group_reduction;
        }

        if ($only_reduc) {
            return \Tools::ps_round($specific_price_reduction, $decimals);
        }

        $price = \Tools::ps_round((float) $price, $decimals);

        if ($price < 0) {
            $price = 0;
        }

        return $price;
    }

    /**
     * Returns the specificPrice information related to a given productId and context.
     *
     * @param int $specificPriceId
     *
     * @return array<mixed>|bool|false|object|null
     */
    private function getSpecificPrice($specificPriceId)
    {
        if (!\SpecificPrice::isFeatureActive()) {
            return [];
        }

        return $this->specificPriceRepository->getSpecificPrice($specificPriceId);
    }
}