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/farmfun.komma.pro/app/Komma/Availability/AvailabilityService.php
<?php

namespace App\Komma\Availability;

use App\Komma\Availability\Queries\AbstractAvailabilityProductFilterQuery;
use App\Komma\Availability\Queries\ProductCategoryQuery;
use App\Komma\Availability\Queries\SearchAmountQuery;
use App\Komma\Availability\Types\Availability;
use App\Komma\Availability\Types\TimeSlot;
use App\Komma\Base\Service;
use App\Komma\CalendarBlockOuts\CalendarBlockOutService;
use App\Komma\LocationProducts\Models\LocationProduct;
use App\Komma\Locations\LocationService;
use App\Komma\Locations\Models\Location;
use App\Komma\Locations\Types\DayInfo;
use App\Komma\ProductCategories\Models\ProductCategory;
use App\Komma\Products\Models\Product;
use App\Komma\Reservations\Models\ReservationItem;
use App\Komma\SearchAmountOfPersons\SearchAmountOfPersonsService;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\MessageBag;

final class AvailabilityService extends Service
{
    public static $reservedItems;

    private static $locationId;

    const SEARCH_LOCATION_KEY = 'searchLocation';

    const SEARCH_DATE_KEY = 'searchDate';

    const SEARCH_AMOUNT_KEY = 'searchAttendeesCount';

    const SEARCH_VARIABLES = [self::SEARCH_LOCATION_KEY, self::SEARCH_DATE_KEY, self::SEARCH_AMOUNT_KEY];

    /** @var array */
    private $additionalProductFilters = [];

    /**
     *  Check and return possible redirect response if search for wrong amount of persons
     *
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|void
     */
    public function checkIfShouldRedirect()
    {
        if (! $searchAmountKey = request()->input(__('site/availability.keys.' . self::SEARCH_AMOUNT_KEY))) {
            return;
        }

        /** @var SearchAmountOfPersonsService $searchAmountOfPersonsService */
        $searchAmountOfPersonsService = app(SearchAmountOfPersonsService::class);
        if (! $searchAmountOption = $searchAmountOfPersonsService->getOptionByLabel($searchAmountKey)) {
            throw new \UnexpectedValueException(self::class . ': Option ' . $searchAmountKey . ' not found in searchAmountOptions collection ');
        }

        if (empty($searchAmountOption->redirect_to_page)) {
            return;
        }

        $searchAmountOption->load('redirectPage');

        // Throw error if relation can't be resolved or is inactive
        if (! isset($searchAmountOption->redirectPage) || ! $searchAmountOption->redirectPage->active) {
            throw new \LogicException(self::class . ': Redirect page (id: ' . $searchAmountOption->redirect_to_page . ') has not been found or is inactive.');
        }

        // Load the needed relation for redirecting
        $searchAmountOption->redirectPage->load('translation', 'translation.route');

        // Throw error if relation can't be resolved or is inactive
        if (empty($searchAmountOption->redirectPage->translation) || empty($searchAmountOption->redirectPage->translation->route)) {
            throw new \LogicException(self::class . ': Redirect page "' . $searchAmountOption->redirectPage->code_name . '" has not a translation and/or route');
        }

        if (! empty($searchAmountOption->redirect_message)) {
            return redirect($searchAmountOption->redirectPage->translation->route->alias, 303)
                ->with([
                    'flashMessages' => new MessageBag([
                        [
                            'type' => 'warning',
                            'message' => $searchAmountOption->redirect_message,
                        ],
                    ]),
                ]);
        }

        // Redirect
        return redirect($searchAmountOption->redirectPage->translation->route->alias, 303);
    }

    /**
     * Make an availability out of the request information
     *
     * @return Availability
     */
    public function makeAvailabilityFromRequest(array $availabilityRequest)
    {
        // Make availability out of request.
        $availability = $this->makeAvailabilityByProductAndLocationId($availabilityRequest['product_id'], $availabilityRequest['location_id']);
        $availability->setDate($availabilityRequest['date']);
        $availability->setAmountOfPersons($availabilityRequest['amount_of_persons']);

        // Append the possible additional settings to the availability
//        if(request()->get('amount_of_persons', '') != '') $availability->setAmountOfPersons(request()->get('amount_of_persons'));
//        if(!empty($availabilityRequest['date'])) $availability->setDate($availabilityRequest['date']);
//        if(request()->get('notifications', '') != '') $availability->setNotification(request()->get('notifications'));

        return $availability;
    }

