ekmungai/eloquent-ifrs

View on GitHub
src/Models/ReportingPeriod.php

Summary

Maintainability
C
1 day
Test Coverage
A
97%
<?php

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

namespace IFRS\Models;

use Carbon\Carbon;

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

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

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

use IFRS\Exceptions\MissingReportingPeriod;
use IFRS\Exceptions\InvalidAccountType;
use IFRS\Exceptions\InvalidPeriodStatus;
use IFRS\Exceptions\MissingClosingRate;

use IFRS\Reports\BalanceSheet;
use IFRS\Transactions\JournalEntry;

/**
 * Class ReportingPeriod
 *
 * @package Ekmungai\Eloquent-IFRS
 *
 * @property Entity $entity
 * @property integer $year
 * @property integer $period_count
 * @property string $status
 * @property Carbon $destroyed_at
 * @property Carbon $deleted_at
 */
class ReportingPeriod extends Model implements Segregatable, Recyclable
{
    use Segregating;
    use SoftDeletes;
    use Recycling;
    use ModelTablePrefix;

    /**
     * Reporting Period Status
     *
     * @var string
     */

    const OPEN = "OPEN";
    const CLOSED = "CLOSED";
    const ADJUSTING = "ADJUSTING";

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'period_count',
        'calendar_year',
        'status',
        'entity_id',
    ];

    /**
     * Construct new Reporting Period.
     */
    public function __construct($attributes = [])
    {
        if (!isset($attributes['status'])) {
            $attributes['status'] = ReportingPeriod::OPEN;
        }
        return parent::__construct($attributes);
    }

    /**
     * Fetch reporting period for the date
     *
     * @param string|Carbon $date
     * @return ReportingPeriod
     */
    public static function getPeriod($date = null, Entity $entity = null)
    {
        if (is_null($entity)) {
            $entity = Auth::user()->entity;
        }

        $year = ReportingPeriod::year($date, $entity);

        $period = ReportingPeriod::where("calendar_year", $year)
            ->where('entity_id', '=', $entity->id)->first();
        if (is_null($period)) {
            throw new MissingReportingPeriod($entity->name, $year);
        }
        return $period;
    }

    /**
     * ReportingPeriod year
     *
     * @param string | Carbon $date
     *
     * @return int
     */
    public static function year($date = null, Entity $entity = null)
    {
        if (is_null($entity)) {
            $entity = Auth::user()->entity;
        }

        if (is_null($entity)) {
            return date("Y");
        }

        $year = is_null($date) ? date("Y") : date("Y", strtotime($date));
        $month = is_null($date) ? date("m") : date("m", strtotime($date));

        $year = intval($month) < $entity->year_start ? intval($year) - 1 : $year;

        return intval($year);
    }

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

    /**
     * Closing Rates.
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function closingRates()
    {
        return $this->hasMany(ClosingRate::class);
    }

    /**
     * Closing Transactions.
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function closingTransactions()
    {
        return $this->hasMany(ClosingTransaction::class);
    }

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

    /**
     * Get the auto generated translation transactions for the period.
     *
     * @return array
     */
    public function getTranslations(): array
    {
        $transactions = [];

        foreach ($this->closingTransactions as $translation) {
            $transaction = Transaction::find($translation->transaction_id);
            $account = $transaction->account;
            $balances = $account->closingBalance($transaction->transaction_date, $translation->currency_id);
            $closingRate = ClosingRate::where('reporting_period_id', $this->id)
                ->whereHas('ExchangeRate', function ($q) use ($translation) {
                    $q->where('currency_id', $translation->currency_id);
                })->first()->exchangeRate->rate;
            $credited = $transaction->credited ? -1 : 1;
            $transactions[$account->name][] = [
                'currency' => $transaction->reference,
                'closingRate' => $closingRate,
                'currencyBalance' => $balances[$translation->currency_id],
                'localBalance' => $balances[$transaction->currency_id],
                'foreignBalance' => $balances[$translation->currency_id] * $closingRate,
                'translation' => $transaction->amount * $credited,
                'posted' => $transaction->is_posted
            ];
        }
        return $transactions;
    }

    /**
     * Commit the auto generated translation transactions for the period to the ledger.
     *
     * @return void
     */
    public function postTranslations(): void
    {
        foreach ($this->closingTransactions as $translation) {
            $transaction = Transaction::find($translation->transaction_id);
            $transaction->post();
        }
    }

    /**
     * Prepare Forex Account Balances translations.
     *
     * @param int $forexAccountId
     * @param int $accountId
     *
     * @return array $transactions
     */
    public function prepareBalancesTranslation($forexAccountId, int $accountId = null): array
    {

        if (Account::find($forexAccountId)->account_type != Account::EQUITY) {
            throw new InvalidAccountType('Transaltion Forex', Account::EQUITY);
        }

        if ($this->status != ReportingPeriod::ADJUSTING) {
            throw new InvalidPeriodStatus();
        }

        $rates = $transactions = [];
        foreach ($this->transactionCurrencies() as $currency) {
            $closingRate = ClosingRate::where('reporting_period_id', $this->id)
                ->whereHas('ExchangeRate', function ($q) use ($currency) {
                    $q->where('currency_id', $currency->currency_id);
                });

            if ($closingRate->count() == 0) {
                $currencyCode = Currency::find($currency->currency_id)->currency_code;
                throw new MissingClosingRate($currencyCode);
            }
            $rates[$currency->currency_id] = $closingRate->get()->first()->exchangerate->rate;
        }

        $reportingCurrency = $this->entity->currency_id;
        $periodEnd = ReportingPeriod::periodEnd($this->calendar_year . '01-01', $this->entity);

        $accounts = Account::whereNotIn('currency_id', [$reportingCurrency]);
        if (!is_null($accountId)) {
            $accounts->where('id', $accountId);
        }
        foreach ($accounts->where('entity_id', '=', $this->entity->id)->get() as $account) {
            if (!$account->isClosed($this->calendar_year)) {
                $balances = $account->closingBalance($periodEnd);
                if (array_sum($balances) <> 0) {
                    foreach ($this->transactionCurrencies($account->id) as $currency) {
                        $balances = $account->closingBalance($periodEnd, $currency->currency_id);
                        $localBalance = $balances[$reportingCurrency];
                        $foreignBalance = $balances[$currency->currency_id] * $rates[$currency->currency_id];

                        if ($localBalance <> round($foreignBalance, config('ifrs.forex_scale'))) {
                            $transactions[] = $this->balanceAccount(
                                $forexAccountId,
                                $account,
                                $localBalance,
                                $foreignBalance,
                                $periodEnd,
                                $currency
                            );
                        }
                    }
                }
            }
        }
        return $transactions;
    }

    /**
     * Retrieve the currencies used in trasactions for the period.
     *
     * @param int $accountId
     *
     * @return collection
     */
    public function transactionCurrencies($accountId = null)
    {
        $reportingCurrency = $this->entity->currency_id;
        $transactionTable = config('ifrs.table_prefix') . 'transactions';
        $currenciesTable = config('ifrs.table_prefix') . 'currencies';
        $query = DB::table($transactionTable)
            ->leftJoin($currenciesTable, $currenciesTable . '.id', '=', $transactionTable . '.currency_id')
            ->whereYear($transactionTable . '.transaction_date', '=', $this->calendar_year)
            ->whereNotIn($currenciesTable . '.id', [$reportingCurrency])
            ->select($transactionTable . '.currency_id', $currenciesTable . '.currency_code')
            ->distinct();

        if (!is_null($accountId)) {
            $query->where($transactionTable . '.account_id', $accountId);
        }

        return $query->get();
    }

    /**
     * ReportingPeriod end date
     *
     * @return Carbon
     */
    public static function periodEnd($date = null, Entity $entity = null)
    {
        return ReportingPeriod::periodStart($date, $entity)
            ->addYear()
            ->subDay();
    }

    /**
     * ReportingPeriod start date
     *
     * @return Carbon $date
     */
    public static function periodStart($date = null, Entity $entity = null)
    {
        if (is_null($entity)) {
            if (Auth::user()) {
                $entity = Auth::user()->entity;
            }
        }
        return is_null($entity) ? Carbon::parse(date("Y") . "-01-01")->startOfDay() : Carbon::create(
            ReportingPeriod::year($date, $entity),
            $entity->year_start,
            1
        )->startOfDay();
    }

    /**
     * Closing Rates.
     *
     * @param int $forexAccountId
     * @param Account $account
     * @param float $localBalance
     * @param float $foreignBalance
     * @param Carbon $closingDate
     * @param object $currency
     *
     * @return Transaction
     */
    private function balanceAccount(
        int $forexAccountId,
        Account $account,
        float $localBalance,
        float $foreignBalance,
        Carbon $closingDate,
        object $currency
    ): Transaction
    {

        $difference = $foreignBalance - $localBalance;
        $isAsset = in_array($account->account_type, config('ifrs')[BalanceSheet::ASSETS]);
        $isLiability = in_array($account->account_type, config('ifrs')[BalanceSheet::LIABILITIES]);

        if ($isAsset && $difference > 0 || $isLiability && $difference < 0) {
            $credited = true;
        } elseif ($isAsset && $difference < 0 || $isLiability && $difference > 0) {
            $credited = false;
        }

        $entity = $account->entity;

        $balanceTransaction = JournalEntry::create([
            "account_id" => $account->id,
            "transaction_date" => $closingDate,
            "narration" => $currency->currency_code . " " . $this->calendar_year . " Forex Balance Translation",
            "reference" => $currency->currency_code,
            "credited" => $credited,
            "entity_id" => $entity->id
        ]);

        $balanceTransaction->addLineItem(LineItem::create([
            'account_id' => $forexAccountId,
            'amount' => abs($difference),
            'entity_id' => $entity->id
        ]));

        $balanceTransaction->save();

        ClosingTransaction::create([
            "reporting_period_id" => $this->id,
            "transaction_id" => $balanceTransaction->id,
            "currency_id" => $currency->currency_id,
            "entity_id" => $entity->id
        ]);
        return $balanceTransaction;
    }
}