ekmungai/eloquent-ifrs

View on GitHub
src/Models/Balance.php

Summary

Maintainability
A
1 hr
Test Coverage
A
94%
<?php

/**
 * Eloquent IFRS Accounting
 *
 * @author    Edward Mungai
 * @copyright Edward Mungai, 2020, Germany
 * @license   MIT
 */

namespace IFRS\Models;

use Illuminate\Support\Facades\Auth;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

use IFRS\Reports\IncomeStatement;

use IFRS\Interfaces\Clearable;
use IFRS\Interfaces\Recyclable;
use IFRS\Interfaces\Segregatable;

use IFRS\Traits\Clearing;
use IFRS\Traits\Recycling;
use IFRS\Traits\Segregating;
use IFRS\Traits\ModelTablePrefix;

use IFRS\Exceptions\NegativeAmount;
use IFRS\Exceptions\InvalidBalanceType;
use IFRS\Exceptions\InvalidBalanceDate;
use IFRS\Exceptions\InvalidBalanceTransaction;
use IFRS\Exceptions\InvalidAccountClassBalance;
use IFRS\Exceptions\InvalidCurrency;

/**
 * Class Balance
 *
 * @package Ekmungai\Eloquent-IFRS
 *
 * @property Entity $entity
 * @property Account $account
 * @property Currency $currency
 * @property ExchangeRate $exchangeRate
 * @property integer $year
 * @property string $reference
 * @property string $transaction_no
 * @property string $transaction_type
 * @property string $balance_type
 * @property float $total_amount
 * @property float $balance
 * @property Carbon $destroyed_at
 * @property Carbon $deleted_at
 */
class Balance extends Model implements Recyclable, Clearable, Segregatable
{
    use Segregating;
    use SoftDeletes;
    use Recycling;
    use Clearing;
    use ModelTablePrefix;

    /**
     * Balance Model Name
     *
     * @var string
     */

    const MODELNAME = self::class;

    /**
     * Balance Type
     *
     * @var string
     */

    const DEBIT = "D";
    const CREDIT = "C";

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'currency_id',
        'exchange_rate_id',
        'account_id',
        'reporting_period_id',
        'transaction_no',
        'reference',
        'balance_type',
        'entity_id',
        'transaction_type',
        'transaction_date',
        'balance'
    ];

    /**
     * Construct new Balance.
     */
    public function __construct($attributes = [])
    {
        if (!isset($attributes['transaction_type'])) {
            $attributes['transaction_type'] = Transaction::JN;
        }

        if (!isset($attributes['balance_type'])) {
            $attributes['balance_type'] = Balance::DEBIT;
        }

        return parent::__construct($attributes);
    }

    /**
     * Get Human Readable Balance Type.
     *
     * @param string $type
     *
     * @return string
     */
    public static function getType($type): string
    {
        return config('ifrs')['balances'][$type];
    }

    /**
     * Get Human Readable Balance types
     *
     * @param array $types
     *
     * @return array
     */
    public static function getTypes($types): array
    {
        $typeNames = [];

        foreach ($types as $type) {
            $typeNames[] = Balance::getType($type);
        }
        return $typeNames;
    }

    /**
     * Instance Identifier.
     *
     * @return string
     */
    public function toString($type = false): string
    {
        $classname = explode('\\', self::class);
        $description = $this->account->toString() . ' for year ' . $this->reportingPeriod->calendar_year;
        return $type ? $this->type . ' ' . array_pop($classname) . ': ' . $description : $description;
    }

    /**
     * Instance Type Translator.
     *
     * @return string
     */
    public function getTypeAttribute(): string
    {
        return Balance::getType($this->balance_type);
    }

    /**
     * Instance Transaction Type Translator.
     *
     * @return string
     */
    public function getTransactionAttribute(): string
    {
        return Transaction::getType($this->transaction_type);
    }

    /**
     * is_posted analog for Assignment model.
     */
    public function getIsPostedAttribute(): bool
    {
        return $this->exists();
    }

    /**
     * is_credited analog for Assignment model.
     *
     * @return bool
     */
    public function getIsCreditedAttribute(): bool
    {
        return $this->balance_type == Balance::CREDIT;
    }

    /**
     * cleared_type analog for Assignment model.
     *
     * @return string
     */
    public function getClearedTypeAttribute(): string
    {
        return Balance::MODELNAME;
    }

    /**
     * amount analog for Assignment model.
     *
     * @return float
     */
    public function getAmountAttribute(): float
    {
        return $this->balance / $this->exchangeRate->rate;
    }

    /**
     * Balance Currency.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function currency()
    {
        return $this->belongsTo(Currency::class);
    }

    /**
     * Balance Account.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function account()
    {
        return $this->belongsTo(Account::class);
    }

    /**
     * Balance Exchange Rate.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function exchangeRate()
    {
        return $this->belongsTo(ExchangeRate::class);
    }

    /**
     * Balance Reporting Period.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function reportingPeriod()
    {
        return $this->belongsTo(ReportingPeriod::class);
    }

    /**
     * Balance attributes.
     *
     * @return object
     */
    public function attributes(): object
    {
        return (object)$this->attributes;
    }

    /**
     * Balance Validation.
     * 
     * @return bool
     */
    public function save(array $options = []): bool
    {
        if (is_null($this->entity_id)) {
            $entity = Auth::user()->entity;
        } else {
            $entity = Entity::where('id', '=', $this->entity_id)->first();
        }

        if (!is_null($entity)) {
            $reportingPeriod = $entity->current_reporting_period;

            if (!isset($this->reporting_period_id)) {
                $this->reporting_period_id = $reportingPeriod->id;
            }

            if (!isset($this->exchange_rate_id)) {
                $this->exchange_rate_id = $entity->default_rate->id;
            }
        }

        if ($this->amount < 0) {
            throw new NegativeAmount("Balance");
        }

        if (!in_array($this->transaction_type, Assignment::CLEARABLES)) {
            throw new InvalidBalanceTransaction(Assignment::CLEARABLES);
        }

        if (!in_array($this->balance_type, [Balance::DEBIT, Balance::CREDIT])) {
            throw new InvalidBalanceType([Balance::DEBIT, Balance::CREDIT]);
        }

        if (in_array($this->account->account_type, IncomeStatement::getAccountTypes())) {
            throw new InvalidAccountClassBalance();
        }

        if (in_array($this->account->account_type, config('ifrs.single_currency')) && $this->account->currency_id != $this->currency_id) {
            throw new InvalidCurrency("Balance", $this->account);
        }

        if (ReportingPeriod::periodStart(null, $entity)->lt($this->transaction_date) && !$entity->mid_year_balances) {
            throw new InvalidBalanceDate();
        }

        if (!isset($this->currency_id)) {
            $this->currency_id = $this->account->currency_id;
        }

        if (!isset($this->transaction_no)) {
            $currency = $this->currency->currency_code;
            $year = ReportingPeriod::find($this->reporting_period_id)->calendar_year;
            $this->transaction_no = $this->account_id . $currency . $year;
        }

        $this->balance *= $this->exchangeRate->rate;

        return parent::save();
    }
}