    /**
     * Make an availability out of the request information
     *
     * @return Availability
     */
    public function makeAvailabilityFromGetRequest($product, int $location, Carbon $date)
    {
        // Make availability out of request.
        $availability = $this->makeAvailabilityByProductAndLocationId($product->id, $location);

        // Append the possible additional settings to the availability
        $availability->setDate($date->format('d-m-Y'));
        if (isset($product->timeSlot)) {
            $availability->timeSlot = $product->timeSlot;
        }
        if ($product->persons != '') {
            $availability->setAmountOfPersons($product->persons);
        }

        return $availability;
    }

    /**
     * Make an availability out of the product and location
     *
     * @param int $productId
     * @param int $locationId
     * @return Availability
     */
    public function makeAvailabilityByProductAndLocationId(int $productId, int $locationId) : Availability
    {
        $product = Product::where('id', $productId)
            ->where('active', 1)
            ->first();

        $location = Location::where('id', $locationId)
            ->where('active', 1)
            ->first();

        return new Availability($product, $location);
    }

    /**
     * Check if availability class is possible within the system.
     *
     * @param Availability $availability
     * @return bool
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function isAvailabilityValid(Availability &$availability, bool $appendTimeSlots = false) : bool
    {
        /** @var DayInfo $dayInfo */
        $dayInfo = $availability->getLocation()->availability->infoForDay($availability->getDate());

        // Special exception for given product
        if ($availability->getProduct()->id == 50) {
            $nthReservations = ReservationItem::where('location_id', '=', $availability->getLocation()->id)
                ->where('product_id', '=', 50)
                ->where('date', '=', $availability->getDate()->format('Y-m-d'))
                ->count();

            if ($availability->getDate()->format('Y-m-d') !== '2022-09-18') {
                $availability->available = false;
                $availability->available_reason = 'unavailable';

                return false;
            }

            if ($nthReservations < 12) {
                $availability->available = true;
            } else {
                $availability->available = false;
                $availability->available_reason = 'full';

                return false;
            }

            if ($appendTimeSlots) {
                $timeSlotDate = $availability->getDate();

                $startTime = clone $timeSlotDate;
                $startTime->setHour(13);

                $endTime = clone $timeSlotDate;
                $endTime->setHour(16);

                $availability->timeSlots = collect([new TimeSlot($startTime, $endTime, $endTime)]);
            }

            return true;
        }

        /** Check if day is available at location */
        if (! $dayInfo->open) {
            $availability->available_reason = 'closed';

            return $availability->available = false;
        }

        /**
         * Check if availability has been blocked for that day.
         * This could be because of the whole day has been blocked for the location or a activity specific
         *
         * @var CalendarBlockOutService $calendarBlockOutService
         */
        $calendarBlockOutService = app()->make(CalendarBlockOutService::class);
        if ($calendarBlockOutService->isAvailabilityBlockedForToday($availability)) {
            $availability->available_reason = 'unavailable';

            return $availability->available = false;
        }

        // Make time slots for availability
        $timeSlots = $this->makeTimeSlots($dayInfo, $availability->getProduct()->system_duration, $availability->getProduct()->duration, $availability);

        /** Check if there are some time slots not locked. */
        if ($timeSlots->where('locked', false)->count() <= 0) {
            $availability->available_reason = 'full';

            return $availability->available = false;
        }

//        if($availability->getLocation()->id === 6 && app()->environment('local')) {
//            $availability->available_reason = 'full';
//            return $availability->available = false;
//        }

        // Everything is valid
        if ($appendTimeSlots) {
            $availability->timeSlots = $timeSlots;
        }

