HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/SBogers10/structura.komma.pro/app/KommaApp/Shop/Discounts/DiscountService.php
<?php
namespace App\KommaApp\Shop\Discounts;

use App\KommaApp\Kms\Core\AbstractTranslatableModel;
use App\KommaApp\Kms\Core\Attributes\Attribute;
use App\KommaApp\Kms\Core\Attributes\Models\SelectOption;
use App\KommaApp\Kms\Core\KmsInterface;
use App\KommaApp\Kms\Core\Sections\SectionService;
use App\KommaApp\Kms\Core\Sections\SectionTabItem;
use App\KommaApp\Kms\SidebarListItem;
use App\KommaApp\Languages\Models\Language;
use App\KommaApp\Shop\Cart\HasCouponCodesInterface;
use App\KommaApp\Shop\Cart\ShoppingCartItemInterface;
use App\KommaApp\Shop\Discounts\Actions\ModifyPriceAction;
use App\KommaApp\Shop\Discounts\Conditions\CouponDiscountCondition;
use App\KommaApp\Shop\Discounts\Conditions\DiscountableCondition;
use App\KommaApp\Shop\Discounts\Conditions\QuantityDiscountCondition;
use App\KommaApp\Shop\Products\Product\Product;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

/**
 * Class DiscountService
 *
 * Can add discount to discountables
 *
 * @package App\KommaApp\Shop\Discounts
 */
class DiscountService extends SectionService implements DiscountServiceInterface
{
    protected $sortable = false;

    /**
     * DiscountService constructor.
     */
    public function __construct()
    {
        $this->forModelName = Discount::class;
        parent::__construct();
    }

    /**
     * Applies discounts if needed to the Discountable given
     *
     * @param DiscountableInterface $discountable
     * @return DiscountServiceInterface
     */
    public function updateDiscounts(DiscountableInterface $discountable): DiscountServiceInterface
    {
        //Get discounts
        /** @var Collection $discountModels */
        $discountModels = $this->models()->get();

        $this->processDiscountModelsForDiscountable($discountModels, $discountable);

        //Fluently return the Discountable so that another call on it can be done
        return $this;
    }

    /**
     * Exchanges a coupon code into a discount if possible
     * @param string $couponCode
     * @param DiscountableInterface $discountable
     * @return void
     */
    public function applyCouponCode(string $couponCode, DiscountableInterface $discountable): void
    {
        if(is_a($discountable, HasCouponCodesInterface::class)) throw new \InvalidArgumentException('The discountable "'.get_class().'" must also implement the HasCouponCodesInterface to be able to receive a coupon code');
        /** @var $discountable HasCouponCodesInterface|DiscountableInterface */
        $discountable->addCouponCode($couponCode); //Does not mean that the coupon code is valid

        /** @var Collection $discountModels */
        $discountModels = $this->models()->where('type', '=', DiscountTypes::Coupon)->get();

        $this->processDiscountModelsForDiscountable($discountModels, $discountable);
    }

    /**
     * Iterates over all given discountModels, inserts their conditions and actions, and checks if they should be applied to the discountable and if so, applies them to the discountable.
     *
     * @param Collection $discountModels
     * @param DiscountableInterface $discountable
     */
    private function processDiscountModelsForDiscountable(Collection $discountModels, DiscountableInterface $discountable)
    {
        //Validate that everything in the $discountModels collection really are DiscountableInterface instances
        $discountModels->each(function ($discountModel)
        {
            if(!is_a($discountModel, Discount::class)) throw new \RuntimeException('The given models must all be an instance of DiscountModel. "'.get_class($discountModel).'" was found at at least one position in the collection');
        });

        //Fill discounts with discount conditions and actions
        $discountModels = $this->insertDiscountConditionAndActionsIfNotInsertedAlready($discountModels, $discountable);

        $discountModels->each(function ($discountModel) use ($discountable) {
            /** @var Discount $discountModel */
            if($discountModel->isValidAndActive()) {
                if ($discountModel->getDiscountCondition()->canBeApplied($discountable)) {
                    //If the Discount can be applied to the discountable apply it via the addDiscount method on the Discountable
                    $discountable->applyDiscount($discountModel);
                }
            }
        });
    }


    /**
     * Loop over a collection of DiscountModel items and insert the correct discount condition and action in it depending on the discount type
     * using the discounts condition and action setting
     *
     * @param Collection $discounts
     * @return Collection
     */
    public function insertDiscountConditionAndActionsIfNotInsertedAlready(Collection $discounts): Collection
    {
        $discounts->each(function($discount) {
            /** @var Discount $discount */
            switch ($discount->type)
            {
                case DiscountTypes::Discountable;
                    if(!$discount->hasDiscountCondition()) $discount->setDiscountCondition(DiscountableCondition::newInstanceFromDiscountSettings($discount->condition_settings));
                    if(!$discount->hasDiscountAction()) $discount->setDiscountAction(ModifyPriceAction::newInstanceFromDiscountSettings($discount->action_settings));
                    break;
                case DiscountTypes::Quantity;
                    if(!$discount->hasDiscountCondition()) $discount->setDiscountCondition(QuantityDiscountCondition::newInstanceFromDiscountSettings($discount->condition_settings));
                    if(!$discount->hasDiscountAction()) $discount->setDiscountAction(ModifyPriceAction::newInstanceFromDiscountSettings($discount->action_settings));
                    break;
                case DiscountTypes::Coupon;
                    if(!$discount->hasDiscountCondition()) $discount->setDiscountCondition(CouponDiscountCondition::newInstanceFromDiscountSettings($discount->condition_settings));
                    if(!$discount->hasDiscountAction()) $discount->setDiscountAction(ModifyPriceAction::newInstanceFromDiscountSettings($discount->action_settings));
                    break;
                default:
                    throw new \RuntimeException('The discount with id "'.$discount->id.'" did have an unsupported type of "'.$discount->type.'". It must be one of the DiscountTypes class\' constants');
            }
        });

        return $discounts;
    }

