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/farmfun/reserveren.farmfun.be/app/Komma/Shop/Vat/VatService.php
<?php

namespace App\Komma\Shop\Vat;

use App\Komma\Kms\Core\Attributes\Attribute;
use App\Komma\Kms\Core\ModelService;
use App\Komma\Kms\Core\ModelServiceInterface;
use App\Komma\Kms\Core\Tree\NestedSets\Nodes\TreeModel;
use App\Komma\Shop\Categories\Kms\CategorizableInterface;
use App\Komma\Shop\Orders\Models\Order;
use App\Komma\Shop\Products\ProductableInterface;
use App\Komma\Shop\Vat\Models\Rate;
use App\Komma\Sites\HasSitesInterface;
use App\Komma\Sites\Models\Site;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use RuntimeException;

/**
 * Class KmsUserService
 */
final class VatService extends ModelService implements ModelServiceInterface
{
    protected $sortable = false;

    public function __construct()
    {
        $this->forModelName = Rate::class;

        parent::__construct();
    }

    /**
     * Puts the values of attributes in an Eloquent model. And then saves that model.
     *
     * @param Model $model
     * @param Collection $attributes
     * @return Model
     */
    public function save(Model $model, Collection $attributes = null): Model
    {
        /** @var TreeModel|CategorizableInterface $model */
        //Process Product and discount Specific attributes

        $this->checkContainsAttributes($attributes);
        $keys = array_keys($model->getAttributes());

        $attributes->each(function (Attribute $attribute) use (&$model, &$keys) {
            $valueFrom = $attribute->getsValueFrom();
            $valueReference = $attribute->getsValueFromReference();
            $value = $attribute->getValue();

            switch ($attribute->getsValueFrom()) {
                case Attribute::ValueFromItself:
                    if ($valueReference == 'site_id') {
                        $this->siteService->linkModelToSitesUsingIdCsvString($model, $value);
                    }
                    break;
            }
        });

        $model = parent::saveModel($model, $attributes);

        return $model;
    }

    /**
     * Gets the values of an Eloquent model and passes them to a collection of attributes
     *
     * @param Model $model
     * @param Collection $attributes
     * @return mixed
     */
    public function load(Model $model, Collection $attributes = null): Collection
    {
        if ($attributes == null) {
            return new Collection();
        }

        $attributes = $attributes->map(function (Attribute $attribute) use (&$model) {
            $valueReference = $attribute->getsValueFromReference();
            $valueFrom = $attribute->getsValueFrom();

            if ($valueFrom !== Attribute::ValueFromModel) {
                return $attribute;
            }

            switch ($valueReference) {
                //Case 1. Check if the translation of the model has a value.
                case 'site_id':
                    /** @var HasSitesInterface $model */
                    $idString = $this->siteService->getSiteIdsForModel($model);
                    if ($idString) {
                        $attribute->setValue($idString);
                    }
                    break;
            }

            return $attribute;
        });

        return $this->treeService->load($model, $attributes);
    }

    /**
     * Calculates the orders vat amount for the current site and the total order price including vat.
     * It fills the vat_amount and total_price (including vat) of an order.
     *
     * @param $order
     */
    public function calculateOrderVatTotal(Order $order)
    {
        if (! $order->total_price_ex || $order->total_price_ex == 0) {
            throw new RuntimeException('Could not calculate vat total for order since it does not have a total price excluding vat.');
        }

        //The total price ex must be an integer or a float with zero decimal
        if ($order->total_price_ex - floor($order->total_price_ex) != 0) {
            throw new RuntimeException('Could not calculate vat total for order since the total price excluding vat isn\'t an integer representing a monetary value in cents.');
        }

        $order->vat_percentage = $this->getVatRate()->percentage;
        $order->vat_amount = $this->calculateVatRateAmountFromExAmount($order->total_price_ex);
        $order->total_price = $order->total_price_ex + $order->vat_amount; //No rounding needed. Both numbers are integer like.
    }

