141 lines
5.0 KiB
PHP
141 lines
5.0 KiB
PHP
<?php
|
|
|
|
namespace Modules\Accounting\Services;
|
|
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Modules\Accounting\Domain\FinancialPostingRequest;
|
|
use Modules\Accounting\Entities\AccountingAccount;
|
|
use Modules\Accounting\Entities\AccountingFiscalPeriod;
|
|
use Modules\Accounting\Utils\AccountingUtil;
|
|
|
|
class PeriodCloseService
|
|
{
|
|
public function __construct(
|
|
protected FinancialPostingService $postingService,
|
|
protected AccountingUtil $accountingUtil
|
|
) {}
|
|
|
|
public function isDateLocked(int $businessId, string $date): bool
|
|
{
|
|
return AccountingFiscalPeriod::where('business_id', $businessId)
|
|
->where('is_closed', true)
|
|
->whereDate('start_date', '<=', $date)
|
|
->whereDate('end_date', '>=', $date)
|
|
->exists();
|
|
}
|
|
|
|
public function createPeriod(int $businessId, string $name, string $startDate, string $endDate): AccountingFiscalPeriod
|
|
{
|
|
return AccountingFiscalPeriod::create([
|
|
'business_id' => $businessId,
|
|
'name' => $name,
|
|
'start_date' => $startDate,
|
|
'end_date' => $endDate,
|
|
'is_closed' => false,
|
|
]);
|
|
}
|
|
|
|
public function closePeriod(int $businessId, int $periodId, int $userId, ?int $retainedEarningsAccountId = null): AccountingFiscalPeriod
|
|
{
|
|
$period = AccountingFiscalPeriod::where('business_id', $businessId)->findOrFail($periodId);
|
|
|
|
if ($period->is_closed) {
|
|
throw new \RuntimeException('Period already closed.');
|
|
}
|
|
|
|
$balanceFormula = $this->accountingUtil->balanceFormula();
|
|
$incomeExpense = AccountingAccount::join('accounting_accounts_transactions as AAT', 'AAT.accounting_account_id', '=', 'accounting_accounts.id')
|
|
->where('accounting_accounts.business_id', $businessId)
|
|
->whereIn('accounting_accounts.account_primary_type', ['income', 'expense', 'expenses'])
|
|
->whereDate('AAT.operation_date', '>=', $period->start_date)
|
|
->whereDate('AAT.operation_date', '<=', $period->end_date)
|
|
->select(
|
|
'accounting_accounts.id',
|
|
'accounting_accounts.account_primary_type',
|
|
DB::raw($balanceFormula)
|
|
)
|
|
->groupBy('accounting_accounts.id', 'accounting_accounts.account_primary_type')
|
|
->get();
|
|
|
|
$lines = [];
|
|
$netIncome = 0.0;
|
|
|
|
foreach ($incomeExpense as $row) {
|
|
$balance = (float) $row->balance;
|
|
if (abs($balance) < 0.01) {
|
|
continue;
|
|
}
|
|
|
|
if ($row->account_primary_type === 'income') {
|
|
$netIncome += $balance;
|
|
$lines[] = [
|
|
'accounting_account_id' => $row->id,
|
|
'amount' => abs($balance),
|
|
'type' => $balance > 0 ? 'debit' : 'credit',
|
|
];
|
|
} elseif (in_array($row->account_primary_type, ['expense', 'expenses'], true)) {
|
|
$netIncome -= $balance;
|
|
$lines[] = [
|
|
'accounting_account_id' => $row->id,
|
|
'amount' => abs($balance),
|
|
'type' => $balance > 0 ? 'credit' : 'debit',
|
|
];
|
|
}
|
|
}
|
|
|
|
if ($retainedEarningsAccountId && abs($netIncome) >= 0.01) {
|
|
$lines[] = [
|
|
'accounting_account_id' => $retainedEarningsAccountId,
|
|
'amount' => abs($netIncome),
|
|
'type' => $netIncome >= 0 ? 'credit' : 'debit',
|
|
];
|
|
}
|
|
|
|
if (! empty($lines)) {
|
|
$mapping = $this->postingService->post(new FinancialPostingRequest(
|
|
businessId: $businessId,
|
|
userId: $userId,
|
|
sourceType: 'period_close',
|
|
sourceId: $period->id,
|
|
note: 'Period close: '.$period->name,
|
|
lines: $lines,
|
|
operationDate: Carbon::parse($period->end_date)->endOfDay()->toDateTimeString(),
|
|
mappingType: 'journal_entry',
|
|
));
|
|
|
|
$period->closing_journal_mapping_id = $mapping?->id;
|
|
}
|
|
|
|
$period->is_closed = true;
|
|
$period->closed_at = now();
|
|
$period->closed_by = $userId;
|
|
$period->save();
|
|
|
|
return $period;
|
|
}
|
|
|
|
public function closeAndRollForward(
|
|
int $businessId,
|
|
int $periodId,
|
|
int $userId,
|
|
?int $retainedEarningsAccountId = null,
|
|
bool $createNextPeriod = false
|
|
): AccountingFiscalPeriod {
|
|
$period = $this->closePeriod($businessId, $periodId, $userId, $retainedEarningsAccountId);
|
|
|
|
if ($createNextPeriod) {
|
|
$start = Carbon::parse($period->end_date)->addDay();
|
|
$end = $start->copy()->addYear()->subDay();
|
|
$this->createPeriod(
|
|
$businessId,
|
|
(string) $start->year.' - '.($start->year + 1),
|
|
$start->toDateString(),
|
|
$end->toDateString()
|
|
);
|
|
}
|
|
|
|
return $period;
|
|
}
|
|
}
|