File: D:/HostingSpaces/SBogers10/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;
}
}