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/csb.komma.pro/app/Vat/VatService.php
<?php
namespace App\Vat;


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

/**
 * Class KmsUserService
 *
 * @package App\Users\Kms
 */
final class VatService extends ModelService implements ModelServiceInterface
{
    protected $sortable = false;

    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;
    }
}