        return $availability->available = true;
    }

    /**
     * Append the desired additional function we want to call.
     * Set the productCategory
     *
     * @param ProductCategory $productCategory
     */
    public function setProductCategoryFilter(ProductCategory $productCategory)
    {
        $this->additionalProductFilters[] = new ProductCategoryQuery($productCategory);
    }

    /**
     * Get the availabilityResultCollection based upon the defined search inputs
     *
     * @return AvailabilityResultCollection
     */
    public function getAvailabilityResultCollection(): AvailabilityResultCollection
    {
        /** @var LocationService $locationService */
        $locationService = app(LocationService::class);

        /** @var AvailabilityResultCollection $availabilityResultCollection */
        $availabilityResultCollection = new AvailabilityResultCollection();

        // If location key is 0 (is statically defined), we load all the available location
        if (session()->get(self::SEARCH_LOCATION_KEY, 0) == 0) {
            $locations = $locationService->getLocations();
        } else {
            // Else we get the filled location by looking up it's key
            $location = $locationService->getLocationById(session()->get(self::SEARCH_LOCATION_KEY));
            $locations = collect([$location]);
        }

        foreach ($locations as $location) {
            $locationProducts = $this->getAvailableProductsForLocation($location);
            $availabilityResults = $this->convertProductsIntoAvailabilityResults($locationProducts, $location);
            $availabilityResultCollection->addResults($availabilityResults);
        }

        return $availabilityResultCollection;
    }

    /**
     * Get the bound products of a location filtered by the search parameters
     *
     * @param Location $location
     * @return Collection
     */
    private function getAvailableProductsForLocation(Location $location)
    {
        // First of all, get the bound products by location
        $productFilterQuery = $location->boundProducts()
            ->where('hide_on_site', '!=', 1);

        // Filter the products that are within the amount of persons range
        $searchAmountQuery = new SearchAmountQuery();
        if ($searchAmountQuery->shouldRunFilter()) {
            $productFilterQuery = $searchAmountQuery->filter($productFilterQuery);
        }

        // Call additional filter query we have defined in additionalProductFilters
        // This will be classes that are in the Availability/Queries folder
        /** @var AbstractAvailabilityProductFilterQuery $additionalProductFilter */
        foreach ($this->additionalProductFilters as $additionalProductFilter) {
            $productFilterQuery = $additionalProductFilter->filter($productFilterQuery);
        }

        return $productFilterQuery->with('translation', 'overviewImage')->get();
    }

    /**
     * Convert the Products into AvailabilityResults
     *
     * @param Collection $products
     * @param Location $location
     * @return \Illuminate\Support\Collection
     */
    private function convertProductsIntoAvailabilityResults(Collection $products, Location $location)
    {
        $results = collect();

        foreach ($products as $product) {
            $result = new Availability($product, $location);
            $result->appendCapacity($product->pivot->max_persons_each_block, $product->pivot->available_each_block, $product->pivot->capacity_type);

            $date = session()->get(self::SEARCH_DATE_KEY);
            if (! isset($date)) {
                $date = Carbon::today()->addDays(14)->format('d-m-Y');
            }

            $result->setDate($date);

            $this->isAvailabilityValid($result);

            $results->push($result);
        }

        return $results;
    }

    /**
     * Gets a random availability for each location
     *
     * @return AvailabilityResultCollection
     */
    public function getRandomAvailabilityForEachLocation(): AvailabilityResultCollection
    {
        /** @var LocationService $locationService */
        $locationService = app(LocationService::class);

        $locations = $locationService->getLocations();
        $locations->load('availability');

        /** @var AvailabilityResultCollection $availabilityResultCollection */
        $availabilityResultCollection = new AvailabilityResultCollection();

        $excludedIds = [];

        foreach ($locations as $location) {
            // First of all, get the bound products by location
            $locationProduct = $location->boundProducts()
                ->where('hide_on_site', '!=', 1)
                ->where('product_type', '=', 0) // Do - Activities
                ->whereNotIn('products.id', $excludedIds)
                ->where('products.price_each_unit', '!=', 0)
                ->inRandomOrder()
                ->take(1)
                ->get();

            // Prevent duplicate products
            if ($locationProduct->count() != 0) {
                $excludedIds[] = $locationProduct->first()->id;
            }

            $availabilityResults = $this->convertProductsIntoAvailabilityResults($locationProduct, $location);
            $availabilityResultCollection->addResults($availabilityResults);
        }

        return $availabilityResultCollection;
    }

    /**
     * Get an amount of random availbilities for a given location and e
     *
     * @param Location $location
     * @param int $amount
     * @param array $excludedProductIds
     * @return AvailabilityResultCollection
     */
    public function getAmountOfRandomAvailabilitiesForLocationWhereNot(Location $location, int $amount, array $excludedProductIds = []): AvailabilityResultCollection
    {
        /** @var AvailabilityResultCollection $availabilityResultCollection */
        $availabilityResultCollection = new AvailabilityResultCollection();

        $otherAvailabilitiesQuery = $location->boundProducts()
            ->where('hide_on_site', '!=', 1);

        // If we have any excluded ids we run the where not in
        if (count($excludedProductIds) != 0) {
            $otherAvailabilitiesQuery = $otherAvailabilitiesQuery->whereNotIn('products.id', $excludedProductIds);
        }

        $otherAvailabilities = $otherAvailabilitiesQuery->inRandomOrder()
            ->take($amount)
            ->get();

        $availabilityResults = $this->convertProductsIntoAvailabilityResults($otherAvailabilities, $location);
        $availabilityResultCollection->addResults($availabilityResults);

        return $availabilityResultCollection;
    }

    /**
     * Get an amount of food activities for a given location
     *
     * @param int $amount
     * @param array $excludedProductIds
     * @return \Illuminate\Support\Collection
     */
    public function getAmountOfFoodAvailabilities(int $amount, array $excludedProductIds, Location $location): \Illuminate\Support\Collection
    {
        $productQuery = Product::where('active', 1)
            ->where('hide_on_site', '!=', 1)
            ->where('product_type', '=', 1) // Food is product Type 1
            ->with('overviewImage', 'translation', 'locations');

        // If we have any excluded ids we run the where not in
        if (count($excludedProductIds) != 0) {
            $productQuery = $productQuery->whereNotIn('id', $excludedProductIds);
        }

        return $productQuery->orderBy('price_each_unit', 'asc')
            ->take($amount)
            ->get()
            ->map(function ($product) use ($location) {

                $locationProduct = LocationProduct::where('product_id', $product->id)
                    ->where('location_id', $location->id)
                    ->first();

                return (object) [
                    'id' => $product->id,
                    'name' => $product->translation->name,
                    'slug' => $product->translation->slug,
                    'overview_image' => $product->overviewImage->small_image_url ?? null,
                    'price_label' => $this->getCartLabel($locationProduct, $product),
                    'locations' => $product->locations->pluck('id')->toArray() ?? [],
                    'minimum_amount_of_persons' => $product->minimum_amount_of_persons,
                ];
            });
    }

    /**
     * Make time slots by the given day info and the duration.
     *
     * @param DayInfo $dayInfo
     * @param $duration
     * @param $visualDuration
     * @param Availability $availability
     * @return \Illuminate\Support\Collection
     */
    public function makeTimeSlots(DayInfo $dayInfo, $duration, $visualDuration, Availability $availability = null): \Illuminate\Support\Collection
    {
        /** @var \Illuminate\Support\Collection $timeSlots */
        $timeSlots = collect();

        // First time slot
        $loopableTimeSlot = $dayInfo->from;

        // Day end
        $dayInfoEnd = clone $dayInfo;
        $dayInfoEnd->latest_start->addHours($visualDuration);

        // Loop till we have reached the cap (or latest_start or end time exceed till)
        while ($loopableTimeSlot <= $dayInfoEnd->latest_start) {
            // clone the time slot (because it remembers it origin and will also increment with the loop)
            $slotStartTime = clone $loopableTimeSlot;

            $slotEndTime = clone $loopableTimeSlot;
            $slotEndTime->addMinutes($duration * 60);

            $slotVisualEndTime = clone $loopableTimeSlot;
            $slotVisualEndTime->addMinutes($visualDuration * 60);

            // If the end time is larger the end of the openings hours break
            if ($slotEndTime > $dayInfoEnd->latest_start || $slotStartTime > $dayInfo->till) {
                break;
            }

            $timeSlot = new TimeSlot($slotStartTime, $slotEndTime, $slotVisualEndTime);

            // If availability is given, we also validate if the slot is available
            if (isset($availability)) {
                $timeSlot->locked = ! $this->isTimeSlotAvailable($timeSlot, $availability);
            }

            // Append a new TimeSlot
            $timeSlots[] = $timeSlot;

            // Increment the time slot
            $loopableTimeSlot->addMinutes(config('site.time_gap'));
//            $breakCounter++;
        }

        return $timeSlots;
    }

    /**
     * Validate a given time slot
     *
     * @param TimeSlot $timeSlot
     * @param Availability $availability
     * @return bool
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function isTimeSlotAvailable(TimeSlot $timeSlot, Availability $availability): bool
    {
        if (! self::$reservedItems || $availability->getLocation()->id !== self::$locationId) {
            self::$locationId = $availability->getLocation()->id;
            self::$reservedItems = ReservationItem::where('location_id', '=', $availability->getLocation()->id)
                ->where('date', '=', $availability->getDate()->format('Y-m-d'))
                ->orderBy('start_time')
                ->get();
        }

        /**
         * Check that the timeslot is not locked because upon a block out
         * @var CalendarBlockOutService $calendarBlockOutService
         */
        $calendarBlockOutService = app()->make(CalendarBlockOutService::class);
        if ($calendarBlockOutService->isTimeSlotForAvailabilityBlocked($timeSlot, $availability)) {
            return false;
        }

        // First prep some values for the next checks