    /**
     * Returns option models for the select attribute referring to types
     *
     * @return SelectOption[]
     */
    public function getOptionsModelsForTypeSelect():array
    {
        $models = [];
        $models[] = (new SelectOption())->setHtmlContent(__('shop/discounts.quantity'))->setValue(DiscountTypes::Quantity);
        $models[] = (new SelectOption())->setHtmlContent(__('shop/discounts.coupon'))->setValue(DiscountTypes::Coupon);
        $models[] = (new SelectOption())->setHtmlContent(__('shop/discounts.shopping_cart_or_product'))->setValue(DiscountTypes::Discountable);

        return $models;
    }


    /**
     * Creates, updates, deletes the quantity discount for a specific discountable. Currently we only support product instances
     *
     * @param Product $discountable //TODO this may also be a ProductGroup and ProductComposite i think! Maybe we need to invent an interface to group all of them
     * @param null $quantity
     * @param null $quantityPrice
     */
    public function manageQuantityDiscountForASpecificDiscountable(Product $discountable, $quantity = null, $quantityPrice = null)
    {
        if($quantityPrice && $quantity)
        {
            //Create or update a discount
            $discountAction = new ModifyPriceAction($quantityPrice, ModifyPriceAction::MethodAbsolute);
            $discountCondition = new QuantityDiscountCondition(ShoppingCartItemInterface::class, $quantity, [$discountable->id]);

            //Check in the database if there is a quantity discount with the same condition as specified above. If it does not exist we create the new discount.
            $discount = Discount::where('condition_settings', '=', $discountCondition->getValue())->first();
            if(!$discount)
            {
                $discount = new Discount();
                $discount->type = DiscountTypes::Quantity;
                $discount->active = 1;
                $discount->valid_from = new Carbon();
                $discount->valid_trough = (new Carbon())->addYearsNoOverflow(30);
            }

            //Check if there already where quantity discounts for this specific product but not necessary the with same quantity. We delete those discounts
            $likeValue = QuantityDiscountCondition::getSearchLikeValue(ShoppingCartItemInterface::class, '%', [$discountable->id]);
            $oldDiscounts = Discount::where('condition_settings', '=', $likeValue)->get();
            $oldDiscounts->each(function($discount, $index) {
                /** @var Discount $discount */
                $discount->delete();
            });


            $discount->setDiscountAction($discountAction)->setDiscountCondition($discountCondition);

            $discount->save();

            /** @var KmsInterface $kms */
            $kms = \App::make(KmsInterface::class);

            /** @var Language $language */
            $language = Language::find($kms->getSiteDefaultLanguage());

            /** @var DiscountTranslation $translation */
            $translation = $discount->translation()->first();
            if(!$translation) $translation = new DiscountTranslation();

            $translation->title = 'Quantity discount condition';
            $translation->description = 'Automatically created via a section';
            $discount->translation()->save($translation);

            $translation->language()->associate($language);
            $translation->save();
        } else {
            //Delete if needed
            $likeValue = QuantityDiscountCondition::getSearchLikeValue(ShoppingCartItemInterface::class, '%', [$discountable->id]);
            /** @var AbstractTranslatableModel $discount */
            $discount = Discount::where('condition_settings', 'like', $likeValue)->where('type', '=', DiscountTypes::Quantity)->first();

            if($discount) {
                try {
                    if($discount->translation()->first()) $discount->translation()->delete();
                    $discount->delete();
                } catch (\Exception $e) {
                    \Log::debug('Could not delete discount model for a discountable with id: '.$discountable->id);
                }
            }
        }
    }

    /**
     * Gets the quantity discount for a specific discountable. Currently we only support product instances
     *
     * @param Product $discountable //TODO this may also be a ProductGroup and ProductComposite i think! Maybe we need to invent an interface to group all of them
     * @return Discount|null
     */
    public function getQuantityDiscountForASpecificDiscountable(Product $discountable): ?Discount
    {
        $likeValue = QuantityDiscountCondition::getSearchLikeValue(ShoppingCartItemInterface::class, '%', [$discountable->id]);

        $discounts = Discount::where('condition_settings', 'like', $likeValue)->get();
        if($discounts)
        {
            $discounts = $this->insertDiscountConditionAndActionsIfNotInsertedAlready($discounts);
            return $discounts->first();
        }

        return null;
    }


    /**
     * Returns all models for the sidebar menu in the backend
     *
     * @return array
     */
    public function getModelsForSideBar():array
    {
        $models = $this->forModelName::all();

        $sidebarList = [];
        foreach ($models as $model) {
            /** @var Discount $model */

            $sidebarListItem = new SidebarListItem();
            $this->setThumbnail($model);
            $this->generateThumbnail($model);

            //Set the values for the sidebar
            $sidebarListItem->setId($model->id);
            $sidebarListItem->setStatus($model->active);
            $title = $model->translation()->first()->title;
            $sidebarListItem->setName($title);
            $sidebarListItem->setThumbnail($model->thumbnail);

            $sidebarList[] = $sidebarListItem;
        }

        return $sidebarList;
    }
}