    /**
     * Returns the vat rate for the current site
     *
     * @return Rate
     */
    public function getVatRate(): Rate
    {
        $currentSite = $this->siteService->getCurrentSite();
        $vatRates = $this->vatRatesForSite($currentSite)->where('default', '=', true)->get();
        if ($vatRates->count() > 1) {
            throw new RuntimeException('The site with name "'.$currentSite->name.'" must have exactly 1 vat rate marked as default. But did have '.$vatRates->count().'.');
        }

        /** @var Rate $vatRate */
        $vatRate = $vatRates->first();
        if (! $vatRate) {
            throw new RuntimeException('The site with name "'.$currentSite->name.'" must have vat rate that is marked as default. But did not have one.');
        }

        return $vatRate;
    }

    /**
     * Gets the vat rates for a certain site or the current one if your don't specify it.
     *
     * @param Site|null $site
     * @return Builder
     */
    public function vatRatesForSite(Site $site = null): Builder
    {
        if (! $site) {
            $site = $this->siteService->getCurrentSite();
            if (! $site) {
                throw new RuntimeException('The current site was not set but must be.');
            }
        }
        $vatRatesQuery = Rate::whereHas('sites', function (Builder $query) use ($site) {
            $query->where('site_id', '=', $site->id);
        })->orWhereDoesntHave('sites');

        return $vatRatesQuery;
    }

    /**
     * @param ProductableInterface $productable
     * @return ProductableInterface
     */
    public function calculateVatForProductable(ProductableInterface $productable)
    {
        $price_ex = $productable->getPrice();
        $productable->vat_amount = $this->calculateVatRateAmountFromExAmount($price_ex);
        $productable->price_inc = $this->calculateIncVatRatePrice($productable->getPrice());

        return $productable;
    }

    /**
     * Receives a price in cents and returns the price in cents including vat.
     *
     * @param float $exPriceInCents
     * @param Rate|null $rate If you dont specify it, a default vat rate will be used.
     * @return int The price including vat. The decimals represent fractions of cents.
     */
    public function calculateIncVatRatePrice(float $exPriceInCents, Rate $rate = null): int
    {
        if (! $rate) {
            $rate = $this->getVatRate();
        }
        $priceInc = $exPriceInCents * floatval('1.'.$rate->percentage);
        $result = $this->roundVatTotal($priceInc);

        return $result;
    }

    /**
     * Receives a price in cents, that includes a given vat amount,
     * and returns the price in cents without vat.
     *
     * @param float $incPriceInCents
     * @param Rate|null $rate If you dont specify it, a default vat rate will be used.
     * @return int The price excluding vat.
     */
    public function calculateExVatRatePrice(float $incPriceInCents, Rate $rate = null): int
    {
        if (! $rate) {
            $rate = $this->getVatRate();
        }
        $priceEx = $incPriceInCents / floatval('1.'.$rate->percentage);
        $result = $this->roundVatTotal($priceEx);

        return $result;
    }

    /**
     * Calculates the total amount of vat in cents for a certain given amount in cents.
     * Warning! Rounds fractions of cents according to a rule in a config file.
     *
     * @param float $amount
     * @param Rate|null $rate If you dont specify it, a default vat rate will be used.
     * @return int
     */
    public function calculateVatRateAmountFromExAmount(float $amount, Rate $rate = null): int
    {
        if (! $rate) {
            $rate = $this->getVatRate();
        }
        $vatAmount = ($rate->percentage * 0.01) * $amount;
        $result = $this->roundVatTotal($vatAmount);

        return $result;
    }

    /**
     * Receives a price in cents, that includes a given vat amount, and returns the amount of vat in cents.
     * @param float $incPriceInCents
     * @param Rate|null $rate If you dont specify it, a default vat rate will be used.
     * @return int
     */
    public function calculateVatRateAmountFromIncAmount(float $incPriceInCents, Rate $rate = null): int
    {
        if (! $rate) {
            $rate = $this->getVatRate();
        }
        $priceEx = $incPriceInCents / floatval('1.'.$rate->percentage);
        $result = $this->roundVatTotal($incPriceInCents - $priceEx);

        return $result;
    }

    /**
     * Rounds the vat total cent fractions to cents.
     *
     * @param float $vatTotal
     * @return int
     */
    protected function roundVatTotal(float $vatTotal): int
    {
        $result = round($vatTotal, 0, config('financial.cent_rounding.vat_total', PHP_ROUND_HALF_UP));

        return $result;
    }
}