//        $reservations = self::$reservedItems->groupBy('reservation_id'); // Items group by reservation
        $reservationStartItems = self::$reservedItems->unique('reservation_id'); // Start items of the reservations
        $reservationItemsWithOverlap = self::$reservedItems->where('end_time', '>', $timeSlot->start->format('H:i:s'))
            ->where('start_time', '<', $timeSlot->end->format('H:i:s'));

        /**
         * Check if the amount of starting reservation at the same time is below the max amount of starting
         * This is defined in the config
         */
        $amountOfReservationWithSameStartTime = $reservationStartItems->where('start_time', '=', $timeSlot->start->format('H:i:s'))->count();
        if ($amountOfReservationWithSameStartTime >= config('site.max_start_item_each_time_gap')) {
            return false;
        }

        /**
         * Checks that only needs to be done when there are overlapping items
         */
        if ($reservationItemsWithOverlap->count() >= 1) {
            // Get the overlapping activities with the same products
            $sameActivitiesWithOverlap = $reservationItemsWithOverlap->where('product_id', $availability->getProduct()->id);

            /**
             * Checks only for same activities within the overlapping
             */
            if ($sameActivitiesWithOverlap->count() >= 1) {
                if (! $this->validateCapacity($timeSlot, $availability, $sameActivitiesWithOverlap)) {
                    return false;
                }
            }
        }

        // No check have return false so therefor available
        return true;
    }

    private function validateCapacity(TimeSlot $timeSlot, Availability $availability, $overlappingActivities)
    {
        // Get capacity values from availability
        [$maxPersonEachBlock, $availableEachBlock, $capacityType] = $availability->getCapacity();

        // Quick exit functions for the capacity types
        // This way we don't have to loop through the possible overlapping time slots :)
        switch ($capacityType) {
            // Unlimited therefor always true
            case LocationProduct::CAPACITY_TYPE_UNLIMITED:
                return true;

            case LocationProduct::CAPACITY_TYPE_MAX_PERSONS:
                if ($maxPersonEachBlock == 0) {
                    Log::channel('daily')->warning(self::class . ': ValidateCapacity function has been called but in fact the LocationAvailability (for product id "' . $availability->getProduct()->id . '" and location id "' . $availability->getLocation()->id . '") should be inactive.. ');

                    return false;
                }
                break;

            case LocationProduct::CAPACITY_TYPE_AVAILABLE:
                if ($availableEachBlock == 0) {
                    Log::channel('daily')->warning(self::class . ': ValidateCapacity function has been called but in fact the LocationAvailability (for product id "' . $availability->getProduct()->id . '" and location id "' . $availability->getLocation()->id . '") should be inactive.. ');

                    return false;
                }
                break;

            case LocationProduct::CAPACITY_TYPE_MAX_PERSONS_AND_AVAILABLE:
                if ($availableEachBlock == 0 || $maxPersonEachBlock == 0) {
                    Log::channel('daily')->warning(self::class . ': ValidateCapacity function has been called but in fact the LocationAvailability (for product id "' . $availability->getProduct()->id . '" and location id "' . $availability->getLocation()->id . '") should be inactive.. ');

                    return false;
                }
                break;
            default:
                throw new \RuntimeException(self::class . ': LocationProduct for constant value "' . $capacityType . '" has not been defined.');
        }

        $loopTimeBlock = $timeSlot->start;

        // Loop till we have reached the cap (or latest_start or end time exceed till)
        while ($loopTimeBlock < $timeSlot->end) {
            // clone the time slot (because it remembers it origin and will also increment with the loop)
            $slotStartTime = clone $loopTimeBlock;
            $slotEndTime = clone $loopTimeBlock;
            $slotEndTime->addMinutes(config('site.time_gap'));

            // Get the activities during this time block
            $activitiesInTimeSlots = $overlappingActivities->where('end_time', '>', $slotStartTime->format('H:i:s'))
                ->where('start_time', '<', $slotEndTime->format('H:i:s'));

            /**
             * If zero activities continue
             */
            if ($activitiesInTimeSlots->count() == 0) {
                $loopTimeBlock = $slotEndTime;
                continue;
            }

            switch ($capacityType) {
                case LocationProduct::CAPACITY_TYPE_MAX_PERSONS:
                    if ($this->doesAmountOfPersonsExceedsCapacity($availability, $activitiesInTimeSlots, $maxPersonEachBlock)) {
                        return false;
                    }
                    break;

                case LocationProduct::CAPACITY_TYPE_AVAILABLE:
                    if ($activitiesInTimeSlots->count() >= $availableEachBlock) {
                        return false;
                    }
                    break;

                case LocationProduct::CAPACITY_TYPE_MAX_PERSONS_AND_AVAILABLE:
                    debug($activitiesInTimeSlots->count() >= $availableEachBlock);
                    if ($activitiesInTimeSlots->count() >= $availableEachBlock) {
                        return false;
                    }
                    if ($this->doesAmountOfPersonsExceedsCapacity($availability, $activitiesInTimeSlots, $maxPersonEachBlock)) {
                        return false;
                    }
                    break;

                default:
                    throw new \RuntimeException(self::class . ': LocationProduct handling for constant value "' . $capacityType . '" has not been defined.');
            }

            $loopTimeBlock = $slotEndTime;
        }

        return true;
    }

    /**
     * @param Availability $availability
     * @param Collection $activitiesInTimeSlots
     * @param int $maxPersonEachBlock
     * @return bool
     */
    private function doesAmountOfPersonsExceedsCapacity(Availability $availability, Collection $activitiesInTimeSlots, int $maxPersonEachBlock): bool
    {
        // Start value is amount of persons of when undefined the minimum amount of persons
        $capacityFilled = $availability->getAmountOfPersons();
        if (! isset($capacityFilled)) {
            $capacityFilled = $availability->getProduct()->minimum_amount_of_persons;
        }

        // Increment the capacity with the already stored items
        foreach ($activitiesInTimeSlots as $activityInTimeSlots) {
            $capacityFilled += $activityInTimeSlots->quantity;
        }

        // If capacity exceed the max persons each block fail validation
        if ($capacityFilled > $maxPersonEachBlock) {
            return true;
        }

        return false;
    }

    private function getCartLabel($locationProduct, $product): string
    {

        if (! $locationProduct || $locationProduct->use_location_product_price === 0) {
           return $product->getCartLabel(true, false) . ' (incl. btw)';
        }

        if ($locationProduct->price_each_unit !== 0) {
            $cartLine = config('site.shop_currency') . ' ' . euro_pricing_format($locationProduct->price_each_unit) . ' ' . __('site/availability.price_each_person');
            if (! $locationProduct->has_fixed_price && $locationProduct->price_start_up !== 0) {
                $cartLine .= ' <br/>+ ' . config('site.shop_currency') . ' ' . euro_pricing_format($locationProduct->price_start_up) . ' ' . __('site/availability.start_up_small');
            }
            $cartLine .= ' (incl. btw)';

            return $cartLine;
        } else {
            return config('site.shop_currency') . ' ' . $locationProduct->getPriceLabel(true, false) . ' (incl. btw)';
        }

    }
}