- Redesigned category create/edit modal with elegant wizard-style UI - Redesigned batch categorization modal with visual cards and better preview - Added missing i18n translations (common.continue, creating, remove) - Added budgets.general and wizard translations for ES, PT-BR, EN - Fixed 3 demo user transactions that were missing categories
120 lines
4.1 KiB
PHP
120 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\LiabilityAccount;
|
|
use App\Models\LiabilityInstallment;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class GenerateDemoInstallments extends Command
|
|
{
|
|
protected $signature = 'demo:generate-installments';
|
|
protected $description = 'Gerar parcelas de exemplo para passivos DEMO que não têm parcelas';
|
|
|
|
public function handle(): int
|
|
{
|
|
$this->info('Verificando passivos sem parcelas...');
|
|
|
|
// Buscar passivos que têm total_installments > 0 mas não têm parcelas
|
|
$accounts = LiabilityAccount::withCount('installments')
|
|
->having('installments_count', '=', 0)
|
|
->where('total_installments', '>', 0)
|
|
->get();
|
|
|
|
if ($accounts->isEmpty()) {
|
|
$this->info('Todos os passivos já têm parcelas geradas.');
|
|
return Command::SUCCESS;
|
|
}
|
|
|
|
$this->info("Encontrados {$accounts->count()} passivos sem parcelas.");
|
|
|
|
DB::beginTransaction();
|
|
try {
|
|
foreach ($accounts as $account) {
|
|
$this->generateInstallments($account);
|
|
}
|
|
DB::commit();
|
|
$this->info('✓ Parcelas geradas com sucesso!');
|
|
return Command::SUCCESS;
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
$this->error('Erro: ' . $e->getMessage());
|
|
return Command::FAILURE;
|
|
}
|
|
}
|
|
|
|
private function generateInstallments(LiabilityAccount $account): void
|
|
{
|
|
$this->info("Gerando parcelas para: {$account->name}");
|
|
|
|
$totalInstallments = $account->total_installments;
|
|
$paidInstallments = $account->paid_installments ?? 0;
|
|
$principal = $account->principal_amount;
|
|
$annualRate = $account->annual_interest_rate ?? 0;
|
|
$monthlyRate = $annualRate / 12 / 100;
|
|
$startDate = $account->first_due_date ?? $account->start_date ?? now();
|
|
|
|
if (is_string($startDate)) {
|
|
$startDate = Carbon::parse($startDate);
|
|
}
|
|
|
|
// Calcular parcela mensal (sistema PRICE)
|
|
if ($monthlyRate > 0) {
|
|
$pmt = $principal * ($monthlyRate * pow(1 + $monthlyRate, $totalInstallments)) /
|
|
(pow(1 + $monthlyRate, $totalInstallments) - 1);
|
|
} else {
|
|
$pmt = $principal / $totalInstallments;
|
|
}
|
|
|
|
$balance = $principal;
|
|
$installments = [];
|
|
|
|
for ($i = 1; $i <= $totalInstallments; $i++) {
|
|
$dueDate = $startDate->copy()->addMonths($i - 1);
|
|
|
|
// Calcular juros e capital
|
|
$interestAmount = $balance * $monthlyRate;
|
|
$principalPaid = $pmt - $interestAmount;
|
|
|
|
// Última parcela ajusta para zerar o saldo
|
|
if ($i === $totalInstallments) {
|
|
$principalPaid = $balance;
|
|
$pmt = $principalPaid + $interestAmount;
|
|
}
|
|
|
|
$balance -= $principalPaid;
|
|
|
|
// Determinar status
|
|
$isPaid = $i <= $paidInstallments;
|
|
$status = $isPaid ? 'paid' : ($dueDate->isPast() ? 'overdue' : 'pending');
|
|
|
|
$installments[] = [
|
|
'liability_account_id' => $account->id,
|
|
'installment_number' => $i,
|
|
'due_date' => $dueDate->format('Y-m-d'),
|
|
'installment_amount' => round($pmt, 2),
|
|
'principal_amount' => round($principalPaid, 2),
|
|
'interest_amount' => round($interestAmount, 2),
|
|
'fee_amount' => 0,
|
|
'paid_amount' => $isPaid ? round($pmt, 2) : 0,
|
|
'paid_date' => $isPaid ? $dueDate->format('Y-m-d') : null,
|
|
'status' => $status,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
}
|
|
|
|
// Inserir em lotes
|
|
foreach (array_chunk($installments, 50) as $chunk) {
|
|
LiabilityInstallment::insert($chunk);
|
|
}
|
|
|
|
$this->info(" ✓ {$totalInstallments} parcelas criadas");
|
|
|
|
// Recalcular totais
|
|
$account->recalculateTotals();
|
|
}
|
|
}
|